@ConfigurationProperties注解原理與實(shí)踐方式
一、@ConfigurationProperties 基本使用
在 SpringBoot 中,當(dāng)想需要獲取到配置文件數(shù)據(jù)時(shí),除了可以用 Spring 自帶的 @Value 注解外,SpringBoot 還提供了一種更加方便的方式:@ConfigurationProperties。只要在 Bean 上添加上了這個(gè)注解,指定好配置文件的前綴,那么對(duì)應(yīng)的配置文件數(shù)據(jù)就會(huì)自動(dòng)填充到 Bean 中。
比如在application.properties文件中有如下配置文件
config.username=jay.zhou config.password=3333
那么按照如下注解配置,SpringBoot項(xiàng)目中使用@ConfigurationProperties的Bean,它的username與password就會(huì)被自動(dòng)注入值了。
就像下面展示的那樣
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "config")
public class TestBean{
private String username;
private String password;
}二、@ConfigurationProperties 源碼的探索及啟發(fā)
SpringBoot為什么能夠獲取到它的值呢?如果讓我從無到有設(shè)計(jì),我應(yīng)該怎么設(shè)計(jì)呢?
首先通過源碼發(fā)現(xiàn),SpringBoot主要幫助我們做了兩件事情。
- 第一件事情就是獲取到使用@ConfigurationProperties的類。
- 第二件事就是解析配置文件,并把對(duì)應(yīng)的值設(shè)置到我們的Bean中。
按照源碼提供的實(shí)現(xiàn)思路,其核心就是對(duì)Bean的聲明周期的管理。主要涉及一個(gè)叫做 BeanPostProcessor 的接口,可以在Bean初始化的時(shí)候,我們做一些文章。
下面的兩個(gè)方法,很簡(jiǎn)單,大致意思就是,在Spring的Bean初始化之前與之后執(zhí)行。
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}好了,作為程序員,看了半天源碼,扯了半天淡,不如自己實(shí)現(xiàn)一個(gè)類似的效果來的實(shí)在。
三、代碼
第一步,定義自己的注解。這個(gè)注解最后實(shí)現(xiàn)的功能希望與@ConfigurationProperties 類似。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
/**
* 配置屬性 的前綴
*/
String prefix();
}第二步,定義配置測(cè)試實(shí)體。
用上了我們剛才定義的@Config注解,并指明其prefix屬性的值為 " default "
import lombok.Data;
import org.springframework.stereotype.Component;
@Config(prefix = "default")
@Component
@Data
public class TestDataSource {
private String username;
private String password;
private int maxActiveCount;
}第三步,編寫我們的自己的配置文件。為了便于操作(實(shí)際上其它的我不會(huì)解析),使用properties這種文件類型。
我們的目標(biāo)就是把 " default." 后面的對(duì)應(yīng)的字段,注入到上面的TestDataSource里面。
config.properties
default.username = root default.password = 3333 default.maxActiveCount = 10 default.maxActiveCount2 = 10
第四步,編寫處理邏輯。
(1)獲取使用了我們自定義注解@Config的類
(2)解析配置文件,并將對(duì)應(yīng)的值使用反射技術(shù),注入到Bean中。
下面是最主要的處理代碼。實(shí)現(xiàn)BeanPostProcessor接口的方法,檢查每一個(gè)初始化成功的Bean,如果使用了我們的自定義注解,那么就把從配置文件中解析出來的數(shù)據(jù),使用反射技術(shù)注入進(jìn)去。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
@Component
public class ConfigPostProcess implements BeanPostProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigPostProcess.class);
private static final String FILE_NAME = "config.properties";
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//獲取使用了我們自定義注解@Config的類
Config configAnnotation = AnnotationUtils.findAnnotation(bean.getClass(), Config.class);
//如果這個(gè)對(duì)象使用了此注解
if (configAnnotation != null) {
LOGGER.info("當(dāng)前操作的類:{}", beanName);
//解析配置文件,并將解析結(jié)果放入Map中
Map<String, String> configProperties = getConfigPropertiesFromFile(configAnnotation);
//將對(duì)應(yīng)的值,使用反射技術(shù),注入到這個(gè)bean中
bindBeanValue(bean, configProperties);
}
return bean;
}
...
}四、實(shí)現(xiàn)效果
SpringBoot很多都是通過注解來實(shí)現(xiàn)功能的。只要我們能夠?qū)W習(xí)其源碼實(shí)現(xiàn)思路,我們也可以做出很多很多類似的功能。盡管我們的代碼健壯性做不到跟大佬一樣,但是在特定業(yè)務(wù)場(chǎng)景下,比如對(duì)某個(gè)特別重要的單例Bean進(jìn)行操作,或者為某一類特定的接口實(shí)現(xiàn),做一些特定的處理,可以考慮這種技術(shù)。
另外,對(duì)于反射的使用,在SpringBoot框架學(xué)習(xí)過程中也是一個(gè)非常重要的部分。
只有經(jīng)常手敲代碼,才能孰能生巧,切忌紙上談兵。只有切實(shí)自己實(shí)現(xiàn)了特定功能,回頭查看SpringBoot替我們做的事情,才能做到心中有數(shù),否則被新手程序員問一句 " 為什么要這么配置 " 就啞口無言,實(shí)在是很尷尬。
點(diǎn)擊動(dòng)圖后,效果可以更加清楚哦?。。?/span>

