Spring源码:EnableConfigurationProperties源码分析

本文深入探讨了Spring框架中配置属性绑定的实现机制,详细分析了EnableConfigurationProperties、ConfigurationPropertiesBindingPostProcessor等核心组件的工作原理,以及它们如何帮助开发者轻松地将属性文件中的配置映射到Java对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. EnableConfigurationProperties

2. EnableConfigurationPropertiesImportSelector

2.1 ConfigurationPropertiesBeanRegistrar

2.2 ConfigurationPropertiesBindingPostProcessorRegistrar

3. ConfigurationPropertiesBindingPostProcessor

3.1 InitializingBean.afterPropertiesSet实现

3.2 BeanPostProcessor生命周期方法实现


spring项目中,当在属性文件中定义键值对时,我们希望单独定义一个对象,用来映射属性文件中的键值对,这时就需要用到spring提供的EnableConfigurationProperties功能,该注解开启配置属性支持;

1. EnableConfigurationProperties

如下源码注释,我们有两种方式来完成属性到bean的绑定:

  • 同时声明@ConfigurationProperties注解和@Bean创建bean
  • 在EnableConfigurationProperties注解中直接声明标注了@ConfigurationProperties的属性对象
/**
 * 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 {};

}

注意到在该注解中,通过@Import导入了EnableConfigurationPropertiesImportSelector,它是一个ImportSelector,下面分析一下EnableConfigurationPropertiesImportSelector的实现

2. EnableConfigurationPropertiesImportSelector

实现源码如下,可见,selectImports方法根据EnableConfigurationProperties注解value指定的情况,又引入了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar;

/**
 * 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 ? (Object[]) attributes.getFirst("value")
				: null);
		if (type == null || type.length == 0) {
			return new String[] {
					ConfigurationPropertiesBindingPostProcessorRegistrar.class
							.getName() };
		}
		return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),
				ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
	}
}

ConfigurationPropertiesBeanRegistrar用于将value中指定的类注册为bean definition,

ConfigurationPropertiesBindingPostProcessorRegistrar用于标注@ConfigurationProperties注解的类注册为bean definitiion,

下面分别对其进行分析:

2.1 ConfigurationPropertiesBeanRegistrar

ConfigurationPropertiesBeanRegistrar实现ImportBeanDefinitionRegistrar接口,将EnableConfigurationProperties指定的value表示的配置类注册为bean definition,实现如下:

/**
	 * {@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() + "'.");
		}

	}

2.2 ConfigurationPropertiesBindingPostProcessorRegistrar

ConfigurationPropertiesBindingPostProcessorRegistrar同样实现ImportBeanDefinitionRegistrar接口,注册了如下两个bean定义:

  • ConfigurationBeanFactoryMetaData
  • ConfigurationPropertiesBindingPostProcessor

前者实现BeanFactoryPostProcessor工厂后处理器,主要完成bean工厂方法上ConfigurationProperties注解的获取

下面主要分析下ConfigurationPropertiesBindingPostProcessor的实现

3. ConfigurationPropertiesBindingPostProcessor

该类是一个bean后处理器,主要完成属性文件中的配置信息绑定到配置类上,下面主要从如下几点来进行剖析:

  1. InitializingBean接口方法afterPropertiesSet实现
  2. bean后处理器生命周期方法实现

3.1 InitializingBean.afterPropertiesSet实现

从如下源码中可以看出,该初始化方法主要完成PropertySources、验证器Validator以及属性转化器ConversionService的初始化,这里主要分析一下属性源PropertySources的初始化过程;

@Override
	public void afterPropertiesSet() throws Exception {
		if (this.propertySources == null) {
			this.propertySources = deducePropertySources();
		}
		if (this.validator == null) {
			this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class);
		}
		if (this.conversionService == null) {
			this.conversionService = getOptionalBean(
					ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME,
					ConversionService.class);
		}
	}

获取 PropertySources时,首先从beanFactory中获取PropertySourcesPlaceholderConfigurer,当不存在时,直接由environment来构造属性源PropertySources

private PropertySources deducePropertySources() {
		PropertySourcesPlaceholderConfigurer configurer = getSinglePropertySourcesPlaceholderConfigurer();
		if (configurer != null) {
			// Flatten the sources into a single list so they can be iterated
			return new FlatPropertySources(configurer.getAppliedPropertySources());
		}
		if (this.environment instanceof ConfigurableEnvironment) {
			MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment)
					.getPropertySources();
			return new FlatPropertySources(propertySources);
		}
		// empty, so not very useful, but fulfils the contract
		logger.warn("Unable to obtain PropertySources from "
				+ "PropertySourcesPlaceholderConfigurer or Environment");
		return new MutablePropertySources();
	}

	private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() {
		// Take care not to cause early instantiation of all FactoryBeans
		if (this.beanFactory instanceof ListableBeanFactory) {
			ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
			Map<String, PropertySourcesPlaceholderConfigurer> beans = listableBeanFactory
					.getBeansOfType(PropertySourcesPlaceholderConfigurer.class, false,
							false);
			if (beans.size() == 1) {
				return beans.values().iterator().next();
			}
			if (beans.size() > 1 && logger.isWarnEnabled()) {
				logger.warn("Multiple PropertySourcesPlaceholderConfigurer "
						+ "beans registered " + beans.keySet()
						+ ", falling back to Environment");
			}
		}
		return null;
	}

当PropertySourcesPlaceholderConfigurer存在时,则从PropertySourcesPlaceholderConfigurer获取属性源,下面分析下PropertySourcesPlaceholderConfigurer获取属性源的过程;

首先看下类PropertySourcesPlaceholderConfigurer的继承结构:

可以看出,PropertySourcesPlaceholderConfigurer是一个bean工厂后处理器,其生命周期方法实现如下:

/**
	 * {@inheritDoc}
	 * <p>Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
	 * against this configurer's set of {@link PropertySources}, which includes:
	 * <ul>
	 * <li>all {@linkplain org.springframework.core.env.ConfigurableEnvironment#getPropertySources
	 * environment property sources}, if an {@code Environment} {@linkplain #setEnvironment is present}
	 * <li>{@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any}
	 * {@linkplain #setLocations have} {@linkplain #setProperties been}
	 * {@linkplain #setPropertiesArray specified}
	 * <li>any property sources set by calling {@link #setPropertySources}
	 * </ul>
	 * <p>If {@link #setPropertySources} is called, <strong>environment and local properties will be
	 * ignored</strong>. This method is designed to give the user fine-grained control over property
	 * sources, and once set, the configurer makes no assumptions about adding additional sources.
	 */
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		if (this.propertySources == null) {
			this.propertySources = new MutablePropertySources();
			if (this.environment != null) {
				this.propertySources.addLast(
					new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
						@Override
						public String getProperty(String key) {
							return this.source.getProperty(key);
						}
					}
				);
			}
			try {
				PropertySource<?> localPropertySource =
						new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
				if (this.localOverride) {
					this.propertySources.addFirst(localPropertySource);
				}
				else {
					this.propertySources.addLast(localPropertySource);
				}
			}
			catch (IOException ex) {
				throw new BeanInitializationException("Could not load properties", ex);
			}
		}

		processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
		this.appliedPropertySources = this.propertySources;
	}


