參考 知識星球 中 芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄
該系列文章是筆者在學習 Spring Boot 過程中總結下來的,里面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring Boot 源碼分析 GitHub 地址 進行閱讀
Spring Boot 版本:2.2.x
最好對 Spring 源碼有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章導讀》 系列文章
如果該篇內容對您有幫助,麻煩點擊一下“推薦”,也可以關注博主,感激不盡~
該系列其他文章請查看:《精盡 Spring Boot 源碼分析 - 文章導讀》
概述
我們的 Spring Boot 應用經常會在 application.yml
配置文件里面配置一些自定義的配置,對於不同環境設置不同的值,然后可以通過 @ConfigurationProperties
注解將這些配置作為 Spring Bean 的屬性值進行注入,那么本文來簡單分析一下這個注解是如何將配置自動設置到 Spring Bean 的。
在開始之前,結合我前面的這么多 Spring 相關的源碼分析文章,想必你會知道原理的,無非就是在 Spring Bean 的加載過程的某個階段(大概率是初始化的時候)通過 BeanPostProcessor 解析該注解,並獲取對應的屬性值設置到其中。
先來看看這個注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
/**
* 指定的配置項前綴
*/
@AliasFor("prefix")
String value() default "";
/**
* 指定的配置項前綴
*/
@AliasFor("value")
String prefix() default "";
/**
* 是否忽略無效的字段
*/
boolean ignoreInvalidFields() default false;
/**
* 是否忽略不知道的字段
*/
boolean ignoreUnknownFields() default true;
}
使用方式有兩種:
@ConfigurationProperties
+@Component
注解(一個類)@EnableConfigurationProperties
(某個 Bean)+@ConfigurationProperties
注解(另一個普通類)
第二種方式和第一種原理都是一樣的,不過第二種方式會注冊一個 BeanPostProcessor 用於處理帶有 @ConfigurationProperties
注解的 Spring Bean,同時會將指定的 Class 們解析出 BeanDefinition(Bean 的前身)並注冊,這也就是為什么第二種不用標注 @Component
注解
那么第一種方式在哪注冊的 BeanPostProcessor 呢?因為 Spring Boot 有一個 ConfigurationPropertiesAutoConfiguration
自動配置類,如下:
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration { }
很簡單,也是通過 @EnableConfigurationProperties
注解注冊的這個 BeanPostProcessor 對象
這里有一個疑問,為什么
@ConfigurationProperties
注解上面不直接加一個@Component
注解呢?可能是因為這個注解的作用就是讓 配置類 外部化配置吧
@EnableConfigurationProperties
org.springframework.boot.context.properties.EnableConfigurationProperties
,支持將指定的帶有 @ConfigurationProperties
注解的類解析出 BeanDefinition(Bean 的前身)並注冊,同時注冊一個 BeanPostProcessor 去處理帶有 @ConfigurationProperties
注解的 Bean
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
/**
* The bean name of the configuration properties validator.
* @since 2.2.0
*/
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
/**
* 指定的 Class 類對象們
*/
Class<?>[] value() default {};
}
可以看到這個注解也是通過 @Import
注解來驅動某個功能的,是不是發現 @EnableXxx
驅動注解都是以這樣的方式來實現的
那么關於 @Import
注解的實現原理我在很多地方都提到過,這里再提一下,模塊驅動注解通常需要結合 @Configuration
注解一起使用,因為需要先被當做一個配置類,然后解析到上面有 @Import
注解后則進行處理,對於 @Import
注解的值有三種情況:
-
該 Class 對象實現了
ImportSelector
接口,調用它的selectImports(..)
方法獲取需要被處理的 Class 對象的名稱,也就是可以將它們作為一個 Bean 被 Spring IoC 管理- 該 Class 對象實現了
DeferredImportSelector
接口,和上者的執行時機不同,在所有配置類處理完后再執行,且支持@Order
排序
- 該 Class 對象實現了
-
該 Class 對象實現了
ImportBeanDefinitionRegistrar
接口,會調用它的registerBeanDefinitions(..)
方法,自定義地往 BeanDefinitionRegistry 注冊中心注冊 BeanDefinition(Bean 的前身) -
該 Class 對象是一個
@Configuration
配置類,會將這個類作為一個 Bean 被 Spring IoC 管理
對於 @Import
注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的實現原理》 這篇文章
這里的 @EnableConfigurationProperties
注解,通過 @Import
導入 EnableConfigurationPropertiesRegistrar 這個類(實現了 ImportBeanDefinitionRegistrar
接口)來實現該功能的,下面會進行分析
EnableConfigurationPropertiesRegistrar
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar
,實現了 ImportBeanDefinitionRegistrar
接口,是 @EnableConfigurationProperties
注解的核心類
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// <1> 先注冊兩個內部 Bean
registerInfrastructureBeans(registry);
// <2> 創建一個 ConfigurationPropertiesBeanRegistrar 對象
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
// <3> 獲取 `@EnableConfigurationProperties` 注解指定的 Class 類對象們
// <4> 依次注冊指定的 Class 類對應的 BeanDefinition
// 這樣一來這個 Class 不用標注 `@Component` 就可以注入這個配置屬性對象了
getTypes(metadata).forEach(beanRegistrar::register);
}
private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
return metadata.getAnnotations().stream(EnableConfigurationProperties.class)
.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
.filter((type) -> void.class != type).collect(Collectors.toSet());
}
/**
* 可參考 ConfigurationPropertiesAutoConfiguration 自動配置類
*/
@SuppressWarnings("deprecation")
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
// 注冊一個 ConfigurationPropertiesBindingPostProcessor 類型的 BeanDefinition(內部角色),如果不存在的話
// 同時也會注冊 ConfigurationPropertiesBinder 和 ConfigurationPropertiesBinder.Factory 兩個 Bean,如果不存在的話
ConfigurationPropertiesBindingPostProcessor.register(registry);
// 注冊一個 ConfigurationBeanFactoryMetadata 類型的 BeanDefinition(內部角色)
// 這個 Bean 從 Spring 2.2.0 開始就被廢棄了
ConfigurationBeanFactoryMetadata.register(registry);
}
}
注冊 BeanDefinition(Bean 的前身)的過程如下:
-
先注冊兩個內部 Bean
-
注冊一個 ConfigurationPropertiesBindingPostProcessor 類型的 BeanDefinition(內部角色),如果不存在的話
public static void register(BeanDefinitionRegistry registry) { Assert.notNull(registry, "Registry must not be null"); // 注冊 ConfigurationPropertiesBindingPostProcessor 類型的 BeanDefinition(內部角色) if (!registry.containsBeanDefinition(BEAN_NAME)) { GenericBeanDefinition definition = new GenericBeanDefinition(); definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BEAN_NAME, definition); } // 注冊 ConfigurationPropertiesBinder 和 ConfigurationPropertiesBinder.Factory 兩個 BeanDefinition(內部角色) ConfigurationPropertiesBinder.register(registry); }
-
注冊一個 ConfigurationBeanFactoryMetadata 類型的 BeanDefinition(內部角色),從 Spring 2.2.0 開始就被廢棄了,忽略掉
-
-
創建一個
ConfigurationPropertiesBeanRegistrar
對象 -
獲取
@EnableConfigurationProperties
注解指定的 Class 類對象們 -
調用
ConfigurationPropertiesBeanRegistrar
的register(Class<?> type)
方法,依次注冊指定的 Class 類對應的 BeanDefinition,這樣一來這個 Class 不用標注@Component
就可以注入這個配置屬性對象了
ConfigurationPropertiesBeanRegistrar
org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrar
,是 EnableConfigurationPropertiesRegistrar
的輔助類
final class ConfigurationPropertiesBeanRegistrar {
private final BeanDefinitionRegistry registry;
private final BeanFactory beanFactory;
ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
this.registry = registry;
this.beanFactory = (BeanFactory) this.registry;
}
void register(Class<?> type) {
// <1> 先獲取這個 Class 類對象的 `@ConfigurationProperties` 注解
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
// <2> 為這個 Class 對象注冊一個 BeanDefinition
register(type, annotation);
}
}
過程如下:
-
先獲取這個 Class 類對象的
@ConfigurationProperties
注解 -
調用
register(..)
方法,為這個 Class 對象注冊一個 BeanDefinitionvoid register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) { // <1> 生成一個 Bean 的名稱,為 `@ConfigurationProperties` 注解的 `${prefix}-類全面`,或者`類全名` String name = getName(type, annotation); if (!containsBeanDefinition(name)) { // <2> 如果沒有該名稱的 Bean,則注冊一個 `type` 類型的 BeanDefinition registerBeanDefinition(name, type, annotation); } } private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) { String prefix = annotation.isPresent() ? annotation.getString("prefix") : ""; return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName()); }
- 生成一個 Bean 的名稱,為
@ConfigurationProperties
注解的${prefix}-類全面
,或者類全名
- 如果沒有該名稱的 Bean,則注冊一個
type
類型的 BeanDefinition
- 生成一個 Bean 的名稱,為
registerBeanDefinition 方法
注冊帶有 @ConfigurationProperties
注解的 Class 對象
private void registerBeanDefinition(String beanName, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation) {
// 這個 Class 對象必須有 `@ConfigurationProperties` 注解
Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
+ " annotation found on '" + type.getName() + "'.");
// 注冊一個 `beanClass` 為 `type` 的 GenericBeanDefinition
this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
}
private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
if (BindMethod.forType(type) == BindMethod.VALUE_OBJECT) {
return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
}
// 創建一個 GenericBeanDefinition 對象,設置 Class 為 `type`
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
}
邏輯比較簡單,就是將這個 @ConfigurationProperties
注解的 Class 對象生成一個 BeanDefinition 並注冊
ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
,將配置綁定到 @ConfigurationProperties
注解的配置類中
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName();
/**
* The bean name of the configuration properties validator.
* @deprecated since 2.2.0 in favor of
* {@link EnableConfigurationProperties#VALIDATOR_BEAN_NAME}
*/
@Deprecated
public static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME;
/** Spring 應用上下文 */
private ApplicationContext applicationContext;
/** BeanDefinition 注冊中心 */
private BeanDefinitionRegistry registry;
/** 屬性綁定器 */
private ConfigurationPropertiesBinder binder;
/**
* Create a new {@link ConfigurationPropertiesBindingPostProcessor} instance.
* @deprecated since 2.2.0 in favor of
* {@link EnableConfigurationProperties @EnableConfigurationProperties} or
* {@link ConfigurationPropertiesBindingPostProcessor#register(BeanDefinitionRegistry)}
*/
@Deprecated
public ConfigurationPropertiesBindingPostProcessor() {
}
}
setApplicationContext 方法
ApplicationContextAware 的回調
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// Aware 接口回調,獲取 Spring 應用上下文
this.applicationContext = applicationContext;
}
afterPropertiesSet 方法
InitializingBean 初始化方法
/**
* 初始化當前 Bean
*/
@Override
public void afterPropertiesSet() throws Exception {
// We can't use constructor injection of the application context because
// it causes eager factory bean initialization
// 從 Spring 應用上下文獲取 BeanDefinition 注冊中心
this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
// 獲取 ConfigurationPropertiesBinder 這個 Bean,在這個類的 `register` 方法中注冊了哦
this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
}
getOrder 方法
PriorityOrdered 優先級
// 次於最高優先級
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
1. postProcessBeforeInitialization 方法
BeanPostProcessor 的初始化前置操作
/**
* 在 Bean 的初始化前會調用這個方法
* 參考 {@link AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization(Object, String)}
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// <1> 先嘗試根據 Bean 解析出一個 ConfigurationPropertiesBean 對象,包含 `@ConfigurationProperties` 注解信息
// <2> 然后開始獲取指定 `prefix` 前綴的屬性值,設置到這個 Bean 中
bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
// <3> 返回屬性填充后的 Bean
return bean;
}
過程如下:
- 調用
ConfigurationPropertiesBean#get(..)
方法,嘗試根據 Bean 解析出一個 ConfigurationPropertiesBean 對象,包含@ConfigurationProperties
注解信息 - 調用
bind(..)
方法,開始獲取指定prefix
前綴的屬性值,設置到這個 Bean 中 - 返回屬性填充后的 Bean
4. bind 方法
private void bind(ConfigurationPropertiesBean bean) {
// <1> 如果這個 `bean` 為空,或者已經處理過,則直接返回
if (bean == null || hasBoundValueObject(bean.getName())) {
return;
}
// <2> 對 `@ConstructorBinding` 的校驗,如果使用該注解但是沒有找到合適的構造器,那么在這里拋出異常
Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
+ bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
try {
// <3> 通過 Binder 將指定 `prefix` 前綴的屬性值設置到這個 Bean 中,會借助 Conversion 類型轉換器進行類型轉換,過程復雜,沒看懂...
this.binder.bind(bean);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(bean, ex);
}
}
可以看到最后是通過 ConfigurationPropertiesBinder
屬性綁定器來將屬性綁定到 bean
中的
ConfigurationPropertiesBean
org.springframework.boot.context.properties.ConfigurationPropertiesBean
,是 @ConfigurationProperties
注解對應的 Bean 的封裝,用於將對應的屬性值綁定到這個 Bean 中
public final class ConfigurationPropertiesBean {
/**
* Bean 的名稱
*/
private final String name;
/**
* Bean 的實例對象
*/
private final Object instance;
/**
* Bean 的 `@ConfigurationProperties` 注解
*/
private final ConfigurationProperties annotation;
/**
* `@Bean` 對應的方法資源對象,包括實例對象和注解信息
*/
private final Bindable<?> bindTarget;
/**
* `@Bean` 對應的方法
*/
private final BindMethod bindMethod;
private ConfigurationPropertiesBean(String name, Object instance, ConfigurationProperties annotation,
Bindable<?> bindTarget) {
this.name = name;
this.instance = instance;
this.annotation = annotation;
this.bindTarget = bindTarget;
this.bindMethod = BindMethod.forType(bindTarget.getType().resolve());
}
}
參考上面的注釋查看每個屬性的描述
2. get 方法
獲取某個 @ConfigurationProperties
注解對應的 Bean 的 ConfigurationPropertiesBean
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
// <1> 找到這個 `beanName` 對應的工廠方法,例如 `@Bean` 標注的方法就是一個工廠方法,不是 `@Bean` 的話這里為空
Method factoryMethod = findFactoryMethod(applicationContext, beanName);
// <2> 創建一個 ConfigurationPropertiesBean 對象,包含了這個 Bean 的 `@ConfigurationProperties` 注解信息
return create(beanName, bean, bean.getClass(), factoryMethod);
}
過程如下:
- 找到這個
beanName
對應的工廠方法,例如@Bean
標注的方法就是一個工廠方法,不是@Bean
的話這里為空 - 調用
create(..)
方法,創建一個 ConfigurationPropertiesBean 對象,包含了這個 Bean 的@ConfigurationProperties
注解信息
3. create 方法
private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
// <1> 找到這個 Bean 上面的 `@ConfigurationProperties` 注解
// 如果是 `@Bean` 標注的方法 Bean,也會嘗試從所在的 Class 類上面獲取
ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
// <2> 如果沒有配置 `@ConfigurationProperties` 注解,則直接返回 `null`
if (annotation == null) {
return null;
}
// <3> 找到這個 Bean 上面的 `@Validated` 注解
Validated validated = findAnnotation(instance, type, factory, Validated.class);
// <4> 將 `@ConfigurationProperties`、`Validated`注解信息,目標 Bean 以及它的 Class 對象,綁定到一個 Bindable 對象中
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
ResolvableType bindType = (factory != null) ? ResolvableType.forMethodReturnType(factory)
: ResolvableType.forClass(type);
Bindable<Object> bindTarget = Bindable.of(bindType).withAnnotations(annotations);
if (instance != null) {
bindTarget = bindTarget.withExistingValue(instance);
}
// <5> 將 `beanName`、目標 Bean、`ConfigurationProperties` 注解、第 `4` 步的 Bindable 對象封裝到一個 ConfigurationPropertiesBean 對象中
return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
}
過程如下:
- 找到這個 Bean 上面的
@ConfigurationProperties
注解,如果是@Bean
標注的方法 Bean,也會嘗試從所在的 Class 類上面獲取 - 如果沒有配置
@ConfigurationProperties
注解,則直接返回null
- 找到這個 Bean 上面的
@Validated
注解 - 將
@ConfigurationProperties
、Validated
注解信息,目標 Bean 以及它的 Class 對象,綁定到一個 Bindable 對象中 - 將
beanName
、目標 Bean、ConfigurationProperties
注解、第4
步的 Bindable 對象封裝到一個 ConfigurationPropertiesBean 對象中
ConfigurationPropertiesBinder
org.springframework.boot.context.properties.ConfigurationPropertiesBinder
,對 ConfigurationPropertiesBean 進行屬性綁定
5. bind 方法
對 ConfigurationPropertiesBean 進行屬性綁定,如下:
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
// <1> 獲取這個 Bean 的 Bindable 對象(包含了 `@ConfigurationProperties`、`@Validated` 配置信息和這個 Bean)
Bindable<?> target = propertiesBean.asBindTarget();
// <2> 獲取這個 Bean 的 `@ConfigurationProperties` 注解信息
ConfigurationProperties annotation = propertiesBean.getAnnotation();
// <3> 獲取一個 BindHandler 綁定處理器
BindHandler bindHandler = getBindHandler(target, annotation);
// <4> 獲取一個 Binder 對象,包含了 Spring 應用上下文的所有配置信息,占位符處理器,類型轉換器
// <5> 通過這個 Binder 將指定 `prefix` 前綴的屬性值設置到這個 Bean 中,會借助 Conversion 類型轉換器進行類型轉換,過程復雜,沒看懂...
return getBinder().bind(annotation.prefix(), target, bindHandler);
}
過程如下:
-
獲取這個 Bean 的 Bindable 對象(包含了
@ConfigurationProperties
、@Validated
配置信息和這個 Bean) -
獲取這個 Bean 的
@ConfigurationProperties
注解信息 -
獲取一個 BindHandler 綁定處理器
private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperties annotation) { // <1> 獲取幾個 Validator 校驗器 List<Validator> validators = getValidators(target); // <2> 創建一個最頂層的 BindHandler BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler(); // <3> 如果忽略無效的字段(默認為 `false`) if (annotation.ignoreInvalidFields()) { handler = new IgnoreErrorsBindHandler(handler); } // <4> 如果不忽略不知道的字段(默認也不會進入這里) if (!annotation.ignoreUnknownFields()) { UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter(); handler = new NoUnboundElementsBindHandler(handler, filter); } // <5> 如果檢驗器不為空,則將其封裝成 ValidationBindHandler 對象,里面保存了這幾個 Validator if (!validators.isEmpty()) { handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0])); } // <6> 獲取 ConfigurationPropertiesBindHandlerAdvisor 對 `handler` 應用,暫時忽略 for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) { handler = advisor.apply(handler); } // <7> 返回這個 `handler` 配置綁定處理器 return handler; } private List<Validator> getValidators(Bindable<?> target) { List<Validator> validators = new ArrayList<>(3); if (this.configurationPropertiesValidator != null) { validators.add(this.configurationPropertiesValidator); } if (this.jsr303Present && target.getAnnotation(Validated.class) != null) { validators.add(getJsr303Validator()); } if (target.getValue() != null && target.getValue().get() instanceof Validator) { validators.add((Validator) target.getValue().get()); } return validators; }
-
獲取一個 Binder 對象,包含了 Spring 應用上下文的所有配置信息,占位符處理器,類型轉換器
private Binder getBinder() { if (this.binder == null) { this.binder = new Binder(getConfigurationPropertySources(), // Spring 應用的 PropertySource 屬性資源 getPropertySourcesPlaceholdersResolver(), // 占位符處理器 getConversionService(), // 類型轉換器 getPropertyEditorInitializer(), // 屬性編輯器 null, ConfigurationPropertiesBindConstructorProvider.INSTANCE); } return this.binder; }
-
通過這個 Binder 將指定
prefix
前綴的屬性值設置到這個 Bean 中,會借助 ConversionService 類型轉換器進行類型轉換
整個處理過程主要在第 5
步,有點復雜,借助於 Binder 綁定器實現的,這里就不講述了,感興趣的可以去研究研究😄
加餐
我們在編寫 application.yml
文件時,當你輸入一個字母時,IDE 是不是會提示很多選項供你選擇,這個就要歸功於 META-INF/spring-configuration-metadata.json
、META-INF/additional-spring-configuration-metadata.json
兩個文件,在這兩個文件里面可以定義你需要的配置的信息,例如 Spring Boot 提供的:
{
"groups": [
{
"name": "logging",
"type": "org.springframework.boot.context.logging.LoggingApplicationListener"
}
],
"properties": [
{
"name": "logging.config",
"type": "java.lang.String",
"description": "Location of the logging configuration file. For instance, `classpath:logback.xml` for Logback.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
},
{
"name": "spring.application.name",
"type": "java.lang.String",
"description": "Application name.",
"sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer"
},
{
"name": "spring.profiles",
"type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of profile expressions that at least one should match for the document to be included.",
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener"
},
{
"name": "spring.profiles.active",
"type": "java.util.List<java.lang.String>",
"description": "Comma-separated list of active profiles. Can be overridden by a command line switch.",
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener"
}
],
"hints": [
{
"name": "logging.level.values",
"values": [
{
"value": "trace"
},
{
"value": "debug"
},
{
"value": "info"
},
{
"value": "warn"
},
{
"value": "error"
},
{
"value": "fatal"
},
{
"value": "off"
}
],
"providers": [
{
"name": "any"
}
]
}
]
}
上面僅列出了部分內容,可以看到定義了每個配置的名稱、類型、描述和來源,同時可以定義每個配置能夠輸入的值,這樣一來,我們就能夠在 IDE 中快速的輸入需要的配置項。
這個文件是通過 Spring Boot 提供的 spring-boot-configuration-processor
工具模塊生成的,借助於 SPI 機制配置了一個 ConfigurationMetadataAnnotationProcessor
注解處理器,它繼承 javax.annotation.processing.AbstractProcessor
抽象類。也就是說這個處理器在編譯階段,會解析每個 @ConfigurationProperties
注解標注的類,將這些類對應的一些配置項(key)的信息保存在 META-INF/spring-configuration-metadata.json
文件中,例如類型、默認值,來幫助你編寫 application.yml
的時候會有相關提示。
而且,當我們使用 @ConfigurationProperties
注解后,IDE 會提示我們引入這個工具類:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
關於這部分內容可參考 Spring Boot 官方文檔
總結
本文分析了 Spring Boot 中的 @ConfigurationProperties
注解的實現過程,原理就是通過注冊的一個 BeanPostProcessor 會在加載 Spring Bean 初始化的時候進行前置處理,解析出 @ConfigurationProperties
注解相關信息,然后找到對應前綴的屬性值綁定到這個 Bean 中。
使用這個注解有兩種方式:
@ConfigurationProperties
+@Component
注解(一個類)@EnableConfigurationProperties
(某個 Bean)+@ConfigurationProperties
注解(另一個普通類)
關於 @EnableConfigurationProperties
注解的處理過程也比較簡單,通過 @Import
注解的方法,注冊一個 BeanPostProcessor 用於處理 @ConfigurationProperties
注解的 Bean,同時會將指定的帶有 @ConfigurationProperties
注解的 Class 對象注冊到 Spring IoC 容器中,這也就是為什么不用加 @Component
注解的原因
關於上面第一種方式是通過一個 ConfigurationPropertiesAutoConfiguration 自動配置類借助 @EnableConfigurationProperties
注解注冊的這個 BeanPostProcessor 去處理 @ConfigurationProperties
注解的 Bean
學習完 Spring Boot 源碼后,個人覺得是非常有幫助的,讓自己能夠清楚的了解 Sprig Boot 應用的運行原理,在處理問題以及調優等方面會更加輕松。另外,熟悉 Spring Boot 的自動配置功能后,編寫一個 Spring Boot Starter 可以說是輕而易舉。
至此,關於 Spirng 和 Spring Boot 兩個流行的基礎框架的源碼已經全部分析完了,接下來筆者要開始學習其他的東西了,例如 MySQL、Dubbo 和 Spring Cloud,敬請期待吧,加油👨🎓
這里提一句,Apache Dubbo 3.0 正式發布,全面擁抱雲原生,先深入學習一下 Dubbo ~
路漫漫其修遠兮,吾將上下而求索