概述
@ConfigurationProperties
注解大家应该在项目中都使用过,主要用来绑定属性中的值到我们的对象中。那大家对于怎么进行属性绑定的原理知道吗?
ConfigurationProperties使用
注解介绍
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed public @interface ConfigurationProperties { // 配置的前缀, 和prefix一样 @AliasFor("prefix") String value() default ""; // 配置的前缀,和value一样 @AliasFor("value") String prefix() default ""; // 绑定时是否忽视无效字段,比如字段的类型错误等,默认false boolean ignoreInvalidFields() default false; // 绑定时是否忽视没有的字段,默认true boolean ignoreUnknownFields() default true; }
例子
- 定义Bean, 使用
@ConfigurationProperties
@ConfigurationProperties(prefix = "bsfit.user") @Component public class User { private String userName; private Integer age; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
- 配置文件添加如下配置
- 执行查看Bean的结果
看到User这个bean被赋值了。
注意点
- 被注释对象必须被注册为一个Bean, 可以通过以下几种方式
- 通过
@Component
、@Service
注解,参考上面的例子 - 通过
@Bean
的方式,在方法上添加
@Bean @ConfigurationProperties(prefix = "bsfit.user") public User newUser() { return new User(); }
- 通过
@EnableConfigurationProperties(value = User.class)
方式 - 通过
@ConfigurationPropertiesScan()
的方式
- 属性的用中横线分隔和驼峰的方式都可以和Bean的字段映射
bsfit.user: ## user-name等价于userName使用 user-name: ${person.last-name} age: 55
源码解析
@ConfigurationProperties
读取配置主要是通过ConfigurationPropertiesBindingPostProcessor
配置绑定bean初始化后置处理器来实现的,它是在Bean初始化前后执行的。TODO
执行主流程
- 首先我们看下
ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization
方法,它是在Bean初始化前调用。
// Bean初始化前调用postProcessBeforeInitialization方法 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //1. ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName)方法根据 Bean 解析出一个 ConfigurationPropertiesBean 对象,包含Bean和相关的注解信息 //2. 调用bind方法为对象添加属性 bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName)); //3.返回填充了配置属性的对象 return bean; }
ConfigurationPropertiesBean#get(this.applicationContext, bean, beanName)
方法如下:
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) { // 1.找到这个beanName对应的工厂方法,例如@Bean标注的方法就是一个工厂方法,不是@Bean的话这里为空 Method factoryMethod = findFactoryMethod(applicationContext, beanName); // 2.创建一个 ConfigurationPropertiesBean 对象,包含了这个 Bean 的@ConfigurationProperties注解信息 return create(beanName, bean, bean.getClass(), factoryMethod); }
我们看下创建的ConfigurationPropertiesBean
对象都有哪些属性。
public final class ConfigurationPropertiesBean { /** * Bean 的名称 */ private final String name; /** * Bean 的实例对象 */ private final Object instance; /** * Bean 的 `@ConfigurationProperties` 注解 */ private final ConfigurationProperties annotation; /** * `@Bean` 对应的方法资源对象,包括实例对象和注解信息 */ private final Bindable<?> bindTarget; /** * `@Bean` 对应的方法 */ private final BindMethod bindMethod; }
- 调用关键方法
bind
对属性进行绑定
private void bind(ConfigurationPropertiesBean bean) { //1. 如果bean为空或者bean采用构造器绑定,直接返回,构造器方式绑定注解暂时不讨论,用的相对较小 if (bean == null || hasBoundValueObject(bean.getName())) { return; } Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '" + bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean"); try { // 通过属性绑定器ConfigurationPropertiesBinder进行属性的绑定 this.binder.bind(bean); } catch (Exception ex) { throw new ConfigurationPropertiesBindException(bean, ex); } }
- 通过属性绑定器
ConfigurationPropertiesBinder#bind
方法对属性进行绑定
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) { //1. 获取这个 Bean 的 Bindable 对象(包含了 @ConfigurationProperties、@Validated 配置信息和这个 Bean) Bindable<?> target = propertiesBean.asBindTarget(); //2.获取这个 Bean 的 @ConfigurationProperties注解信息 ConfigurationProperties annotation = propertiesBean.getAnnotation(); //3. 获取属性绑定处理器,此处用了装饰器模式,提供了一些回调接口处理额外的逻辑,比如执行Validated校验等 BindHandler bindHandler = getBindHandler(target, annotation); //4. 通过getBinder()方法new一个绑定器Binder对象,里面包括了配置属性对象列表、配置属性解析器、类型转换器等 //5. 调用Binder的bind方法进行属性设置 return getBinder().bind(annotation.prefix(), target, bindHandler); }
- 最关键的是
Binder#bind
方法
public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) { //1. ConfigurationPropertyName.of(name)根据传入的name构造出ConfigurationPropertyName对象 //2. 调用重载的方法bind return bind(ConfigurationPropertyName.of(name), target, handler); } public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) { // 调用重载方法bind,返回绑定了属性的bean对象 T bound = bind(name, target, handler, false); return BindResult.of(bound); } private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, boolean create) { Assert.notNull(name, "Name must not be null"); Assert.notNull(target, "Target must not be null"); handler = (handler != null) ? handler : this.defaultBindHandler; // 创建绑定的上下文对象,主要用来处理属性的递归调用,比如我们的bean中可能包含了另外一个bean Context context = new Context(); // 调用重载方法bind return bind(name, target, handler, context, false, create); } private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding, boolean create) { try { // 1. 调用handler的onStart回调方法,在绑定属性前做一些额外的处理 Bindable<T> replacementTarget = handler.onStart(name, target, context); if (replacementTarget == null) { return handleBindResult(name, target, handler, context, null, create); } target = replacementTarget; // 2. 调用bindObject方法机进行绑定 Object bound = bindObject(name, target, handler, context, allowRecursiveBinding); // 3. 处理绑定后的结果 return handleBindResult(name, target, handler, context, bound, create); } catch (Exception ex) { return handleBindError(name, target, handler, context, ex); } }
ConfigurationPropertyName
: 由点分隔的元素组成的配置属性名称。用户创建的名称可以包含“a-z”“0-9”)和“-”,必须为小写,且首字符必须为字母和数字。“-”纯粹用于格式化,即。“foo-bar”和“foobar”被认为是等价的。
- 我们看下核心方法
Binder#bindObject
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context, boolean allowRecursiveBinding) { // 1. 根据属性名name从配置文件等source中遍历查找对应的结果 ConfigurationProperty property = findProperty(name, target, context); // 2. 如果属性为空且不是第一层节点且不包含子节点,直接返回,否则继续遍历 if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) { return null; } //3. 处理聚合的绑定,比如List, Map等 AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context); if (aggregateBinder != null) { return bindAggregate(name, target, handler, context, aggregateBinder); } // 如果属性不为空, 直接绑定数据到属性上 if (property != null) { try { // return bindProperty(target, context, property); } catch (ConverterNotFoundException ex) { // We might still be able to bind it using the recursive binders Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding); if (instance != null) { return instance; } throw ex; } } // 绑定对象数据 return bindDataObject(name, target, handler, context, allowRecursiveBinding); }
- 关注下核心方法
Binder#bindDataObject
private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler, Context context, boolean allowRecursiveBinding) { if (isUnbindableBean(name, target, context)) { return null; } Class<?> type = target.getType().resolve(Object.class); if (!allowRecursiveBinding && context.isBindingDataObject(type)) { return null; } // 1. 构造属性绑定器,比如用于绑定我们user对象的userName // DataObjectPropertyBinder是一个接口,最终执行的是Binder中的bind方法 DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName), propertyTarget, handler, context, false, false); // 2. 相对比较复杂,调用Binder上下文对象Context的withDataObject return context.withDataObject(type, () -> { for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) { Object instance = dataObjectBinder.bind(name, target, context, propertyBinder); if (instance != null) { return instance; } } return null; }); }
- 看下
Context.withDataObject
方法
private <T> T withDataObject(Class<?> type, Supplier<T> supplier) { // 向栈中推入当前正在绑定的对象 this.dataObjectBindings.push(type); try { // 深入属性绑定 return withIncreasedDepth(supplier); } finally { this.dataObjectBindings.pop(); } } private <T> T withIncreasedDepth(Supplier<T> supplier) { // 层级+1 increaseDepth(); try { // 执行supplier,也就是一开始传入的方法 return supplier.get(); } finally { decreaseDepth(); } }
- 我们看下传入的Supperlier方法如下
() -> { // 遍历dataObjectBinders, 有两个,一个是构造函数绑定器,一个是JavaBeanBinder,也就是Set方法绑定,我们重点关注该绑定器 for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) { // 调用数据对象绑定器DataObjectBinder的bind方法 Object instance = dataObjectBinder.bind(name, target, context, propertyBinder); if (instance != null) { return instance; } } return null; }
- 关注在
JavaBeanBinder#bind
方法
private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier, Context context) { boolean bound = false; // 遍历Bean的属性列表 for (BeanProperty beanProperty : bean.getProperties().values()) { // 调用bind方法绑定属性 bound |= bind(beanSupplier, propertyBinder, beanProperty); context.clearConfigurationProperty(); } return bound; } private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder, BeanProperty property) { String propertyName = property.getName(); ResolvableType type = property.getType(); Supplier<Object> value = property.getValue(beanSupplier); Annotation[] annotations = property.getAnnotations(); // 调用属性绑定器获取属性的值,bindProperty最终还是调用Binder对象中的bind方法 Object bound = propertyBinder.bindProperty(propertyName, Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations)); if (bound == null) { return false; } if (property.isSettable()) { // 通过反射的set方法设置值 property.setValue(beanSupplier, bound); } else if (value == null || !bound.equals(value.get())) { throw new IllegalStateException("No setter found for property: " + property.getName()); } return true; }
总结
整个ConfigurationProperties
属性绑定的过程还是很复杂的,代码的很多细节都没有展示,需要大家一步一步debug才能更好的理解和体会。