简述
由于在分析Gateway的时候,Gateway通过 GatewayProperties 来自动配置外部配置,故写此文章。
先思考一个问题 Springboot 中我们加入Redis 配置文件,Springboot 是如何加载配置的呢?
spring.redis.url=127.0.0.1:6379
spring.redis.host=localhost
spring.redis.password=123
其中都是以 spring.redis
为前缀。这其实是 Spring Boot
为每个组件提供了对应的 Properties
配置类,并将配置文件中的属性值給映射到配置类中,而且它们有个特点,都是以 Properties
结尾,如 Redis
对应的配置类是 RedisProperties
:
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
}
其中有个名为 @ConfigurationProperties
的注解,它的 prefix
参数就是spring.redis
。该注解的功能就是将配置文件中的属性和 Properties
配置类中的属性进行映射,来达到自动配置的目的。这个过程分为两步,第一步是注册 Properties
配置类,第二步是绑定配置属性,过程中还涉及到一个注解,它就是 @EnableConfigurationProperties
,该注解是用来触发那两步操作的。我们以 Redis
为例来看它使用方式:
...
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
...
}
可以看到它的参数是 RedisProperties
配置类。这个注解就是关键,我们就来从此分析。
源码分析
@EnableConfigurationProperties
@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 {};
}
EnableConfigurationPropertiesImportSelector
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
private static final String[] IMPORTS = {
//① 用于注册 Properties类
ConfigurationPropertiesBeanRegistrar.class.getName(),
//②用于进行绑定配置属性
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@Override
public String[] selectImports(AnnotationMetadata metadata) {
return IMPORTS;
}
}
-
①
用于注册 Properties类//org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector的内部类 public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //① 调用 getTypes 方法,返回 Properties 配置类集合。 //② 调用 register 方法,把 Properties 配置类注册到 Spring 容器中。 getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type)); } }
-
① getTypes
,返回Properties 配置类集合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()); } private List<Class<?>> collectClasses(List<?> values) { return values.stream().flatMap((value) -> Arrays.stream((Object[]) value)).map((o) -> (Class<?>) o) .filter((type) -> void.class != type).collect(Collectors.toList()); }
-
② 调用 register 方法
,把 Properties 配置类注册到 Spring 容器中。private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,Class<?> type) { // getName 返回的是 Bean 的名称 String name = getName(type); // 判断有没有注册过这个 Bean if (!containsBeanDefinition(beanFactory, name)) { //不存在则,则进行注册 registerBeanDefinition(registry, name, type); } } 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()); } //下面这两个方法就是Spring注入,不是本文分析的重点。 private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) { if (beanFactory.containsBeanDefinition(name)) { return true; } BeanFactory parent = beanFactory.getParentBeanFactory(); if (parent instanceof ConfigurableListableBeanFactory) { return c ontainsBeanDefinition((ConfigurableListableBeanFactory) parent, name); } return false; } private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) { assertHasAnnotation(type); GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(type); registry.registerBeanDefinition(name, definition); }
到这里所有需要的 Properties配置类 就被注册到了
Spring
容器中。接下来就是与配制类进行绑定。
-
-
②
用于进行绑定配置属性public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { //① 真正用来绑定属性的 registerConfigurationPropertiesBindingPostProcessor(registry); //② 存储元数据 registerConfigurationBeanFactoryMetadata(registry); } } }
-
① 绑定属性
private void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) { GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition); }
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean { /** * The bean name that this post-processor is registered with. */ public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName(); /** * The bean name of the configuration properties validator. */ 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 的属性初始化后会被调用。 @Override public void afterPropertiesSet() throws Exception { //初始化beanFactoryMetadata this.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME, ConfigurationBeanFactoryMetadata.class); //初始化configurationPropertiesBinder this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(this.applicationContext, VALIDATOR_BEAN_NAME); } //排序 @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 1; } //③ 重写的 BeanPostProcessor 接口中的方法,在 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; } }
-
实现的几个接口:
-
BeanPostProcessor
:这是Bean
的后置处理器。该类有两个方法,一个是 postProcessBeforeInitialization ,Bean
初始化前该方法会被调用; 另一个是 postProcessAfterInitialization ,Bean
初始化后该方法会被调用;需注意的是,Spring
上下文中所有Bean
的初始化都会触发这两个方法。 -
ApplicationContextAware
:这是Spring
的Aware
系列接口之一。该类有一个 setApplicationContext 方法,主要是用来获取ApplicationContext
上下文对象;同理,如果是其它前缀的Aware
,则获取相应前缀名的对象。 -
InitializingBean
:这是Bean
的生命周期相关接口。该类有一个 afterPropertiesSet 方法,当Bean
的所有属性初始化后,该方法会被调用。其中,
BeanPostProcessor
和InitializingBean
的功能都是在Bean
的生命周期中执行额外的操作。
-
-
① setApplicationContext
: 重写的 ApplicationContextAware 接口中的方法,用来获取 ApplicationContext 上下文对象 -
② afterPropertiesSet
: 重写的 InitializingBean 接口中的方法,当 Bean 的属性初始化后会被调用。该方法主要是会初始化configurationPropertiesBinder
class ConfigurationPropertiesBinder { ConfigurationPropertiesBinder(ApplicationContext applicationContext, String validatorBeanName) { //① 上下文 this.applicationContext = applicationContext; //② 获取外部的所有配置 this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources(); //③ 配置的校验器 this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext, validatorBeanName); this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext); } }
主要看②,获取外部的所有配置。
// ①通过 extractEnvironmentPropertySources 方法,返回 MutablePropertySources 对象,MutablePropertySources 是 PropertySources 的实现类 public PropertySources getPropertySources() { PropertySourcesPlaceholderConfigurer configurer = getSinglePropertySourcesPlaceholderConfigurer(); if (configurer != null) { return configurer.getAppliedPropertySources(); } //② 调用 Environment 的 getPropertySources 方法,返回 MutablePropertySources MutablePropertySources sources = extractEnvironmentPropertySources(); if (sources != null) { return sources; } throw new IllegalStateException( "Unable to obtain PropertySources from " + "PropertySourcesPlaceholderConfigurer or Environment"); } //这里的Environment 就是在SpringBoot 初始化的时候创建的 Environment private MutablePropertySources extractEnvironmentPropertySources() { Environment environment = this.applicationContext.getEnvironment(); if (environment instanceof ConfigurableEnvironment) { return ((ConfigurableEnvironment) environment).getPropertySources(); } return null; }
到这里就是已经获取到所有外部的配置了。具体的配置文件是如何被加载成Environment的,
可以看我的这篇文章。
-
③ postProcessBeforeInitialization
: 重写的 BeanPostProcessor 接口中的方法,在 Bean 初始化前会被调用,绑定属性的操作就是从这里开始通过上面 ② 中的 afterPropertiesSet 方法,初始化好了 ConfigurationPropertiesBinder 对象以及获取到了 Environment 对象,那么这里就开始与配置类进行绑定了
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //① 判断当前类是否被@ConfigurationProperties 注解 ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class); if (annotation != null) { //② 进行绑定 bind(bean, beanName, annotation); } return bean; } private void bind(Object bean, String beanName, ConfigurationProperties annotation) { ResolvableType type = getBeanType(bean, beanName); Validated validated = getAnnotation(bean, beanName, Validated.class); Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated } : new Annotation[] { annotation }; Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations); try { //③ 会发现这个是调用了在afterPropertiesSet方法初始化好的configurationPropertiesBinder中的bind方法。 this.configurationPropertiesBinder.bind(target); } catch (Exception ex) { throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex); } }
-
③ 会发现这个是调用了在afterPropertiesSet方法初始化好的configurationPropertiesBinder中的bind方法。
public void bind(Bindable<?> target) { //校验是否是@ConfigurationProperties 注解的 ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class); Assert.state(annotation != null, () -> "Missing @ConfigurationProperties on " + target); List<Validator> validators = getValidators(target); BindHandler bindHandler = getBindHandler(annotation, validators); //绑定 getBinder().bind(annotation.prefix(), target, bindHandler); }
到这里就已经知道了
@ConfigurationProperties
它是怎么运作的,我个人就分析到这。如果要分析如何绑定的,那么请自行分析
-
-
总结:
-
其实当我们使用
@ConfigurationProperties
时,无需标注@EnableConfigurationProperties
注解,因为Spring Boot
在自动装配的过程中会帮我们加载一个名为ConfigurationPropertiesAutoConfiguration
的类,该类是在spring.factories
中定义好的:# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
-
外部的所有配置文件都会被填充到
Environment
对象中,@ConfigurationProperties
其实也就会获取绑定器然后获取Environment
对象,根据被注解的类与之进行映射达到自动配置的目的。 -
这篇文章主要是为了我在分析Gateway 而写的
-
Gateway 通过 GatewayProperties 来自动装配我们在 properties 或者 yml 中的配置,所以为什么我们在进行配置的时候需要加上
spring.cloud.gateway
@ConfigurationProperties("spring.cloud.gateway") //① 表明以 "spring.cloud.gateway" 前缀的 properties 会绑定 GatewayProperties。 @Validated public class GatewayProperties { //Route private List<RouteDefinition> routes = new ArrayList<>(); //filter private List<FilterDefinition> defaultFilters = new ArrayList<>(); }
//看到这里是不是已经豁然开朗了 spring: cloud: gateway: routes: - id: websocket_test uri: ws://localhost:9000 order: 9000 predicates: - Path=/echo
-
但是Gateway 的自动装配没有加入
ConfigurationPropertiesAutoConfiguration
,它是通过GatewayAutoConfiguration
来开启@ConfigurationProperties
的# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\ @EnableConfigurationProperties// 开启 @ConfigurationProperties public class GatewayAutoConfiguration { }
-
-