Spring Boot v2.4.4源码解析(六)属性绑定篇上 —— 类型安全配置属性

2024-05-07 00:48

本文主要是介绍Spring Boot v2.4.4源码解析(六)属性绑定篇上 —— 类型安全配置属性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Spring Boot JavaBean 属性绑定

一、概述

在Spring Boot下开发某个模块,如果这个模块配置属性比较多而且具有层级关系,使用@Value("${property}")注解依次注入这些属性显得比较麻烦。Spring Boot提供了另一种替代方案,可以使用@ConfigurationProperties注解将这些属性绑定到类上。

例如,Mybatis将配置属性绑定到`MybatisProperties`类上:
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {public static final String MYBATIS_PREFIX = "mybatis";private String configLocation;private String[] mapperLocations;private String typeAliasesPackage;//  省略其余字段private Configuration configuration;// 省略getter/setter方法
}
public class Configuration {protected boolean safeRowBoundsEnabled;protected boolean safeResultHandlerEnabled = true;protected boolean mapUnderscoreToCamelCase;// 省略其余代码}

MybatisProperties 类中各字段在配置文件中分别取值为:

  • mybatis.config-location
  • mybatis.mapper-locations
  • mybatis.type-aliases-package
  • mybatis.configuration.safe-row-bounds-enabled
  • mybatis.configuration.safe-result-handler-enabled
  • mybatis.configuration.map-underscore-to-camel-case;

类似Bean注入除了可以使用@Autowired注入之外,可以使用构造器注入,属性绑定也可以使用@ConstructorBinding注解进行构造器绑定,参考官方文档Spring Boot Reference Documentation。

二、激活 @ConfigurationProperties 注解类

Spring Boot提供了一下几种机制将@ConfigurationProperties注解类注册到IOC容器:

  • @Configuration类(@Component)上将需要使用到的属性绑定类注解类通过@EnableConfigurationProperties列出;
    @EnableConfigurationProperties(MybatisProperties.class)
    @Configuration
    public class MybatisAutoConfiguration implements InitializingBean {
    }
    
  • 通过包扫描方式将扫描到的@ConfigurationProperties注解类注入到容器。这种方式需要在@SpringBootApplication注解的启动主类,或者任何@Configuration注解类上注解@ConfigurationPropertiesScan,默认扫描包为@ConfigurationPropertiesScan注解类所在包,也可通过basePackages()指出;
    @SpringBootApplication
    @ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" })
    public class MyApplication {
    }
    

一旦@ConfigurationProperties注解类注册到IOC容器成功后,便可以在其他Bean中将其注入。

  • 对于第三方Jar包中的类,不能修改源码,加@Component等注解,Spring Boot提供了@Configuration + @Bean 方式将其注册到IOC容器。那如果属性绑定类源自第三方Jar包呢?解决方法类似,通过在@Configuration 注解类中@Beanpublic方法中在加上@ConfigurationProperties可以将该方法返回的Bean属性绑定并注册到IOC容器中;
    @Configuration
    public class Test {@Bean@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)public MybatisProperties mybatisProperties(){return new MybatisProperties();}
    }
    

三、属性转换

Spring Boot 在将这些配置属性绑定到@ConfigurationProperties Bean属性时尝试转换成正确类型。如果需要自定义转换规则,可以提供ConversionService Bean,或者通过CustomEditorConfigurer Bean自定义属性编辑器,或者通过注解@ConfigurationPropertiesBinding自定义Converters

1. Duration 类型转换

如果绑定类中包含java.time.Duration字段,则配置文件中支持如下格式配置:

  • 带单位或者不带单位的数字,例如,10s表示10秒,数字范围需要在long内。如果配置项纯数字没有带单位,可以通过在绑定类java.time.Duration字段中通过@DurationUnit指定单位,如果都没指定,则取默认值默认为ChronoUnit.MILLIS,即毫秒。
  • 标准ISO-8601格式;

转换源码位于org.springframework.boot.convert.StringToDurationConverter类中,上面的两种格式跟别对应org.springframework.boot.convert.DurationStyle枚举类的SIMPLE(正则表达式^([+-]?\d+)([a-zA-Z]{0,2})$)和ISO8601(正则表达式^[+-]?P.*$),Spring Boot用各自正则表达式确定配置项属于那种格式类型。
对于SIMPLE类型格式,其正则表达式包含两部分([+-]?\d+)([a-zA-Z]{0,2}),表示纯数字的时间间隔和0-2位的单位,单位字母大小写不敏感。
SIMPLE支持的单位定义在DurationStyle内部枚举类Unit中:

  • ns,纳秒;
  • us,微妙;
  • ms,毫秒;
  • s,秒;
  • m,分钟;
  • h,小时;
  • d,天;

