开发SpringBoot应用时经常涉及到配置文件,平时只是知道使用@ConfigurationProperties来注解类,或者使用@Value来获取配置值,通过@EnableConfigurationProperties来将配置类作为bean引入容器中,等等这些操作只是局限于使用上,现在我决定去研究下源码,帮助自己能够更充分地了解其中的机制。

一、首先从@ConfigurationProperties注解入手: 

/**
 * Annotation for externalized configuration. Add this to a class definition or a
 * {@code @Bean} method in a {@code @Configuration} class if you want to bind and validate
 * some external Properties (e.g. from a .properties file).
 * <p>
 * Note that contrary to {@code @Value}, SpEL expressions are not evaluated since property
 * values are externalized.
 *
 * @author Dave Syer
 * @see ConfigurationPropertiesBindingPostProcessor
 * @see EnableConfigurationProperties
 */
@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
}

分析:

1.从类注释可以了解到:该注解可以应用于普通类或者 被@Configuration注解的类中又被@Bean注解的方法上,主要用于给对象(或叫bean)绑定一个配置文件并支持校验配置的内容,例如绑定自定义的properties文件;至于后面说的"请注意,与{@code @Value}相反,SpEL表达式不会因为属性而被评估"这句有点不是太懂,网友可以给提示一下?

2.从属性注释了解到:该注解一共有六个属性,其中value和prefix作用相当 ,都是彼此的别名,ignoreInvalidFields做用是忽略哪些解析失败的字段内容,ignoreNestedProperties作用是忽略内容中包含"."这种特殊字符将被忽略,ignoreUnknownFields作用是忽略未知字段,exceptionIfInvalid作用是如果使用Validator校验器进行校验出错时是否引发异常。

3.综上:该注解最重要作用应该就是可以读取自定义配置文件并校验!

使用场景(个人感觉):

            1.默认读取application配置文件

            2.与@PropertySource结合使用,读取自定义配置文件

4.从类注释中我们看到关联的类及注解为:ConfigurationPropertiesBindingPostProcessor、EnableConfigurationProperties

二、我们看下关联注解EnableConfigurationProperties:

/**
 * Enable support for {@link ConfigurationProperties} annotated beans.
 * {@link ConfigurationProperties} beans can be registered in the standard way (for
 * example using {@link Bean @Bean} methods) or, for convenience, can be specified
 * directly on this annotation.
 *
 * @author Dave Syer
 */
@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 {};

}

分析:

1.从类注释可以了解到:该注解目的就是配合@ConfigurationProperties 使用(两者一定要关联使用),注册@ConfigurationProperties 注解的类到容器的方式有两种,一种是使用例如@Bean这种标准方式来注册,另一种就是使用该注解来协助注册到容器中

2.从属性注释了解到:value属性就是指定要注册到容器的哪些类,注意这些类必须是被@ConfigurationProperties注解的类,否则不能注册到容器中

3.这里我们看到了@Import注解,其属性值EnableConfigurationPropertiesImportSelector类至关重要,我们可以直接看下其源码:

/**
 * Import selector that sets up binding of external properties to configuration classes
 * (see {@link ConfigurationProperties}). It either registers a
 * {@link ConfigurationProperties} bean or not, depending on whether the enclosing
 * {@link EnableConfigurationProperties} explicitly declares one. If none is declared then
 * a bean post processor will still kick in for any beans annotated as external
 * configuration. If one is declared then it a bean definition is registered with id equal
 * to the class name (thus an application context usually only contains one
 * {@link ConfigurationProperties} bean of each unique type).
 *
 * @author Dave Syer
 * @author Christian Dupuis
 * @author Stephane Nicoll
 */
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() + "'.");
		}

	}

}

分析:

1.EnableConfigurationPropertiesImportSelector 实现了ImportSelector接口,这里简单说下ImportSelector接口的作用:主要是通过其selectImports方法返回一些要注入到容器中的类

2.内部类ConfigurationPropertiesBeanRegistrar实现了ImportBeanDefinitionRegistrar 接口,同样ImportBeanDefinitionRegistrar 类似于ImportSelector接口,也是为了注册实例到容器中,只不过注册逻辑更为显式

2.看EnableConfigurationPropertiesImportSelector 实现的selectImports方法,其中有根据EnableConfigurationProperties中配置属性进行判断的逻辑,如果有属性,则通过ConfigurationPropertiesBeanRegistrar内部类来实现这些属性的注册逻辑,否则则直接返回ConfigurationPropertiesBindingPostProcessorRegistrar即可。

三、我们看下关联类ConfigurationPropertiesBindingPostProcessorRegistrar:

/**
 * {@link ImportBeanDefinitionRegistrar} for binding externalized application properties
 * to {@link ConfigurationProperties} beans.
 *
 * @author Dave Syer
 * @author Phillip Webb
 */
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());
		}
	}

}

分析:

1.ConfigurationPropertiesBindingPostProcessorRegistrar实现了ImportBeanDefinitionRegistrar 接口,不得而知,又是一个用于注册bean到容器的类

2.我们看下registerBeanDefinitions方法,其中注册了两个bean,一个是ConfigurationPropertiesBindingPostProcessor,另一个是ConfigurationBeanFactoryMetaData,这里对这个两个类进行简要分析一下,就不再写出其源码了,ConfigurationPropertiesBindingPostProcessor其实就是实现了BeanPostProcessor、InitializingBean、DisposableBean等等一系列接口,目的就是给配置类Bean绑定一些Bean的初始化前置、后置操作,实现各种校验逻辑等等..

ConfigurationBeanFactoryMetaData实现了BeanFactoryPostProcessor接口,目的是获取bean的一些元数据。

 

上面是对配置文件注解的一些分析,由上可知,配置注解的类要加入容器有两种方式,其中通过配置@Import导入配置方式具体是如何实现的呢,可以参考ConfigurationClassPostProcessor该类postProcessBeanDefinitionRegistry方法的执行流程,其内部是通过解析@Import注解来实现的。