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
,否则导入ConfigurationPropertiesBindingPostProcessorRegistrar
和ConfigurationPropertiesBeanRegistrar
。
这个其实就是对应的两种用法:第一种针对全局的所有标注ConfigurationProperties
的属性绑定和@EnableConfigurationProperties(XXProperties.class)
的方式。后者实现了快速立即导入某个属性类的功能。
下面分析完成全局所有属性绑定的类ConfigurationPropertiesBindingPostProcessorRegistrar
的功能
三、 ConfigurationPropertiesBindingPostProcessorRegistrar
ConfigurationPropertiesBindingPostProcessorRegistrar
实现了ImportBeanDefinitionRegistrar
接口,用于主动往容器注册BeanDefinition
,其往容器中注册了 ConfigurationPropertiesBindingPostProcessor
和ConfigurationBeanFactoryMetaData
。ConfigurationPropertiesBindingPostProcessor
正是完成属性绑定的关键类。
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注解
),然后提供了getBeansWithFactoryAnnotation
、findFactoryAnnotation
、findFactoryMethod
等工厂方法相关的方法。在定义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