ConfigurationProperties和EnableConfigurationProperties

Posted 恒奇恒毅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ConfigurationProperties和EnableConfigurationProperties相关的知识,希望对你有一定的参考价值。

SpringBoot中提供了属性值和JavaBean属性的绑定功能。
当我们在application.properties中声明了以下数据,那么我们可以用以下声明方式完成属性绑定:

server.context-path=/test
server.port=8080
  • @Configuration和@ConfigurationProperties
@Component/Configuration
@ConfigurationProperties(prefix = "server")
public class XXProperties 
    private String contextPath;
    private String port;
    。。。getter/setter

这种是最常见的用于程序猿自己绑定属性,可以完全控制注解的标注

  • @EnableConfigurationProperties(XXProperties.class)
    此时XXProperties就没必要标注@Component/Configuration
    这种方式最常见于封装自动配置类的时候主动导入属性配置类,例如
    @EnableConfigurationProperties(WebMvcProperties.class)
	protected static class DispatcherServletConfiguration 
  • @Bean方法上声明
    这种最常见于自动配置
	@Bean
    @ConfigurationProperties(prefix = "server")
    public XXProperties xxProperties()
        return new XXProperties();
    

此时XXProperties就没必要标注@Component/Configuration@ConfigurationProperties
这种方式最长见于导入别人写的类,无法标注注解,但是想绑定属性。后面的分析我们可以看到这种方式其实跟第一种方式是完全一样的,只是SpringBoot在处理的时候处理为factory-method注解。

一、@ConfigurationProperties

@Target( ElementType.TYPE, ElementType.METHOD )
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties 

	/**
	 * The name prefix of the properties that are valid to bind to this object. Synonym
	 * for @link #prefix().
	 * @return the name prefix of the properties to bind
	 */
	@AliasFor("prefix")
	String value() default "";

	/**
	 * The name prefix of the properties that are valid to bind to this object. Synonym
	 * for @link #value().
	 * @return the name prefix of the properties to bind
	 */
	@AliasFor("value")
	String prefix() default "";

	/**
	 * Flag to indicate that when binding to this object invalid fields should be ignored.
	 * Invalid means invalid according to the binder that is used, and usually this means
	 * fields of the wrong type (or that cannot be coerced into the correct type).
	 * @return the flag value (default false)
	 */
	boolean ignoreInvalidFields() default false;

	/**
	 * Flag to indicate that when binding to this object fields with periods in their
	 * names should be ignored.
	 * @return the flag value (default false)
	 */
	boolean ignoreNestedProperties() default false;

	/**
	 * Flag to indicate that when binding to this object unknown fields should be ignored.
	 * An unknown field could be a sign of a mistake in the Properties.
	 * @return the flag value (default true)
	 */
	boolean ignoreUnknownFields() default true;

	/**
	 * Flag to indicate that an exception should be raised if a Validator is available,
	 * the class is annotated with @link Validated @Validated and validation fails. If
	 * it is set to false, validation errors will be swallowed. They will be logged, but
	 * not propagated to the caller.
	 * @return the flag value (default true)
	 * @deprecated as of 1.5 since validation only kicks in when @code @Validated is
	 * present
	 */
	@Deprecated
	boolean exceptionIfInvalid() default true;



该注解提供了几个属性,
value和prefix互为别名,表示绑定的前缀
ignoreInvalidFields表示在绑定的时候无效的字段是否忽略,默认不忽略,发生即报错
ignoreNestedProperties 表示是否忽略嵌套属性,默认false
ignoreUnknownFields表示是否忽略位置属性,默认true
exceptionIfInvalid已经废弃不用了,而是用@Validated注解取代

二、何时解析@ConfigurationProperties

SpringBoot的自动配置的spring.factories中有一项导入了一个自动配置类ConfigurationPropertiesAutoConfiguration

@Configuration
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration 


