共计 3768 个字符,预计需要花费 10 分钟才能阅读完成。
Spring Boot2.0 的属性绑定
原文从 Spring boot 第一个版本以来,我们可以使用 @ConfigurationProperties 注解将属性绑定到对象。也可以指定属性的各种不同格式。比如,person.first-name,person.firstName 和 PERSON_FIRSTNAME 都可以使用。这个功能叫做“relaxed binding”。
不幸的是,在 spring boot 1.x,“relaxed binding”显得太随意了。从而使得很难来定义准确的绑定规则和指定使用的格式。在 1.x 的实现中,也很难对其进行修正。比如,在 spring boot 1.x 中,不能将属性绑定到 java.util.Set 对象。
所以,在 spring boot 2.0 中,开始重构属性绑定的功能。我们添加了一些新的抽象类和一些全新的绑定 API。在本篇文章中,我们会介绍其中一些新的类和接口,并介绍添加他们的原因,以及如何在自己的代码中如何使用他们。
Property Sources
如果你已经使用 spring 有一段时间,你应该对 Environment 比较熟悉了。这个接口继承了 PropertyResolver,让你从一些 PropertySource 的实现解析属性。
Spring Framework 提供了一些常用的 PropertySource, 如系统属性,命令行属性,属性文件等。Spring Boot 自动配置这些实现(比如加载 application.properties)。
Configuration Property Sources
比起直接使用已存在的 PropertySource 实现类,Spring Boot2.0 引入了新的 ConfigurationPropertySource 接口。我们引入这个新的接口来定义“relaxed binding”规则。
该接口的主要 API 显得非常简单
ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);
另外有个 IterableConfigurationPropertySource 变量实现了 Iterable<ConfigurationPropertyNaame>, 让你可以发现 source 包含的所有属性名称。
你可以向下面这样将 Environment 传给 ConfigurationPropertySources:
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
我们同时提供了 MapConfigurationPropertySource 来帮你应付上面的场景。
Configuration Property Names
如果规则明确,实现 ”relaxed binding” 会简单很多。一直使用一致的格式,而不需要去关系在 source 中的各种无规则的格式。
ConfigurationPropertyNames 类来强制进行这些属性命名规则,例如“use lowercase kebab-case names”,在代码中使用 person.first-name,在 source 中使用 person.firstName 或者 PERSON_FIRSTNAME.
Origin Support
如期望的那样,ConfigurationPropertySource 返回 ConfigurationProperty 对象,里面包含了属性的取值,另外有个可选的 Origin 对象。
spring boot 2.0 引入了新的接口 Origin,能够指出属性取值的准确位置。其中 TextResourceOrigin 是较为常用的实现,会提供所加载的 Resource,以及对应的行。
对于.properties 和.yml 文件,我们写了定制的 souce 加载器,使得追踪成为可能。一些 spring boot 的功能进行了重写来追踪信息。比如,属性绑定的验证异常现在会显示:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under ‘person’ to scratch.PersonProperties failed:
Property: person.name
Value: Joe
Origin: class path resource [application.properties]:1:13
Reason: length must be between 4 and 2147483647
Action:
Update your application’s configuration
Binder API
org.springframework.boot.context.properties.bind.Binder 类允许你使用多个 ConfigurationPropertySource。准确的说,Binder 使用 Bindable 并返回一个 BindResult.
Bindable
一个 Bindable 可以是 Java bean, 或是一个复杂对象 (如 List<Person>). 这里是一些例子
Bindable.ofInstance(existingBean);
Bindable.of(Integer.class);
Bindable.listOf(Person.class);
Bindable.of(resovableType);
Binable 用来携带注解的信息,但不需要太过关注这个。
BindResult
binder 会返回 BindResult, 和 java8 stream 操作返回的 Optional 很相似,一个 BinderResult 表示 bind 的结果。
如果你尝试获取一个没有绑定的对象,会抛出异常,另外有其他的方法来设置没有绑定时的缺省对象。
var bound = binder.bind(“person.date-of-birth”,Bindable.of(LocalDate.class));
// 返回 LocalDate,如果没有则抛出异常
bound.get()
// 返回一个格式化时间,或是“No DOB”
bound.map(dateFormatter::format).orElse(“NO DOB”);
// 返回 LocalDate 或者抛出自定义异常
bound.orElseThrow(NoDateOfBirthException::new);
Formatting and Conversion
大部分 ConfigurationPropertySource 实现将值当字符串处理。当 Binder 需要将值转化为其他类型时,会使用到 Spring 的 ConversionService API。比较常用的是 @NumberFormat 和 @DateFormat 这两个注解。
spring boot 2.0 也引入了一些新的注解和转换器。例如,你现在可以将 4s 转换成 Duration。具体请参考 org.springframework.boot.conver 包。
BindHandler
在 binding 的过程中,你可能会需要实现一些额外的逻辑。BindHandler 接口提供了这样的机会。每一个 BindHandler 有 onStart, onSuccess, onFailure 和 onFinish 方法供重载。
spring boot 提供了一些 handlers, 用来支持已存在的 @ConfigurationProperties binding。比如 ValidationBindHandler 可以用于绑定对象的合法性校验上。
@ConfigurationProperties
如文章开头所提到的,@ConfigurationProperties 是在 spring boot 最初就加入的功能。所以大部分人会继续使用该注解是不可避免的。
Future Work
我们计划在 spring boot2.1 中继续加强 Binder 的功能,而第一个想要支持的功能是不可变属性绑定。另外相较 getters 和 setters 的绑定,使用基于构造器的绑定来代替:
public class Person{
private final String firstName;
private final String lastName;
private final LocalDateTime dateOfBirth;
public Person(String firstName, String lastName, LocalDateTime dateOfBirth){
this.firstName = firstName;
this.lastName = lastName;
this.dateOfBirth = dateOfBirth;
}
//getters
}
Summary
我们希望在 spring boot2.0 中你可以找到更好用的属性绑定功能,并考虑升级你目前的方式。