1. 前言
在开发 Spring Boot 利用时会用到依据条件来向 Spring IoC 容器注入Bean。比方配置文件存在了某个配置属性才注入Bean:
图中红色的局部是说,只有 ali.pay.v1.app-id
存在于 Spring 的环境配置中时这个 @Configuration
标记的类能力注入Spring IoC。
这外面的 @ConditionalOnProperty
就是条件注解系列的一种。它还有很多种来满足各种场景的条件注解:
其实数量远不止截图中这几个,在 Spring 家族的其它框架中也有实现。
这里扯得有点远了,明天不是来讲这些条件管制注解的用法的,只是我发现了一个应用条件注解 @ConditionalOnProperty
无奈解决的问题。
条件注入参考往期:Spring Boot 2 实战:应用 @Condition 注解来依据条件注入 Bean
2. 配置文件存在 Map 构造的场景
上面是一段配置文件:
app:
v1:
foo:
name: felord.cn
description: 码农小胖哥
bar:
name: ooxx.cn
description: xxxxxx
对应配置类:
@Data
@ConfigurationProperties("app")
public class AppProperties {
/**
*
*/
private Map<String, V1> v1 = new HashMap<>();
/**
*
*
* @author felord.cn
* @since 1.0.0.RELEASE
*/
@Data
public static class V1 {
/**
* name
*/
private String name;
/**
* description
*/
private String description;
}
}
非凡之处来了,yml
配置里的 foo
、bar
其实是作为 Map
中的 key
来标识 V1
的,和其它配置参数不同,这个 key
用户能够随便定义一个 String
来标识,可能是 foo
,可能是bar
,齐全依据开发者的爱好进行主观定义。这个时候你想依据app.v1.*.name
(临时用通配符*
)来进行@ConditionalOnProperty
判断是行不通的,因为你不确定 *
的值,该怎么办呢?
3. 解决方案
这里我花了一天的工夫去摸索,最开始我认为 Spring 提供通配符(app.v1.*.name
)甚至是 SpringEL
表达式能够拿到,然而搞了半天无功而返。
忽然我想到之前看 Spring Security OAuth2 源码中有相似的逻辑。用过 Spring Security OAuth2 相干的都晓得 Spring Security OAuth2 也要求用户自定义一个 key
来标识本人的 OAuth2 客户端。比方我用 Gitee 的:
spring:
security:
oauth2:
client:
registration:
gitee:
client-id: xxxxxx
client-secret: xxxxx
这里的
key
就是gitee
, 当然这依据开发者情绪决定,甚至你用zhangshan
作为key
都能够。
Spring Security OAuth2 提供了相干的条件注入思路,上面是其条件注入判断的外围类:
public class ClientsConfiguredCondition extends SpringBootCondition {
private static final Bindable<Map<String, OAuth2ClientProperties.Registration>> STRING_REGISTRATION_MAP = Bindable
.mapOf(String.class, OAuth2ClientProperties.Registration.class);
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage.Builder message = ConditionMessage.forCondition("OAuth2 Clients Configured Condition");
Map<String, OAuth2ClientProperties.Registration> registrations = getRegistrations(context.getEnvironment());
if (!registrations.isEmpty()) {return ConditionOutcome.match(message.foundExactly("registered clients" + registrations.values().stream() .map(OAuth2ClientProperties.Registration::getClientId).collect(Collectors.joining(","))));
}
return ConditionOutcome.noMatch(message.notAvailable("registered clients"));
}
private Map<String, OAuth2ClientProperties.Registration> getRegistrations(Environment environment) {return Binder.get(environment).bind("spring.security.oauth2.client.registration", STRING_REGISTRATION_MAP)
.orElse(Collections.emptyMap());
}
}
显然 OAuth2ClientProperties
的构造和咱们要验证的 AppProperties
构造是一样的。所以下面的逻辑是能够抄过来的,它能够将环境配置中的带有不确定 key
的配置绑定到咱们的配置类 AppProperties
中。外围的绑定逻辑是这一段:
Binder.get(environment).bind("spring.security.oauth2.client.registration", STRING_REGISTRATION_MAP)
首先通过 Bindable
来申明一个可绑定的数据结构, 这里调用了 mapOf
办法申明了一个 Map
的数据绑定构造。而后通过绑定的具体操作对象 Binder
从配置环境接口 Environment
中提取了 spring.security.oauth2.client.registration
结尾的配置属性并注入到 Map
中去。既然咱们可能获取到了Map
,依据什么策略判断就齐全把握在咱们手中了。
Bindable
为 Spring Boot 2.0 提供的数据绑定新个性,有趣味可从 spring.io 获取更多信息。
接下来不必我说了吧,照葫芦画瓢还有谁不会呢?配合 @Conditional
注解就能实现依据 app.v1
下参数的理论状况来动静的进行 Bean 注入。
4. 总结
明天利用 Spring Boot 2.0 的数据绑定个性解决了一个理论需要,花了不少工夫。当咱们解决问题陷入困境时,首先要去想想有没有相似场景以及对应的解决方案。这同样阐明平时的积攒很重要,很多粉丝的问题其实公众号都有讲过,所以处处留心解释学识。多多注意:码农小胖哥,独特学习,共同进步。
关注公众号:Felordcn 获取更多资讯
集体博客:https://felord.cn