Spring Boot 2.0 的配置绑定类Bindable居然如此强大
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 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_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 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 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 获取更多资讯
#感谢您访问本站# #本文转载自互联网,若侵权,请联系删除,谢谢!657271#qq.com#