附錄私有方法
/**
* 將對(duì)應(yīng)的值,使用反射技術(shù),注入到這個(gè)bean中
*/
private void bindBeanValue(Object bean, Map<String, String> configProperties) {
if (configProperties.size() > 0) {
configProperties.forEach((key, value) -> {
setFieldValueByFieldName(key, bean, value);
});
}
}
/**
* 從配置文件中讀取配置好的鍵值對(duì),并放入到Map中
*/
private Map<String, String> getConfigPropertiesFromFile(Config configAnnotation) {
//get prefix from annotation
String prefix = configAnnotation.prefix();
//read value from resource file
Properties properties = getClassNameFromResource(FILE_NAME);
Map<String, String> configProperties = new HashMap<>();
Set<String> keys = properties.stringPropertyNames();
List<String> keyList = new ArrayList<>(keys);
for (String key : keyList) {
if (key.startsWith(prefix)) {
//default.password ==> password
String realKey = key.substring(key.indexOf(prefix) + prefix.length() + 1);
String value = properties.getProperty(key);
configProperties.put(realKey, value);
}
}
return configProperties;
}
/**
* 讀取配置文件,返回一個(gè)流對(duì)象
*/
private Properties getClassNameFromResource(String fileName) {
Properties properties = new Properties();
ClassLoader classLoader = ConfigPostProcess.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream(fileName);
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}
/**
* 為指定字段使用反射技術(shù)設(shè)值(只支持String 與 int 類型)
*/
private void setFieldValueByFieldName(String fieldName, Object object, String value) {
try {
Class c = object.getClass();
if (checkFieldExists(fieldName, c)) {
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
//如果不是String,那么就是int。其它類型不支持
if(f.getType().equals(String.class)){
f.set(object, value);
}else{
int number = Integer.valueOf(value);
f.set(object, number);
}
}
} catch (Exception e) {
LOGGER.error("設(shè)置" + fieldName + "出錯(cuò)");
}
}
/**
* 檢查這個(gè)Bean是否有配置文件中配置的字段
* 沒有就不設(shè)置了
*/
private boolean checkFieldExists(String fieldName, Class c) {
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals(fieldName)) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java Swing實(shí)現(xiàn)選項(xiàng)卡功能(JTabbedPane)實(shí)例代碼
這篇文章主要介紹了java Swing實(shí)現(xiàn)選項(xiàng)卡功能(JTabbedPane)實(shí)例代碼的相關(guān)資料,學(xué)習(xí)java 基礎(chǔ)的朋友可以參考下這個(gè)簡(jiǎn)單示例,需要的朋友可以參考下2016-11-11
Springboot如何優(yōu)雅的關(guān)閉應(yīng)用
這篇文章主要介紹了Springboot如何優(yōu)雅的關(guān)閉應(yīng)用問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Spring Cloud微服務(wù)之間調(diào)用Dubbo的詳細(xì)過程
SpringCloudAlibabaDubbo是阿里巴巴開源的Dubbo與SpringCloud的深度集成方案,本文介紹Spring Cloud微服務(wù)之間調(diào)用Dubbo的詳細(xì)過程,感興趣的朋友跟隨小編一起看看吧2026-02-02
解決idea services窗口不見的一種特殊情況(小白采坑系列)
這篇文章主要介紹了解決idea services窗口不見的一種特殊情況(小白采坑系列),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09
SpringBoot在RequestBody中使用枚舉參數(shù)案例詳解
這篇文章主要介紹了SpringBoot在RequestBody中使用枚舉參數(shù)案例詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
用Java集合中的Collections.sort方法如何對(duì)list排序(兩種方法)
本文通過兩種方法給大家介紹java集合中的Collections.sort方法對(duì)list排序,第一種方式是list中的對(duì)象實(shí)現(xiàn)Comparable接口,第二種方法是根據(jù)Collections.sort重載方法實(shí)現(xiàn),對(duì)collections.sort方法感興趣的朋友一起學(xué)習(xí)吧2015-10-10