该类标注了@EnableConfigurationProperties,所以关键在于@EnableConfigurationProperties的定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties 

	/**
	 * Convenient way to quickly register @link ConfigurationProperties annotated beans
	 * with Spring. Standard Spring Beans will also be scanned regardless of this value.
	 * @return @link ConfigurationProperties annotated beans to register
	 */
	Class<?>[] value() default ;


@EnableConfigurationProperties类定义中标注了@Import(EnableConfigurationPropertiesImportSelector.class),该类实现了@ImportSelector接口,自动往容器导入了一些类,定义如下

class EnableConfigurationPropertiesImportSelector implements ImportSelector 

	@Override
	public String[] selectImports(AnnotationMetadata metadata) 
		MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
				EnableConfigurationProperties.class.getName(), false);
		Object[] type = attributes == null ? null
				: (Object[]) attributes.getFirst("value");
		if (type == null || type.length == 0) 
			return new String[] 
					ConfigurationPropertiesBindingPostProcessorRegistrar.class
							.getName() ;
		
		return new String[]  ConfigurationPropertiesBeanRegistrar.class.getName(),
				ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() ;
	

	/**
	 * @link ImportBeanDefinitionRegistrar for configuration properties support.
	 */
	public static class ConfigurationPropertiesBeanRegistrar
			implements ImportBeanDefinitionRegistrar 

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata,
				BeanDefinitionRegistry registry) 
			MultiValueMap<String, Object> attributes = metadata
					.getAllAnnotationAttributes(
							EnableConfigurationProperties.class.getName(), false);
			List<Class<?>> types = collectClasses(attributes.get("value"));
			for (Class<?> type : types) 
				String prefix = extractPrefix(type);
				String name = (StringUtils.hasText(prefix) ? prefix + "-" + type.getName()
						: type.getName());
				if (!registry.containsBeanDefinition(name)) 
					registerBeanDefinition(registry, type, name);
				
			
		

		private String extractPrefix(Class<?> type) 
			ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type,
					ConfigurationProperties.class);
			if (annotation != null) 
				return annotation.prefix();
			
			return "";
		

		private List<Class<?>> collectClasses(List<Object> list) 
			ArrayList<Class<?>> result = new ArrayList<Class<?>>();
			for (Object object : list) 
				for (Object value : (Object[]) object) 
					if (value instanceof Class && value != void.class) 
						result.add((Class<?>) value);
					
				
			
			return result;
		

		private void registerBeanDefinition(BeanDefinitionRegistry registry,
				Class<?> type, String name) 
			BeanDefinitionBuilder builder = BeanDefinitionBuilder
					.genericBeanDefinition(type);
			AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
			registry.registerBeanDefinition(name, beanDefinition);

			ConfigurationProperties properties = AnnotationUtils.findAnnotation(type,
					ConfigurationProperties.class);
			Assert.notNull(properties,
					"No " + ConfigurationProperties.class.getSimpleName()
							+ " annotation found on  '" + type.getName() + "'.");
		

	


主要逻辑如下:
获取标注@EnableConfigurationProperties的类上@EnableConfigurationProperties的value属性,如果为空,导入ConfigurationPropertiesBindingPostProcessorRegistrar,否则导入ConfigurationPropertiesBindingPostProcessorRegistrarConfigurationPropertiesBeanRegistrar
这个其实就是对应的两种用法:第一种针对全局的所有标注ConfigurationProperties的属性绑定和@EnableConfigurationProperties(XXProperties.class)的方式。后者实现了快速立即导入某个属性类的功能。
下面分析完成全局所有属性绑定的类ConfigurationPropertiesBindingPostProcessorRegistrar的功能

三、 ConfigurationPropertiesBindingPostProcessorRegistrar

ConfigurationPropertiesBindingPostProcessorRegistrar实现了ImportBeanDefinitionRegistrar接口,用于主动往容器注册BeanDefinition,其往容器中注册了 ConfigurationPropertiesBindingPostProcessorConfigurationBeanFactoryMetaDataConfigurationPropertiesBindingPostProcessor正是完成属性绑定的关键类。

