javascript
2.SpringBoot学习(二)——Spring Boot ConfigurationProperties
1.簡介
1.1 概述
Annotation for externalized configuration. Add this to a class definition or a @Bean method in a @Configuration class if you want to bind and validate some external Properties (e.g. from a .properties file).
Binding is either performed by calling setters on the annotated class or, if @ConstructorBinding is in use, by binding to the constructor parameters.
Note that contrary to @Value, SpEL expressions are not evaluated since property values are externalized.
一個外部化配置的注解。如果您要綁定和驗(yàn)證某些外部屬性(例如,來自.properties文件),則將其添加到類定義或 @Configuration 類中的 @Bean 方法中。
綁定可以通過在帶注釋的類上調(diào)用setter來執(zhí)行,或者,如果正在使用 @ConstructorBinding,則可以通過綁定到構(gòu)造函數(shù)參數(shù)來執(zhí)行。
請注意,與@Value相反,由于屬性值是外部化的,因此不評估SpEL表達(dá)式。
1.2 特點(diǎn)
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConfigurationProperties {String value() default "";String prefix() default "";boolean ignoreInvalidFields() default false;boolean ignoreUnknownFields() default true; }1.3 對比 @Value
| 功能 | 批量注入配置文件中的屬性 | 一個個指定 |
| 松散綁定(松散語法) | 支持 | 不支持 |
| SPEL語法 | 不支持 | 支持 |
| JSR303數(shù)據(jù)校驗(yàn) | 支持 | 不支持 |
| 復(fù)雜類型封裝 | 支持 | 不支持 |
2.環(huán)境
3.代碼
3.1 代碼結(jié)構(gòu)
3.2 maven 依賴
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency> </dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins> </build>3.3 配置文件
application.properties
user.prop.name=zhangsan user.prop.age=203.4 java代碼
UserProperties.java
@Component @Validated // JSR303數(shù)據(jù)校驗(yàn) @ConfigurationProperties(prefix = "user.prop") public class UserProperties {@NotBlankprivate String name;@Range(min = 1, max = 200)private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;} }UserProps.java
@Component public class UserProps {@Value("${user.prop.name}")private String name;// SPEL 表達(dá)式@Value("#{10 * 2}")private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;} }UserController.java
@RestController public class UserController {@Autowiredprivate UserProperties userProperties;@Autowiredprivate UserProps userProps;@GetMapping("/user/get/1")public String getUser1() {return userProperties.getName() + "'s age is " + userProperties.getAge();}@GetMapping("/user/get/2")public String getUser2() {return userProps.getName() + "'s age is " + userProps.getAge();} }3.5 git 地址
spring-boot/spring-boot-02-config
4.結(jié)果
啟動 SpringBoot02ConfigApplication.main 方法,在 spring-boot-02-config.http 訪問如下兩個地址,輸出 “zhangsan’s age is 20” 表示請求成功
5.源碼分析
5.1 @ConfigurationProperties 原理分析
@SpringBootApplication 注解是一個復(fù)合注解,它里面包含一個 @ConfigurationPropertiesScan,這個里面又有一個 @EnableConfigurationProperties,@ConfigurationProperties 的作用與它有關(guān)。
@ConfigurationProperties 中通過 @Import 引入一個 EnableConfigurationPropertiesRegistrar,它里面有一個 registerBeanDefinitions 方法
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {registerInfrastructureBeans(registry);ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);getTypes(metadata).forEach(beanRegistrar::register); }registerBeanDefinitions 調(diào)用一個 registerInfrastructureBeans ,這個方法將 屬性綁定后置處理器、bean 校驗(yàn)器、元數(shù)據(jù)注入到 registry 中,這里的 registry 保存了所有 bean 信息。
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {ConfigurationPropertiesBindingPostProcessor.register(registry);ConfigurationPropertiesBeanDefinitionValidator.register(registry);ConfigurationBeanFactoryMetadata.register(registry); }通過查看類圖可以知道,ConfigurationPropertiesBindingPostProcessor 是 BeanPostProcessor 的一個實(shí)現(xiàn)類
它在 bean 實(shí)例化的時候發(fā)生作用,BeanPostProcessor 提供了 postProcessBeforeInitialization 和
postProcessAfterInitialization 兩個方法
public interface BeanPostProcessor {@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;} }在 ConfigurationPropertiesBindingPostProcessor 的 postProcessBeforeInitialization 方法中提供了對于屬性值的注入
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 屬性綁定bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));return bean; }在 bind 方法中,通過 ConfigurationPropertiesBinder 來綁定 ConfigurationProperties 中屬性
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {Bindable<?> target = propertiesBean.asBindTarget();// 獲取目標(biāo) bean 上的 @ConfigurationProperties 注解ConfigurationProperties annotation = propertiesBean.getAnnotation();// 獲取 BindHandlerBindHandler bindHandler = getBindHandler(target, annotation);// 通過配置的 prefix 和 BindHandler 進(jìn)行屬性綁定return getBinder().bind(annotation.prefix(), target, bindHandler); }到這里已經(jīng)比較清晰了,后面的就是從 應(yīng)用上下文中獲取屬性值,然后轉(zhuǎn)換成對應(yīng)的類型,再將屬性值設(shè)置給目標(biāo)對象。
5.2 @Value 原理分析
這個流程中,doCreateBean 前面的流程實(shí)際上是 spirng bean 的初始化流程,在初始化過程中,會對 bean 的依賴和字段進(jìn)行填充;BeanPostProcessor 也是在這個階段發(fā)生作用
for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;} }使用注解進(jìn)行 bean 注入的時候,會有一個 AutowiredAnnotationBeanPostProcessor 的處理類,它里面有一個 postProcessProperties 方法
@Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs; }InjectionMetadata 是類的注入元數(shù)據(jù),這里通過它來對 bean 中的屬性進(jìn)行注入,它里面提供了多種注入元件,而 ConfigurationProperties 主要通過字段屬性進(jìn)行注入
AutowiredFieldElement 的 inject 方法實(shí)現(xiàn)如下
@Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;// 判斷是否已緩存,如果緩存了,直接獲取if (this.cached) {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {// 如果沒有緩存,需要從 beanFactory 中獲取具體值,然后緩存起來DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();try {value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {if (value != null || this.required) {this.cachedFieldValue = desc;registerDependentBeans(beanName, autowiredBeanNames);if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {// 將獲取到的值緩存起來this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}else {this.cachedFieldValue = null;}// 修改標(biāo)記this.cached = true;}}}if (value != null) {// 最終將獲取到的值,通過反射進(jìn)行注入ReflectionUtils.makeAccessible(field);field.set(bean, value);} }接下來調(diào)用流程是 resolveDependency -> doResolveDependency -> resolveEmbeddedValue
@Override @Nullable public String resolveEmbeddedValue(@Nullable String value) {if (value == null) {return null;}String result = value;for (StringValueResolver resolver : this.embeddedValueResolvers) {result = resolver.resolveStringValue(result);if (result == null) {return null;}}return result; }最后調(diào)用到 PropertyPlaceholderConfigurer,通過解析配置文件獲取到最終值
@Override @Nullable public String resolveStringValue(String strVal) throws BeansException {String resolved = this.helper.replacePlaceholders(strVal, this.resolver);if (trimValues) {resolved = resolved.trim();}return (resolved.equals(nullValue) ? null : resolved); }6.參考
總結(jié)
以上是生活随笔為你收集整理的2.SpringBoot学习(二)——Spring Boot ConfigurationProperties的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机语言:机器语言、汇编语言、高级语言
- 下一篇: 智慧食堂到底如何运营?学校食堂必看