目录
1. EnableConfigurationProperties
2. EnableConfigurationPropertiesImportSelector
2.1 ConfigurationPropertiesBeanRegistrar
2.2 ConfigurationPropertiesBindingPostProcessorRegistrar
3. ConfigurationPropertiesBindingPostProcessor
3.1 InitializingBean.afterPropertiesSet实现
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后处理器,主要完成属性文件中的配置信息绑定到配置类上,下面主要从如下几点来进行剖析:
- InitializingBean接口方法afterPropertiesSet实现
- 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对象相应属性的绑定,这里的属性源就是在上面的初始化方法中解析的。