解析SIMPLE类型字符串到java.time.Duration源码如下:

Matcher matcher = matcher(value);
Assert.state(matcher.matches(), "Does not match simple duration pattern");
String suffix = matcher.group(2);
return (StringUtils.hasLength(suffix) ? Unit.fromSuffix(suffix) : Unit.fromChronoUnit(unit)).parse(matcher.group(1));

value表示配置项字符串,unit表示java.time.Duration字段@DurationUnit注解指定的单位,从上面源码可以看出,Spring Boot优先使用配置项中配置的单位,如果配置项只包含纯数字,才会使用java.time.Duration字段@DurationUnit注解指定的单位,都没有则默认毫秒。

2. Period 类型转换

Duration类型转换类似,如果绑定类中包含java.time.Period字段,则配置文件中支持如下格式配置:

  • 带单位或者不带单位的数字, 数字范围需要在int内。如果配置项纯数字没有带单位,可以通过在绑定类java.time.Period字段中通过@PeriodUnit指定单位,如果都没指定,则取默认值默认为ChronoUnit.DAYS,即天。
  • 标准ISO-8601格式;

转换源码位于org.springframework.boot.convert.StringToPeriodConverter类中,同样,上面两种格式类型也定义了枚举类org.springframework.boot.convert.PeriodStyle。对于SIMPLE类型格式, 也定义了其支持单位枚举类org.springframework.boot.convert.PeriodStyle.Unit,其支持的单位如下:

  • d,天;
  • w,周;
  • m,月份;
  • y,年份;

Duration不同的是,PeriodSIMPLE类型格式这些单位可以同时出现,所以SIMPLE正则表达式为"^" + "(?:([-+]?[0-9]+)Y)?" + "(?:([-+]?[0-9]+)M)?" + "(?:([-+]?[0-9]+)W)?" + "(?:([-+]?[0-9]+)D)?" + "$"
"(?:([-+]?[0-9]+)Y)?"表示匹配年, "(?:([-+]?[0-9]+)M)?"部分表示匹配月, "(?:([-+]?[0-9]+)W)?"部分表示匹配周,"(?:([-+]?[0-9]+)D)?"部分表示匹配天,?:表示匹配时单位YMWY不存储,这样可以方便抽取出各自单位的数字。
解析SIMPLE类型字符串到java.time.Period源码如下:

if (NUMERIC.matcher(value).matches()) {return Unit.fromChronoUnit(unit).parse(value);
}
Matcher matcher = matcher(value);
Assert.state(matcher.matches(), "Does not match simple period pattern");
Assert.isTrue(hasAtLeastOneGroupValue(matcher), "'" + value + "' is not a valid simple period");
int years = parseInt(matcher, 1);
int months = parseInt(matcher, 2);
int weeks = parseInt(matcher, 3);
int days = parseInt(matcher, 4);
return Period.of(years, months, Math.addExact(Math.multiplyExact(weeks, 7), days));

3. DataSize 类型转换

DataSize类型转换和Duration类似,由于DataSize类型是Spring自定义类org.springframework.util.unit.DataSize,所以不支持ISO-8601格式,而且单位不能重复出现,使用@DataSizeUnit指定org.springframework.util.unit.DataSize字段单位。
其支持单位有:

  • B
  • KB
  • MB
  • GB
  • TB

四、复杂类型合并处理

通过Spring Boot v2.4.4源码解析(五)配置文件加载篇一可以看出,Spring boot配置可能来自多个配置源,如果和List或者Map中各元素绑定的配置项分散在不同配置源时,Spring Boot怎么处理呢?

1. List 整体替换

List在多个地方配置时,Spring Boot会用后面加载的属性源整体覆盖前面加载的配置源,不会合并。
例如,假设MyPojo对象包含namedescription属性,且默认为null。属性绑定类AcmeProperties包含MyPojo对象列表:

@ConfigurationProperties("acme")
public class AcmeProperties {private final List<MyPojo> list = new ArrayList<>();public List<MyPojo> getList() {return this.list;}
}

配置文件如下:

acme.list[0].name=my name
acme.list[0].description=my description
#---
spring.config.activate.on-profile=dev
acme.list[0].name=my another name

以上写法时Spring Boot 2.4 新增特性,可用在一个Java properties配置文件中用#---隔开多个逻辑配置文件。
如果当前激活profiledevAcmeProperties.list只包含一个MyPojo对象,由第一个逻辑配置文件中定义。然而, 当dev profile 处于激活状态时,AcmeProperties.list也只包含一个元素(name属性为my another namedescription属性为null),源自第二个逻辑配置文件。
Spring Boot v2.4.4源码解析(五)配置文件加载篇一已经说明,Spring Boot 2.4 后加载的属性源优先级比先加载属性源优先级高,这里可以理解为Spring Boot整体区分List,而不会以List每一项作为区分,这样相当于不同配置源包含相同属性时,后加载属性覆盖先加载属性。
再例如,如下配置:

