table of Contents
3, outside of the core configuration
Subsequently the last chapter, "the Spring of the Boot external configuration (a)"
3.2 @ConfigurationProperties
It is well known, when the integrated Spring Boot external components, can be in properties
or the YAML
attribute definition file configuration required components, such as Redis
components:
spring.redis.url=redis://user:[email protected]:6379
spring.redis.host=localhost
spring.redis.password=123456
spring.redis.port=6379
Which are to spring.redis
be prefixed. This is in fact Spring Boot
provided for each component corresponding to a Properties
configuration class, attribute values and the configuration file to the configuration maps to the class, and they have a characteristic, are to Properties
end, such as Redis
the corresponding configuration class is RedisProperties
:
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
...
}
Which has a named @ConfigurationProperties
annotation, its prefix
argument is a good agreement prefix. The annotation function is to profile attributes and Properties
configuration class attribute mapping, to achieve the purpose of automatic configuration. This two-step process, the first step is a registered Properties
configuration class, the second step is to bind configuration properties, the process also involves a comment, it is @EnableConfigurationProperties
the annotation that is used to trigger a two-step operation. We have Redis
an example look at ways to use it:
...
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
...
}
You can see its parameters are RedisProperties
configured class. Through previous "Spring Boot automatic assembly (a)" we know that the annotation belongs @Enable
module annotated, so that the annotation must have @Import
introduced implements ImportSelector
or ImportBeanDefinitionRegistrar
type of interface, by introducing a specific functional classes to implement. We enter this comment:
@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 {};
}
Sure enough, by @Import
introducing the EnableConfigurationPropertiesImportSelector
entire processing flow in this class are handled classes:
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}
...
}
This class implements ImportSelector
the interface, and rewrite selectImports method, which returns the class is Spring
loaded. Here you can see the two classes returned, which ConfigurationPropertiesBeanRegistrar
is used to register Properties
configuration class, and ConfigurationPropertiesBindingPostProcessorRegistrar
is used to bind configuration properties, and they have achieved ImportBeanDefinitionRegistrar
the interface, will be registered directly registerBeanDefinitions method of rewriting Bean
operations. These characteristics are "Spring Boot automatic assembly (a)" 3.1 bar introduced, is not described here. Next, we describe these two categories.
3.2.1 Register Configuration Properties class
Let's look at ConfigurationPropertiesBeanRegistrar
how these configurations registered classes. We go directly to implementation of this class:
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {
// 1、第一步会先执行重写的 registerBeanDefinitions 方法,
// 入参分别是 AnnotationMetadata 和 BeanDefinitionRegistry。
// AnnotationMetadata 是获取类的元数据的,如注解信息、 classLoader 等,
// BeanDefinitionRegistry 则是直接注册所需要的 Bean
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 2、调用 getTypes 方法,返回 Properties 配置类集合。进入 2.1 详细查看
// 3、调用 register 方法,把 Properties 配置类注册到 Spring 容器中。进入 3.1 详细查看
getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
}
// 2.1
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
// 获取指定注解的所有属性值,key是属性名称,Value是值
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
// 返回 key 名称为 value 的值,这里返回的就是 Properties 配置类
return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
}
// 3.1
private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,
Class<?> type) {
// getName 返回的是 Bean 的名称。进入 3.2 详细查看
String name = getName(type);
// 判断有没有注册过这个 Bean
if (!containsBeanDefinition(beanFactory, name)) {
// 没有则注册该 Bean。入参是注册器、Bean 的名称、需注册的 Bean。进入 4 详细查看
registerBeanDefinition(registry, name, type);
}
}
// 3.2
private String getName(Class<?> type) {
// 获取 Properties 配置类上标注的 ConfigurationProperties 注解信息
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);
// 获取该注解中 prefix 的属性值
String prefix = (annotation != null) ? annotation.prefix() : "";
// 最后返回的是名称格式是 属性前缀-配置类全路径名,如:
// spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
}
// 4、
private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) {
assertHasAnnotation(type);
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
// 通过 registerBeanDefinition 方法,注册 Bean 。
// 后期会有 Spring 系列的文章详细介绍该过程,到时候大家再一起讨论。
registry.registerBeanDefinition(name, definition);
}
}
After the implementation of all of our Properties
configuration class was registered to the Spring
vessel. Next, we look at the data in the configuration file and how Properties
to bind configuration class property.
3.2.2 Binding Configuration Properties
We go directly to ConfigurationPropertiesBindingPostProcessorRegistrar
class view:
public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
registerConfigurationPropertiesBindingPostProcessor(registry);
registerConfigurationBeanFactoryMetadata(registry);
}
}
...
}
Here also registered two in registerBeanDefinitions method overridden Bean
, one ConfigurationBeanFactoryMetadata
, this is used to store metadata, we do not do too much attention; the other is ConfigurationPropertiesBindingPostProcessor
, the class is used to bind the property, we mainly class discussion:
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
...
}
It can be seen, which implements a number of interfaces, and are Spring
provided by the extended interface. Here we briefly describe:
1, BeanPostProcessor: this is the
Bean
post-processor. This class has two methods, one is postProcessBeforeInitialization,Bean
before the initialization method is called;
the other is postProcessAfterInitialization,Bean
the method will be called after initialization; must be noted that,Spring
in the context of allBean
the initialization will trigger these two methods.
2, ApplicationContextAware: This isSpring
aAware
one serial interface. SetApplicationContext class has a method, is mainly used to obtainApplicationContext
context object; Similarly, if the other prefixAware
, the prefix corresponding to the object name acquired.
3, InitializingBean: This isBean
the life cycle of related interfaces. This class has a method afterPropertiesSet, whenBean
all the properties after initialization, this method will be called.
Among them,BeanPostProcessor
andInitializingBean
the functions are inBean
additional operations performed life cycle.
Here we simply understand on the line, will later Spring
discuss in detail the series of articles.
Next, we introduce the method in this class:
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
...
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
private ApplicationContext applicationContext;
private ConfigurationPropertiesBinder configurationPropertiesBinder;
// 1、这是重写的 ApplicationContextAware 接口中的方法,用来获取 ApplicationContext 上下文对象
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// 2、这是重写的 InitializingBean 接口中的方法,当 Bean 的属性初始化后会被调用。
// 该方法主要对 ConfigurationBeanFactoryMetadata 和 ConfigurationPropertiesBinder 进行实例化
@Override
public void afterPropertiesSet() throws Exception {
this.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME,
ConfigurationBeanFactoryMetadata.class);
this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(this.applicationContext,
VALIDATOR_BEAN_NAME);
}
// 3、这是重写的 BeanPostProcessor 接口中的方法,在 Bean 初始化前会被调用,绑定属性的操作就是从这里开始。
// 入参 bean 就是待初始化的 Bean,beanName 就是 Bean 的名称
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
if (annotation != null) {
bind(bean, beanName, annotation);
}
return bean;
}
...
}
AfterPropertiesSet us first look at the second step, the method of the two classes is instantiated, one is from ApplicationContext
the acquired
ConfigurationBeanFactoryMetadata
class, operand data is used, too much attention is not; the other is configured by a parameterized initialization of the ConfigurationPropertiesBinder
class, the parameter is ApplicationContext
an object and configurationPropertiesValidator string. We enter the class constructor:
class ConfigurationPropertiesBinder {
private final ApplicationContext applicationContext;
private final PropertySources propertySources;
private final Validator configurationPropertiesValidator;
private final boolean jsr303Present;
...
ConfigurationPropertiesBinder(ApplicationContext applicationContext, String validatorBeanName) {
this.applicationContext = applicationContext;
this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources();
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext,
validatorBeanName);
this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
}
...
}
Instantiate the class and four classes, we focus on the PropertySources
instantiation process, specifically by PropertySourcesDeducer
getPropertySources class method, we enter the class:
class PropertySourcesDeducer {
...
private final ApplicationContext applicationContext;
PropertySourcesDeducer(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// 1、通过 extractEnvironmentPropertySources 方法,返回 MutablePropertySources 对象,
// MutablePropertySources 是 PropertySources 的实现类
public PropertySources getPropertySources() {
...
MutablePropertySources sources = extractEnvironmentPropertySources();
if (sources != null) {
return sources;
}
throw new IllegalStateException(
"Unable to obtain PropertySources from " + "PropertySourcesPlaceholderConfigurer or Environment");
}
// 2、调用 Environment 的 getPropertySources 方法,返回 MutablePropertySources
private MutablePropertySources extractEnvironmentPropertySources() {
Environment environment = this.applicationContext.getEnvironment();
if (environment instanceof ConfigurableEnvironment) {
return ((ConfigurableEnvironment) environment).getPropertySources();
}
return null;
}
...
}
See this, we should be more familiar with, Environment
is what we in "Spring Boot externalize configuration (a)" environmental applications running in Section 3.1 have said, access to all of the external configuration data through the class, and MutablePropertySources
it is an external storage configuration of the underlying real object.
Here, the second step of the method proceeds afterPropertiesSet finished, mainly instantiated ConfigurationPropertiesBinder
object, the object is stored in all of the external configuration. Then look postProcessBeforeInitialization method of the third step:
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
if (annotation != null) {
bind(bean, beanName, annotation);
}
return bean;
}
Mentioned above, all Bean
initialization will call this method, we first determine whether the current Bean
there is no marked @ConfigurationProperties
notes, there is then the current Bean
is Properties
configured class, and call the bind method of the bind attribute class operation, we enter the method:
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
...
try {
this.configurationPropertiesBinder.bind(target);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
}
}
Here in the second step is called instantiation ConfigurationPropertiesBinder
bind method object:
class ConfigurationPropertiesBinder {
...
public void bind(Bindable<?> target) {
...
getBinder().bind(annotation.prefix(), target, bindHandler);
}
...
private Binder getBinder() {
if (this.binder == null) {
this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
getConversionService(), getPropertyEditorInitializer());
}
return this.binder;
}
private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {
return ConfigurationPropertySources.from(this.propertySources);
}
...
}
Back through the first inside getBinder () Binder
object. GetBinder by the method Binder
of the object created with the constructor parameters, we focus on the first argument getConfigurationPropertySources method returns:
class ConfigurationPropertiesBinder {
...
private final PropertySources propertySources;
...
private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {
return ConfigurationPropertySources.from(this.propertySources);
}
...
}
Specifically by ConfigurationPropertySources
return from the method, the parameter propertySources
is the second step of example ConfigurationPropertiesBinder
initialization good value when the object, the source object is stored inside the external configuration of the PropertySource
, we enter the method:
public final class ConfigurationPropertySources {
...
public static Iterable<ConfigurationPropertySource> from(Iterable<PropertySource<?>> sources) {
return new SpringConfigurationPropertySources(sources);
}
...
}
Eventual return is SpringConfigurationPropertySources
arranged source object, the "Spring Boot external configuration of (a)," in said before, this type of adapter is doing a work to MutablePropertySources
be converted to ConfigurationPropertySource
.
Then, the object passed in Binder
the constructor used to create the object:
public class Binder {
...
private final Iterable<ConfigurationPropertySource> sources;
...
public Binder(Iterable<ConfigurationPropertySource> sources,
PlaceholdersResolver placeholdersResolver,
ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
this.sources = sources;
...
}
...
}
Thus, Binder
the object on the outside of a data configuration there, and all the subsequent operations are carried out in binding the class. Due to the subsequent intermediate process is too complex and difficult to understand, here we go directly to the final step, the detailed process of students interested your own research, not repeat them here.
Enter the final stage of the bind method:
// 这里着重介绍一下 BeanProperty 类,该类存储了 properties 配置类中的字段及字段的set、get方法,存储的是反射中的类。
// 如 RedisProperties 中的 url 字段,则 BeanProperty 对象中存储的是
// url 的 Field 类、setUrl 的 Method 类、getUrl 的 Method 类。
private <T> boolean bind(BeanSupplier<T> beanSupplier,
BeanPropertyBinder propertyBinder, BeanProperty property) {
// 这里获取的是字段名
String propertyName = property.getName();
// 这里获取的是字段类型
ResolvableType type = property.getType();
Supplier<Object> value = property.getValue(beanSupplier);
Annotation[] annotations = property.getAnnotations();
// 这里获取到了配置文件中的值,该值来源于 SpringConfigurationPropertySources 对象
Object bound = propertyBinder.bindProperty(propertyName,
Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
if (bound == null) {
return false;
}
if (property.isSettable()) {
// 最后则是通过 set Method 的 invoke 方法,也就是反射的形式进行赋值。
property.setValue(beanSupplier, bound);
}
else if (value == null || !bound.equals(value.get())) {
throw new IllegalStateException(
"No setter found for property: " + property.getName());
}
return true;
}
At this point, the entire configuration properties of the binding process ends. You can see, the final acquisition of external configuration data from previously loaded Environment
object.
Finally, briefly recall @ConfigurationProperties
Notes implementation profile property values and configuration class property mapping:
1, the first
@ConfigurationProperties
label in theProperties
configuration class, attribute prefix parameter is a good agreement.
2, then@EnableConfigurationProperties
to trigger the whole process, it is the parameterProperties
configuration class.
3,@EnableConfigurationProperties
by@import
introducing aEnableConfigurationPropertiesImportSelector
class, the class and two classes loaded, for registering aProperties
configuration class, to bind the other configuration attributes.
4, finally, it is reflected by way of binding properties and property values derivedEnvironment
.
3.1.3 ConfigurationPropertiesAutoConfiguration
In fact, when we use @ConfigurationProperties
the time, no marked @EnableConfigurationProperties
notes, because Spring Boot
will help us in the process of automatic assembly loaded in a named ConfigurationPropertiesAutoConfiguration
class that is in spring.factories
good defined:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
Specific automatic assembly process in "Spring Boot automatic assembly (B)" discussed in this article, will not repeat them here. Let's look at ConfigurationPropertiesAutoConfiguration
implementation:
@Configuration
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {
}
Very simple, direct by tagging @EnableConfigurationProperties
to open the process to automatically configure annotation. So that registered how Properties
to configure it like? Because of the above mentioned, Properties
configuration class is a parameter passed in the annotation. In fact, just marked on the configuration class @Component
notes on the line, then it will be Spring
scanned, and then register.
4, summary
Finally, to Spring Boot
configure externalizing do a whole summary:
1, firstly, it is arranged outside of
Spring Boot
a feature, achieved primarily through external configuration code resources and cooperating to avoid hard-coded, to provide flexibility in application data or behavior changes.
2, then introduces several resource types of external configurations, such asproperties
and theYAML
profile type, and describes the configuration of access to external resources in several ways.
3. Secondly, theEnvironment
loading process class, and all loaded into the external configuration ofEnvironment
the bottom layer is achieved.Environment
Is theSpring Boot
outer configuration of the core class that stores all of the external configuration of resources, and other resources to obtain the external configuration of the embodiment are also dependent on the class.
4. Finally, theSpring Boot
framework core of@ConfigurationProperties
annotation, the annotation is toapplication
profile property values andProperties
configuration class attribute mapping, automatic configuration to achieve the purpose, and take you to explore the underlying process of implementation.
These are the contents of this chapter, such as over or there is an error in the article should be added please put forward, I am grateful.