/**
	 * Visit each bean definition in the given bean factory and attempt to replace ${...} property
	 * placeholders with values from the given properties.
	 */
	protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
			final ConfigurablePropertyResolver propertyResolver) throws BeansException {

		propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
		propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
		propertyResolver.setValueSeparator(this.valueSeparator);

		StringValueResolver valueResolver = new StringValueResolver() {
			@Override
			public String resolveStringValue(String strVal) {
				String resolved = (ignoreUnresolvablePlaceholders ?
						propertyResolver.resolvePlaceholders(strVal) :
						propertyResolver.resolveRequiredPlaceholders(strVal));
				if (trimValues) {
					resolved = resolved.trim();
				}
				return (resolved.equals(nullValue) ? null : resolved);
			}
		};

		doProcessProperties(beanFactoryToProcess, valueResolver);
	}

从上面可以看出,PropertySources中包含了两部分,一部分由environment构造,另一部分由localPropertySource构成(这里扩展一下,@PropertySource注解引入的属性源是怎么添加进来的??后面单独分析一下

完成PropertySources的构造之后,processProperties方法对bean definition中${}的占位符进行解析和替换,最后返回构造好的PropertySources;

3.2 BeanPostProcessor生命周期方法实现

@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;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}

	@SuppressWarnings("deprecation")
	private void postProcessBeforeInitialization(Object bean, String beanName,
			ConfigurationProperties annotation) {
		Object target = bean;
		PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
				target);
		factory.setPropertySources(this.propertySources);
		factory.setApplicationContext(this.applicationContext);
		factory.setValidator(determineValidator(bean));
		// If no explicit conversion service is provided we add one so that (at least)
		// comma-separated arrays of convertibles can be bound automatically
		factory.setConversionService(this.conversionService != null
				? this.conversionService : getDefaultConversionService());
		if (annotation != null) {
			factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
			factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
			factory.setExceptionIfInvalid(annotation.exceptionIfInvalid());
			factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
			if (StringUtils.hasLength(annotation.prefix())) {
				factory.setTargetName(annotation.prefix());
			}
		}
		try {
			factory.bindPropertiesToTarget();
		}
		catch (Exception ex) {
			String targetClass = ClassUtils.getShortName(target.getClass());
			throw new BeanCreationException(beanName, "Could not bind properties to "
					+ targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
		}
	}

在生命周期方法中,在类上,或者在工厂方法上查找注解ConfigurationProperties,然后构造PropertiesConfigurationFactory,通过方法bindPropertiesToTarget完成属性源到target对象相应属性的绑定,这里的属性源就是在上面的初始化方法中解析的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值