3

Spring Boot 2.0 的配置绑定类Bindable居然如此强大

 3 years ago
source link: https://www.daqianduan.com/17530.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

7nm2qa.png

1. 前言

在开发 Spring Boot 应用时会用到根据条件来向 Spring IoC 容器注入 Bean 。比如配置文件存在了某个配置属性才注入 Bean

UVruQb.png

图中红色的部分是说,只有 ali.pay.v1.app-id 存在于 Spring 的环境配置中时这个 @Configuration 标记的类才能注入 Spring IoC

这里面的 @ConditionalOnProperty 就是条件注解系列的一种。它还有很多种来满足各种场景的条件注解:

ERJnM3.png

其实数量远不止截图中这几个,在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

#感谢您访问本站#
#本文转载自互联网,若侵权,请联系删除,谢谢!657271#qq.com#

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK