@ConfigurationProperties源码分析 一
@ConfigurationProperties源码分析 二 什么时候注册的ConfigurationPropertiesBindingPostProcessor?
@ConfigurationProperties源码分析 三 什么时候注册的ConfigurationClassPostProcessor?
@ConfigurationProperties源码分析 四 什么时候注册的ConfigurationPropertiesBindingPostProcessorRegistrar?
@ConfigurationProperties源码分析 五 如何实现递归赋值配置类成员变量?
背景
之前我们项目当中,配置文件是通过 @Value("${zk.register.merinfo}") 方式注入的。
造成问题如下:
- 配置属性不统一,没有结构。
- 注入麻烦每个属性都要写配置名,和属性名。(只要有重复的工作,就应该重构)
- 配置零散在项目中各处
@Component
public class ApiBaseInfoService {
@Value("${zk.register.merinfo}")
public String merInfoRpc;
@Value("${pay.qpass.trade}")
public String payQpassTrade;
@Value("${pay.qpass.refund}")
public String payQpassRefund;
@Value("${pay.qpass.reverse}")
public String payQpassReverse;
}
修改后我们的代码如下.
@Configuration
@ConfigurationProperties(prefix = "jlpay")
@Data
public class TestConf {
private Notify notify = new Notify();
private String node;
public static TestConf TestConfStatic ;
{
TestConfStatic =this;
}
@Data
public class Notify {
String risk;
String trans;
}
}
jlpay:
notify:
risk: risk-url
trans: trans-url
node: node-url
问题
那么就是是如何通过@ConfigurationProperties 将配置文件中的值赋值到配置类里面的呢?
猜想如下:
既然有注解,那肯定有注解处理器。
在配置类初始化的时候调用注解处理器处理
注解处理器会读取@ConfigurationProperties 注解的对象
获取配置文件中的prefix,和注解对象的类成员变量
递归将配置属性赋值给类成员变量
源码分析
ConfigurationPropertiesBindingPostProcessor
Spring 初始化Bean 的时候会调用 bean后置处理 ConfigurationPropertiesBindingPostProcessor 的postProcessBeforeInitialization方法
这里其实就是我们上面猜想的注解处理器,它会处理ConfigurationProperties注解
获取配置文件中的prefix,和注解对象的类成员变量
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//获取@ConfigurationProperties注解信息
ConfigurationProperties annotation = getAnnotation(bean, beanName,
ConfigurationProperties.class);
//有注解的才绑定配置属性 没有的直接返回
if (annotation != null) {
bind(bean, beanName, annotation);
}
return bean;
}
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
ResolvableType type = getBeanType(bean, beanName);
Validated validated = getAnnotation(bean, beanName, Validated.class);
Annotation[] annotations = (validated != null)
? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
//将bean 类型 注解等信息包装传递给后面处理
Bindable<?> target = Bindable.of(type).withExistingValue(bean)
.withAnnotations(annotations);
try {
//委托配置绑定器来处理
this.configurationPropertiesBinder.bind(target);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, bean, annotation,
ex);
}
}
ConfigurationPropertiesBinder
上面的注解处理器委托配置绑定器ConfigurationPropertiesBinder来处理 ,主要考虑职责分离。
public void bind(Bindable<?> target) {
ConfigurationProperties annotation = target
.getAnnotation(ConfigurationProperties.class);
Assert.state(annotation != null,
() -> "Missing @ConfigurationProperties on " + target);
List<Validator> validators = getValidators(target);
BindHandler bindHandler = getBindHandler(annotation, validators);
//委托Binder处理
getBinder().bind(annotation.prefix(), target, bindHandler);
}
Binder
委托Binder处理
private Binder getBinder() {
if (this.binder == null) {
this.binder = new Binder(getConfigurationPropertySources(),
getPropertySourcesPlaceholdersResolver(), getConversionService(),
getPropertyEditorInitializer());
}
return this.binder;
}
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
BindHandler handler, Context context, boolean allowRecursiveBinding) {
if (containsNoDescendantOf(context.streamSources(), name)
|| isUnbindableBean(name, target, context)) {
return null;
}
//用于递归绑定bean属性的绑定器。 理解成回调函数,后面会递归调用给bean属性赋值
BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
name.append(propertyName), propertyTarget, handler, context, false);
Class<?> type = target.getType().resolve(Object.class);
if (!allowRecursiveBinding && context.hasBoundBean(type)) {
return null;
}
//用了java8 lambda 表达式 和流操作
return context.withBean(type, () -> {
Stream<?> boundBeans = BEAN_BINDERS.stream() // BEAN_BINDERS对象 实际是 JavaBeanBinder List
.map((b) -> b.bind(name, target, context, propertyBinder));
return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
});
}
JavaBeanBinder
上面的BEAN_BINDERS对象 实际是 JavaBeanBinder List
private static final List<BeanBinder> BEAN_BINDERS;
static {
List<BeanBinder> binders = new ArrayList<>();
binders.add(new JavaBeanBinder());
BEAN_BINDERS = Collections.unmodifiableList(binders);
}
遍历属性赋值
private <T> boolean bind(BeanPropertyBinder propertyBinder, Bean<T> bean,
BeanSupplier<T> beanSupplier) {
boolean bound = false;
for (Map.Entry<String, BeanProperty> entry : bean.getProperties().entrySet()) {
bound |= bind(beanSupplier, propertyBinder, entry.getValue());
}
return bound;
}
递归调用
private <T> boolean bind(BeanSupplier<T> beanSupplier,
BeanPropertyBinder propertyBinder, BeanProperty property) {
String propertyName = property.getName();
ResolvableType type = property.getType();
Supplier<Object> value = property.getValue(beanSupplier);
Annotation[] annotations = property.getAnnotations();
//这里就是上面提到的回调函数,会递归对象属性赋值
Object bound = propertyBinder.bindProperty(propertyName,
Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
if (bound == null) {
return false;
}
//判断有setter方法,调用赋值,用的是java 反射技术
if (property.isSettable()) {
property.setValue(beanSupplier, bound);
}
else if (value == null || !bound.equals(value.get())) {
throw new IllegalStateException(
"No setter found for property: " + property.getName());
}
return true;
}
主要就是这里实现赋值的 property.setValue(beanSupplier, bound);用的是java 反射技术
public void setValue(Supplier<?> instance, Object value) {
try {
this.setter.setAccessible(true);
this.setter.invoke(instance.get(), value);
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to set value for property " + this.name, ex);
}
}
总结
究竟是如何通过@ConfigurationProperties 将配置文件中的值赋值到配置类里面的呢?
注册了ConfigurationProperties 注解处理器ConfigurationPropertiesBindingPostProcessor,它实现了Spring容器的postProcessBeforeInitialization方法,会在bean初始化之前被调用
注解处理器会读取@ConfigurationProperties 注解的对象
获取配置文件中的prefix,和注解对象的类成员变量
然后递归将配置属性赋值给类成员变量
链接
其他问题