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配置里的 foobar其实是作为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