public class ConfigurationPropertiesBindingPostProcessorRegistrar
		implements ImportBeanDefinitionRegistrar 

	/**
	 * The bean name of the @link ConfigurationPropertiesBindingPostProcessor.
	 */
	public static final String BINDER_BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class
			.getName();

	private static final String METADATA_BEAN_NAME = BINDER_BEAN_NAME + ".store";

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
			BeanDefinitionRegistry registry) 
		if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) 
			BeanDefinitionBuilder meta = BeanDefinitionBuilder
					.genericBeanDefinition(ConfigurationBeanFactoryMetaData.class);
			BeanDefinitionBuilder bean = BeanDefinitionBuilder.genericBeanDefinition(
					ConfigurationPropertiesBindingPostProcessor.class);
			bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME);
			registry.registerBeanDefinition(BINDER_BEAN_NAME, bean.getBeanDefinition());
			registry.registerBeanDefinition(METADATA_BEAN_NAME, meta.getBeanDefinition());
		
	


ConfigurationBeanFactoryMetaData实现了BeanFactoryPostProcessor,在方法postProcessBeanFactory中收集了那些用工厂方法注入的Bean的信息(通过@Bean注解),然后提供了getBeansWithFactoryAnnotationfindFactoryAnnotationfindFactoryMethod等工厂方法相关的方法。在定义BeanDefinition的时候通过bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME);增加二者之间的引用关系。而在ConfigurationPropertiesBindingPostProcessor类中我们能看到的确定义了一个方法:
public void setBeanMetaDataStore(ConfigurationBeanFactoryMetaData beans)

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;
		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 Method findFactoryMethod(String beanName) 
		if (!this.beans.containsKey(beanName)) 
			return null;
		
		final AtomicReference<Method> found = new AtomicReference<Method>(null);
		MetaData meta = this.beans.get(beanName);
		final String factory = meta.getMethod();
		Class<?> type = this.beanFactory.getType(meta.getBean());
		ReflectionUtils.doWithMethods(type, new MethodCallback() 
			@Override
			public void doWith(Method method)
					throws IllegalArgumentException, IllegalAccessException 
				if (method.getName().equals(factory)) 
					found.compareAndSet(null, method);
				
			
		);
		return found.get();
	

	private static class MetaData 

		private String bean;

		private String method;

		MetaData(String bean, String method) 
			this.bean = bean;
			this.method = method;
		

		public String getBean() 
			return this.bean;
		

		public String getMethod() 
			return this.method;
		

	



四、ConfigurationPropertiesBindingPostProcessor

该类实现了BeanPostProcessor,在方法postProcessBeforeInitialization中完成了对所有的Bean的属性绑定,如果在类上或者@Bean的工厂方法上标注了ConfigurationProperties

public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
		BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean,
		DisposableBean, ApplicationListener<ContextRefreshedEvent>, PriorityOrdered 
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException 
		ConfigurationProperties annotation = AnnotationUtils
				.findAnnotation(bean.getClass(), ConfigurationProperties.class);
		if (annotation != null) 
			postProcessBeforeInitialization(bean, beanName, annotation);
		
		annotation = this.beans.findFactoryAnnotation(beanName,
				ConfigurationProperties.class);
		if (annotation != null) 
			postProcessBeforeInitialization(bean, beanName, annotation);
		
		return bean;
	

以上是关于ConfigurationProperties和EnableConfigurationProperties的主要内容,如果未能解决你的问题,请参考以下文章

spring boot配置文件@ConfigurationProperties@Value@ConfigurationProperties@Configuration和 Profile

@ConfigurationProperties注解和@Value注解的区别

@ConfigurationProperties和@Value的区别

ConfigurationProperties和EnableConfigurationProperties

ConfigurationProperties和EnableConfigurationProperties

@ConfigurationProperties