acme.list[0].name=my name
acme.list[0].description=my description
acme.list[1].name=another name
acme.list[1].description=another description
#---
spring.config.activate.on-profile=dev
acme.list[0].name=my another name

dev profile 处于激活状态时,AcmeProperties.list也只包含一个元素(name属性为my another namedescription属性为null),源自第二个逻辑配置文件。

2. Map 合并

对于绑定类为Map, Spring Boot 会将所有配置源中相关属性合并到Map中,如果多配置源中包含相同属性,则使用高优先级(后加载)。
例如,重新定义AcmeProperties如下:

@ConfigurationProperties("acme")
public class AcmeProperties {private final Map<String, MyPojo> map = new HashMap<>();public Map<String, MyPojo> getMap() {return this.map;}
}

考虑如下配置:

acme.map.key1.name=my name 1
acme.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
acme.map.key1.name=dev name 1
acme.map.key2.name=dev name 2
acme.map.key2.description=dev description 2

如果当前激活profiledevAcmeProperties.map包含一个键key1(值对象name属性为my name 1description属性为my description 1)。
dev profile 处于激活状态时,AcmeProperties.map包含两个个键key1(值对象name属性为dev name 1description属性为my description 1)和key2(值对象name属性为dev name 2description属性为dev description 2)。这里可以理解为当配置为Map,Spring Boot按每一项区分,而不是整体区分。

五、@ConfigurationProperties 校验

Controller层参数检验一样,Spring Boot支持在@ConfigurationProperties注解类上使用@Validated对绑定属性开启校验,并可以直接将JSR-303 javax.validation 约束注解加到@ConfigurationProperties注解类属性上进行校验。JSR-303 javax.validation 约束注解主要包括@NotNull(非空),@NotBlank(字符串非空),@Email(邮件格式)等等。
例如:

@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {@NotNullprivate InetAddress remoteAddress;// ... getters and setters
}

@Validated也可用于通过@Configuration+@Bean创建的属性绑定类;
另外需要注意的是,@ConfigurationProperties注解类嵌套属性也开启校验的话,需要在相关字段值标注@Valid注解。
例如:

@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {@NotNullprivate InetAddress remoteAddress;@Validprivate final Security security = new Security();// ... getters and setterspublic static class Security {@NotEmptypublic String username;// ... getters and setters}
}

六、松散绑定

从概述可以看出Java Bean属性和配置属性并不是精确匹配,Java Bean属性采用骆峰式,而配置属性使用短横线命名,也能绑定成功,这是由于Spring Boot采用了松散规则将配置项和@ConfigurationProperties注解类属性进行绑定。
例如,有如下@ConfigurationProperties类:

@ConfigurationProperties(prefix="acme.my-project.person")
public class OwnerProperties {private String firstName;// ... getters and setters
}

对于firstName,如下配置项都可以有其绑定成功:

  • acme.my-project.person.first-name,短横线命名,.properties.yml推荐写法;
  • acme.myProject.person.firstName,标准骆峰式;
  • acme.my_project.person.first_name,下划线分割,.properties.yml替代方案;
  • ACME_MYPROJECT_PERSON_FIRSTNAME,大写格式,只有环境变量数据源systemEnvironment才能使用;

其实使用分数字字母分隔符分割也能匹配成功,比如acme.my#project.person.first#nameacme.my&project.person.first&name等等都能和OwnerPropertiesfirstName绑定成功;

综上,所有数据源简单属性短横线命名,标准骆峰式,下划线分割都能绑定成功,并且环境变量systemEnvironment还支持使用下划线分割的大写方式。
对于List, 除了.properties[])和.yml-)各自配置语法外,还可以使用逗号分割配置。
另外,环境变量也可以配置List,但数字需要用下划线包围, 即.properties中的[]需要变成_,例如在.properties中的my.acme[0].other配置项,在环境变量中需要写成MY_ACME_0_OTHER

七、@ConfigurationProperties 和 @Value对比

特性@ConfigurationPropertie@Value
松散绑定受限
支持元数据×
SpEL计算×

参考

  • [1] Spring Boot Reference Documentation 2.4.1
  • [2] Property Binding in Spring Boot 2.0

这篇关于Spring Boot v2.4.4源码解析(六)属性绑定篇上 —— 类型安全配置属性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/965861

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7