概述
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一、@ConfigurationProperties二、何时解析@ConfigurationProperties?三、 ConfigurationPropertiesBindingPostProcessorRegistrar四、ConfigurationPropertiesBindingPostProcessor的全部内容,希望文章能够帮你解决ConfigurationProperties和EnableConfigurationProperties一、@ConfigurationProperties二、何时解析@ConfigurationProperties?三、 ConfigurationPropertiesBindingPostProcessorRegistrar四、ConfigurationPropertiesBindingPostProcessor所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复