详解@ConfigurationProperties实现原理与实战

在SpringBoot中,当需要获取到配置文件数据时,除了可以用Spring自带的@Value注解外,SpringBoot提供了一种更加方便的方式:@ConfigurationProperties。只要在bean上添加上这个注解,指定好配置文件的前缀,那么对应的配置文件数据就会自动填充到bean中。举个栗子,现在有如下配置:

myconfig.name=test
myconfig.age=22
myconfig.desc=这是我的测试描述

添加对应的配置类,并添加上注解@ConfigurationProperties,指定前缀为myconfig

@Component
@ConfigurationProperties(prefix = "myconfig")
public class MyConfig {
private String name;
private Integer age;
private String desc;
  //get/set 略
  @Override
public String toString() {
	return "MyConfig [name=" + name + ", age=" + age + ", desc=" + desc + "]";
}
}

添加使用:

public static void main(String[] args) throws Exception {
	SpringApplication springApplication = new SpringApplication(Application.class);
	// 非web环境
	springApplication.setWebEnvironment(false);
	ConfigurableApplicationContext application = springApplication.run(args);

	MyConfig config = application.getBean(MyConfig.class);
	log.info(config.toString());
	application.close();
}

可以看到输出log

com.cml.chat.lesson.lesson3.Application - MyConfig [name=test, age=22, desc=这是我的测试描述]

对应的属性都注入了配置中的值,而且不需要其他操作。是不是非常神奇?那么下面来剖析下@ConfigurationProperties到底做了啥?

首先进入@ConfigurationProperties源码中,可以看到如下注释提示:

enter image description here

See Also 中给我们推荐了ConfigurationPropertiesBindingPostProcessor,EnableConfigurationProperties两个类,EnableConfigurationProperties先放到一边,因为后面的文章中会详解EnableXX框架的实现原理,这里就先略过。那么重点来看看ConfigurationPropertiesBindingPostProcessor,光看类名是不是很亲切?不知上篇文章中讲的BeanPostProcessor还有印象没,没有的话赶紧回头看看哦。

ConfigurationPropertiesBindingPostProcessor
一看就知道和BeanPostProcessor有扯不开的关系,进入源码可以看到,该类实现的BeanPostProcessor和其他多个接口:

public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
	BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean,
	DisposableBean, ApplicationListener<ContextRefreshedEvent>, PriorityOrdered 

这里是不是非常直观,光看类的继承关系就可以猜出大概这个类做了什么。
BeanFactoryAware,EnvironmentAware,ApplicationContextAware是Spring提供的获取Spring上下文中指定对象的方法而且优先于BeanPostProcessor调用,至于如何工作的后面的文章会进行详解,这里只要先知道下作用就可以了。
此类同样实现了InitializingBean接口,从上篇文章中已经知道了InitializingBean是在BeanPostProcessor.postProcessBeforeInitialization之后调用,那么postProcessBeforeInitialization目前就是我们需要关注的重要入口方法。

先上源码看看:

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
		throws BeansException {
	//直接通过查找添加了ConfigurationProperties注解的的类
	ConfigurationProperties annotation = AnnotationUtils
			.findAnnotation(bean.getClass(), ConfigurationProperties.class);
	if (annotation != null) {
		postProcessBeforeInitialization(bean, beanName, annotation);
	}
	//查找使用工厂bean中是否有ConfigurationProperties注解
	annotation = this.beans.findFactoryAnnotation(beanName,
			ConfigurationProperties.class);
	if (annotation != null) {
		postProcessBeforeInitialization(bean, beanName, annotation);
	}
	return bean;
}

private void postProcessBeforeInitialization(Object bean, String beanName,
		ConfigurationProperties annotation) {
	Object target = bean;
	PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
			target);
	factory.setPropertySources(this.propertySources);
	factory.setValidator(determineValidator(bean));
	// If no explicit conversion service is provided we add>
public class ConfigurationBeanFactoryMetaData implements BeanFactoryPostProcessor {

private ConfigurableListableBeanFactory beanFactory;

private Map<String, MetaData> beans = new HashMap<String, MetaData>();

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
		throws BeansException {
	this.beanFactory = beanFactory;
 //迭代所有的bean定义,找出那些是工厂bean的对象添加到beans中
	for (String name : beanFactory.getBeanDefinitionNames()) {
		BeanDefinition definition = beanFactory.getBeanDefinition(name);
		String method = definition.getFactoryMethodName();
		String bean = definition.getFactoryBeanName();
		if (method != null && bean != null) {
			this.beans.put(name, new MetaData(bean, method));
		}
	}
}

public <A extends Annotation> Map<String, Object> getBeansWithFactoryAnnotation(
		Class<A> type) {
	Map<String, Object> result = new HashMap<String, Object>();
	for (String name : this.beans.keySet()) {
		if (findFactoryAnnotation(name, type) != null) {
			result.put(name, this.beanFactory.getBean(name));
		}
	}
	return result;
}

public <A extends Annotation> A findFactoryAnnotation(String beanName,
		Class<A> type) {
	Method method = findFactoryMethod(beanName);
	return (method == null ? null : AnnotationUtils.findAnnotation(method, type));
}

//略...
	
private static class MetaData {
	private String bean;
	private String method;
  //构造方法和其他方法略...
}

}

详解@ConfigurationProperties实现原理与实战

扫一扫手机访问