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

Stella981
• 阅读 595

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

1. 前言

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

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

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

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

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这