目录html
接着上一章,《Spring Boot 外部化配置(一)》java
众所周知,当 Spring Boot 集成外部组件后,就可在 properties
或 YAML
配置文件中定义组件须要的属性,如 Redis
组件:redis
spring.redis.url=redis://user:password@example.com:6379 spring.redis.host=localhost spring.redis.password=123456 spring.redis.port=6379
其中都是以 spring.redis
为前缀。这实际上是 Spring Boot
为每一个组件提供了对应的 Properties
配置类,并将配置文件中的属性值給映射到配置类中,并且它们有个特色,都是以 Properties
结尾,如 Redis
对应的配置类是 RedisProperties
:spring
@ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { private String url; private String host = "localhost"; private String password; private int port = 6379; ... }
其中有个名为 @ConfigurationProperties
的注解,它的 prefix
参数就是约定好的前缀。该注解的功能就是将配置文件中的属性和 Properties
配置类中的属性进行映射,来达到自动配置的目的。这个过程分为两步,第一步是注册 Properties
配置类,第二步是绑定配置属性,过程当中还涉及到一个注解,它就是 @EnableConfigurationProperties
,该注解是用来触发那两步操做的。咱们以 Redis
为例来看它使用方式:app
... @EnableConfigurationProperties(RedisProperties.class) public class RedisAutoConfiguration { ... }
能够看到它的参数是 RedisProperties
配置类。经过前面的 《Spring Boot 自动装配(一)》 咱们知道,该注解是属于 @Enable
模块注解,因此,该注解中必然有 @Import
导入的实现了 ImportSelector
或 ImportBeanDefinitionRegistrar
接口的类,具体的功能都由导入的类来实现。咱们进入该注解:框架
@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
类,整个的处理流程都是在该类中进行处理:less
class EnableConfigurationPropertiesImportSelector implements ImportSelector { private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; @Override public String[] selectImports(AnnotationMetadata metadata) { return IMPORTS; } ... }
该类实现了 ImportSelector
接口,并重写了 selectImports 方法,该方法返回的类会被 Spring
加载。能够看到这里返回了两个类,其中 ConfigurationPropertiesBeanRegistrar
就是用来注册 Properties
配置类的,而 ConfigurationPropertiesBindingPostProcessorRegistrar
则是用来绑定配置属性,且它们都实现了 ImportBeanDefinitionRegistrar
接口,会在重写的 registerBeanDefinitions 方法中进行直接注册 Bean
的操做。以上特性都在 《Spring Boot 自动装配(一)》的 3.1 小节介绍过,这里不在叙述。接下来,咱们分别介绍这两个类。ide
咱们先来看看 ConfigurationPropertiesBeanRegistrar
是如何注册这些配置类的。咱们直接进入该类的实现:post
public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { // 一、第一步会先执行重写的 registerBeanDefinitions 方法, // 入参分别是 AnnotationMetadata 和 BeanDefinitionRegistry。 // AnnotationMetadata 是获取类的元数据的,如注解信息、 classLoader 等, // BeanDefinitionRegistry 则是直接注册所须要的 Bean public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 二、调用 getTypes 方法,返回 Properties 配置类集合。进入 2.1 详细查看 // 三、调用 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()); } // 四、 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); } }
执行完后,咱们全部的 Properties
配置类就被注册到了 Spring
容器中。接下来,咱们来看看配置文件中的数据是如何与 Properties
配置类中的属性进行绑定的。ui
咱们直接进入 ConfigurationPropertiesBindingPostProcessorRegistrar
类中进行查看:
public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { registerConfigurationPropertiesBindingPostProcessor(registry); registerConfigurationBeanFactoryMetadata(registry); } } ... }
这里也是在重写的 registerBeanDefinitions 方法中注册了两个 Bean
,一个是 ConfigurationBeanFactoryMetadata
,这个是用来存储元数据的,咱们不作过多关注;另外一个是 ConfigurationPropertiesBindingPostProcessor
,该类就是用来绑定属性的,咱们主要对该类进行讨论:
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean { ... }
能够看到,该类实现了几个接口,且都是 Spring
提供的扩展接口。这里咱们简要介绍一下:
一、BeanPostProcessor:这是
Bean
的后置处理器。该类有两个方法,一个是 postProcessBeforeInitialization ,Bean
初始化前该方法会被调用;
另外一个是 postProcessAfterInitialization ,Bean
初始化后该方法会被调用;需注意的是,Spring
上下文中全部Bean
的初始化都会触发这两个方法。
二、ApplicationContextAware:这是Spring
的Aware
系列接口之一。该类有一个 setApplicationContext 方法,主要是用来获取ApplicationContext
上下文对象;同理,若是是其它前缀的Aware
,则获取相应前缀名的对象。
三、InitializingBean:这是Bean
的生命周期相关接口。该类有一个 afterPropertiesSet 方法,当Bean
的全部属性初始化后,该方法会被调用。
其中,BeanPostProcessor
和InitializingBean
的功能都是在Bean
的生命周期中执行额外的操做。
这里咱们简单的了解就行,后面会在 Spring
系列的文章中详细讨论。
接着,咱们介绍该类中的方法:
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; // 一、这是重写的 ApplicationContextAware 接口中的方法,用来获取 ApplicationContext 上下文对象 @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } // 二、这是重写的 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); } // 三、这是重写的 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 方法,该方法中实例化了两个类,一个是从 ApplicationContext
中获取的
ConfigurationBeanFactoryMetadata
类,是用来操做元数据的,不作过多关注;另外一个是经过带参构造器初始化的 ConfigurationPropertiesBinder
类,参数是 ApplicationContext
对象和 configurationPropertiesValidator 字符串。咱们进入该类的构造器中:
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); } ... }
该类中又实例化了四个类,咱们重点关注 PropertySources
的实例化过程,具体是经过 PropertySourcesDeducer
类的 getPropertySources 方法,咱们进入该类:
class PropertySourcesDeducer { ... private final ApplicationContext applicationContext; PropertySourcesDeducer(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } // 一、经过 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"); } // 二、调用 Environment 的 getPropertySources 方法,返回 MutablePropertySources private MutablePropertySources extractEnvironmentPropertySources() { Environment environment = this.applicationContext.getEnvironment(); if (environment instanceof ConfigurableEnvironment) { return ((ConfigurableEnvironment) environment).getPropertySources(); } return null; } ... }
看到这,你们应该比较熟悉了,Environment
就是咱们在 《Spring Boot 外部化配置(一)》中 3.1 小节讲过的应用运行时的环境,经过该类可获取全部的外部化配置数据,而 MutablePropertySources
则是底层真正存储外部化配置对象的。
到这里,第二步的 afterPropertiesSet 方法就执行完了,主要是实例化了 ConfigurationPropertiesBinder
对象,而该对象中存储了全部的外部化配置。接着看第三步的 postProcessBeforeInitialization 方法:
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; }
上面说过,全部 Bean
初始化都会调用这个方法,因此先判断当前 Bean
有没有标注 @ConfigurationProperties
注解,有则表示当前 Bean
是 Properties
配置类,并调用 bind 方法对该类进行绑定属性的操做,咱们进入该方法:
private void bind(Object bean, String beanName, ConfigurationProperties annotation) { ... try { this.configurationPropertiesBinder.bind(target); } catch (Exception ex) { throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex); } }
这里调用了在第二步实例化的 ConfigurationPropertiesBinder
对象中的 bind 方法:
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); } ... }
里面先经过 getBinder() 返回 Binder
对象。在 getBinder 方法中是经过 Binder
带参构造器建立的该对象,咱们主要关注 getConfigurationPropertySources 方法返回的第一个参数:
class ConfigurationPropertiesBinder { ... private final PropertySources propertySources; ... private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() { return ConfigurationPropertySources.from(this.propertySources); } ... }
具体的是经过 ConfigurationPropertySources
中的 from 方法返回,入参 propertySources
是在第二步实例化 ConfigurationPropertiesBinder
对象时初始化好的值,里面存储的是外部化配置的源对象 PropertySource
,咱们进入该方法:
public final class ConfigurationPropertySources { ... public static Iterable<ConfigurationPropertySource> from(Iterable<PropertySource<?>> sources) { return new SpringConfigurationPropertySources(sources); } ... }
最终返回的就是 SpringConfigurationPropertySources
配置源对象,在 《Spring Boot 外部化配置(一)》中讲过,该类主要是作一个适配器的工做,将 MutablePropertySources
转换为 ConfigurationPropertySource
。
以后,该对象传入了 Binder
的构造器中,用于建立该对象:
public class Binder { ... private final Iterable<ConfigurationPropertySource> sources; ... public Binder(Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver, ConversionService conversionService, Consumer<PropertyEditorRegistry> propertyEditorInitializer) { this.sources = sources; ... } ... }
至此, Binder
对象中就存有一份外部化配置的数据,且后续全部的绑定操做都在该类中进行。因后续中间过程实在太过庞杂,且不易理解,这里咱们直接进入最后一步,对详细过程感兴趣的同窗请自行研究,这里再也不赘述。
进入最后阶段的 bind 方法:
// 这里着重介绍一下 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; }
至此,整个绑定配置属性的流程结束。能够看到,最终获取的外部化配置数据来源于前文加载的 Environment
对象。
最后来简单回顾一下 @ConfigurationProperties
注解实现配置文件中属性值和配置类属性映射的过程:
一、首先将
@ConfigurationProperties
标注在Properties
配置类中,参数是约定好的属性前缀。
二、而后经过@EnableConfigurationProperties
来触发整个流程,参数是Properties
配置类。
三、在@EnableConfigurationProperties
中经过@import
导入了EnableConfigurationPropertiesImportSelector
类,该类中又加载了两个类,一个用来注册Properties
配置类,另外一个用来绑定配置属性。
四、最后,是经过反射的方式进行属性绑定,且属性值来源于Environment
。
其实,当咱们使用 @ConfigurationProperties
时,无需标注 @EnableConfigurationProperties
注解,由于 Spring Boot
在自动装配的过程当中会帮咱们加载一个名为 ConfigurationPropertiesAutoConfiguration
的类,该类是在 spring.factories
中定义好的:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
具体的自动装配过程在 《Spring Boot 自动装配(二)》 这篇文章中讨论过,这里再也不赘述。咱们来看看 ConfigurationPropertiesAutoConfiguration
实现:
@Configuration @EnableConfigurationProperties public class ConfigurationPropertiesAutoConfiguration { }
很简单,直接经过标注 @EnableConfigurationProperties
注解来开启自动配置的流程。那这样怎么注册 Properties
配置类呢?由于上面说过,Properties
配置类是经过该注解的参数传递进来的。其实,只需在配置类上标注 @Component
注解就好了,以后会被 Spring
扫描到,而后注册。
最后,来对 Spring Boot
外部化配置作一个总体的总结:
一、首先,外部化配置是
Spring Boot
的一个特性,主要是经过外部的配置资源实现与代码的相互配合,来避免硬编码,提供应用数据或行为变化的灵活性。
二、而后介绍了几种外部化配置的资源类型,如properties
和YAML
配置文件类型,并介绍了获取外部化配置资源的几种方式。
三、其次,介绍了Environment
类的加载流程,以及全部外部化配置加载到Environment
中的底层是实现。Environment
是Spring Boot
外部化配置的核心类,该类存储了全部的外部化配置资源,且其它获取外部化配置资源的方式也都依赖于该类。
四、最后,介绍了Spring Boot
框架中核心的@ConfigurationProperties
注解,该注解是将application
配置文件中的属性值和Properties
配置类中的属性进行映射,来达到自动配置的目的,并带你们探讨了这一过程的底层实现。
以上就是本章的内容,如过文章中有错误或者须要补充的请及时提出,本人感激涕零。