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;
	}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值