《Spring核心技術》第6章:深度解析@PropertySource註解
作者:冰河
星球:
部落格:
文章彙總:/md/all/all.html
原始碼地址:
沉澱,成長,突破,幫助他人,成就自我。
大家好,我是冰河~~
本章難度:★★★☆☆
本章重點:進一步學習並掌握@PropertySource註解載入配置檔案的案例和流程,從原始碼級別徹底掌握@PropertySource註解在Spring底層的執行流程。
本節目錄如下所示:
學習指引 註解說明 註解原始碼 註解使用場景 使用案例 原始碼時序圖 原始碼解析 總結 思考 VIP服務
一、學習指引
@PropertySource註解是用來幹啥的呢?
在日常開發中,你有沒有遇到過這樣一種場景:專案中需要編寫很多配置檔案,將一些系統資訊配置化,此時,往往需要編寫專門的工具類或者方法來讀取並解析這些配置檔案,將配置檔案中的配置項內容載入到系統記憶體中。後續在使用這些配置項時,可以直接透過工具類或者方法獲取載入到記憶體中的配置項。
沒錯,@PropertySource註解就是Spring中提供的一個可以載入配置檔案的註解,並且可以將配置檔案中的內容存放到Spring的環境變數中。
二、註解說明
簡單介紹下@PropertySource註解吧!
@PropertySource註解是Spring中提供的一個透過指定配置檔案位置來載入配置檔案的註解,並且可以將配置檔案中的內容存放到Spring的環境變數中。除了可以透過Spring的環境變數讀取配置項之外,還可以透過@Value註解獲取配置項的值。
另外,Spring中還提供了一個@PropertySources註解,在@PropertySources註解註解中,可以引入多個@PropertySource註解。
2.1 註解原始碼
Spring中提供了@PropertySource和@PropertySources兩個註解來載入配置檔案。
1.@PropertySource註解
@PropertySource註解只能標註到類上,能夠透過指定配置檔案的位置來載入配置檔案,@PropertySource註解除了可以載入properties配置檔案外,也可以載入xml配置檔案和yml配置檔案。如果載入yml配置檔案時,可以自定義PropertySourceFactory實現yml配置檔案的解析操作。
@PropertySource註解的原始碼詳見:org.springframework.context.annotation.PropertySource。
/**
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @author Sam Brannen
* @since 3.1
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
String name() default "";
String[] value();
/**
* @since 4.0
*/
boolean ignoreResourceNotFound() default false;
/**
* @since 4.3
*/
String encoding() default "";
/**
* @since 4.3
*/
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
從原始碼可以看出,@PropertySource註解是從Spring3.1版本開始提供的註解,註解中各個屬性的含義如下所示。
name:表示載入的資源的名稱,如果為空,則會根據載入的配置檔案自動生成一個名稱。 value:表示載入的資源的路徑,這個路徑可以是類路徑,也可以是檔案路徑。 ignoreResourceNotFound:表示當配置檔案未找到時,是否忽略檔案未找到的錯誤。預設值為false,也就是說當未找到配置檔案時,Spring啟動就會報錯。 encoding:表示解析配置檔案使用的字符集編碼。 factory:表示讀取對應配置檔案的工廠類,預設的工廠類是PropertySourceFactory。
2.@PropertySources註解
除了@PropertySource註解,Spring中還提供了一個@PropertySources註解。@PropertySources註解中的原始碼詳見:org.springframework.context.annotation.PropertySources。
/**
* @author Phillip Webb
* @since 4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
PropertySource[] value();
}
從原始碼可以看出,@PropertySources是從Spring4.0版本開始提供的註解,在@PropertySources註解中,只提供了一個PropertySource陣列型別的value屬性。所以,@PropertySources註解可以引入多個@PropertySource註解。
2.2 註解使用場景
在基於Spring的註解開發專案的過程中,由於不再使用Spring的XML檔案進行配置,如果將配置項直接寫到類中,就會造成配置項與類的緊耦合,後續對於配置項的修改操作非常不方便,不利於專案的維護和擴充套件。此時,可以將這些配置項寫到properties檔案或者yml檔案中,透過@PropertySource註解載入配置檔案。
另外,如果專案本身就存在大量的properties配置檔案或者yml配置檔案,也可以統一由Spring的@PropertySource註解進行載入。
三、使用案例
結合案例學著印象才會更深刻~~
本節,主要實現一個透過@PropertySource註解載入properties配置檔案,將properties配置檔案中的配置項載入到Spring的環境變數中,獲取Spring環境變數中配置項的值,並進行列印。案例的具體實現步驟如下所示。
(1)新增test.properties檔案
在spring-annotation-chapter-06工程的resources目錄下新增test.properties檔案,檔案內容如下所示。
name=binghe
age=18
(2)新增PropertySourceConfig類
PropertySourceConfig類的原始碼詳見:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.config.PropertySourceConfig。
@Configuration
@PropertySource(value = "classpath:test.properties")
public class PropertySourceConfig {
}
可以看到,PropertySourceConfig類是Spring的配置類,並且使用@PropertySource註解指定了test.properties配置檔案的路徑。
(3)新增PropertySourceTest類
PropertySourceTest類的原始碼詳見:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.PropertySourceTest。
public class PropertySourceTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertySourceConfig.class);
ConfigurableEnvironment environment = context.getEnvironment();
System.out.println(environment.getProperty("name") + " ====>>> " + environment.getProperty("age"));
}
}
可以看到,在PropertySourceTest類的main()方法中,透過AnnotationConfigApplicationContext類的物件獲取到ConfigurableEnvironment型別的環境變數物件environment,然後透過environment物件獲取配置檔案中的name和age的值並進行列印。
(4)執行PropertySourceTest類
執行PropertySourceTest類的main()方法,輸出的結果資訊如下所示。
binghe ====>>> 18
可以看到,正確的輸出了配置檔案中的值。
說明:使用@PropertySource註解可以載入properties配置檔案中的配置項,並將配置項載入到Spring的環境變數中,透過Spring的環境變數就可以獲取到配置項的值。
四、原始碼時序圖
結合時序圖理解原始碼會事半功倍,你覺得呢?
本節,就以原始碼時序圖的方式,直觀的感受下@PropertySource註解在Spring原始碼層面的執行流程。@PropertySource註解在Spring原始碼層面的執行流程如圖6-1~6-2所示。
圖6-1
圖6-2
由圖6-1~圖6-2可以看出,@PropertySource註解在Spring原始碼層面的執行流程會涉及到PropertySourceTest類、AnnotationConfigApplicationContext類、AbstractApplicationContext類、PostProcessorRegistrationDelegate類、ConfigurationClassPostProcessor類、ConfigurationClassParser類、PropertySourceRegistry類、PropertySourceProcessor類和DefaultPropertySourceFactory類。具體的原始碼執行細節參見原始碼解析部分。
五、原始碼解析
原始碼時序圖整清楚了,那就整原始碼解析唄!
@PropertySource註解在Spring原始碼層面的執行流程,結合原始碼執行的時序圖,會理解的更加深刻。
(1)執行案例程式啟動類
案例程式啟動類原始碼詳見:spring-annotation-chapter-06工程下的io.binghe.spring.annotation.chapter06.PropertySourceTest,執行PropertySourceTest類的main()方法。
在PropertySourceTest類的main()方法中呼叫了AnnotationConfigApplicationContext類的構造方法,並傳入了PropertySourceConfig類的Class物件來建立IOC容器。接下來,會進入AnnotationConfigApplicationContext類的構造方法。
注意:@PropertySource註解在Spring原始碼中的執行流程的(2)~(11)步與第5章的@Import註解相同,這裡不再贅述,直接跳到ConfigurationClassParser類的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate
(2)解析ConfigurationClassParser類的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate
原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
//#############省略其他程式碼################
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.propertySourceRegistry != null) {
this.propertySourceRegistry.processPropertySource(propertySource);
}
}
//#############省略其他程式碼################
}
可以看到,在ConfigurationClassParser類的doProcessConfigurationClass()方法中,遍歷獲取到的@PropertySources註解和@PropertySource註解的屬性,並且呼叫propertySourceRegistry物件的processPropertySource()方法解析註解屬性的值。
(3)解析PropertySourceRegistry類的processPropertySource(AnnotationAttributes propertySource)方法
原始碼詳見:org.springframework.context.annotation.PropertySourceRegistry#processPropertySource(AnnotationAttributes propertySource)。
void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
Class<? extends PropertySourceFactory> factorClassToUse = (factoryClass != PropertySourceFactory.class ? factoryClass : null);
PropertySourceDescriptor descriptor = new PropertySourceDescriptor(Arrays.asList(locations), ignoreResourceNotFound, name, factorClassToUse, encoding);
this.propertySourceProcessor.processPropertySource(descriptor);
this.descriptors.add(descriptor);
}
可以看到,在PropertySourceRegistry類的processPropertySource()方法中,解析@PropertySource註解中的屬性後,將解析出的屬性值封裝到PropertySourceDescriptor物件中,呼叫propertySourceProcessor物件的processPropertySource()方法,並傳入PropertySourceDescriptor物件進行進一步處理。
(4)解析PropertySourceProcessor類的processPropertySource(PropertySourceDescriptor descriptor)方法
原始碼詳見:org.springframework.core.io.support.PropertySourceProcessor#processPropertySource(PropertySourceDescriptor descriptor)。
public void processPropertySource(PropertySourceDescriptor descriptor) throws IOException {
String name = descriptor.name();
String encoding = descriptor.encoding();
List<String> locations = descriptor.locations();
Assert.isTrue(locations.size() > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = descriptor.ignoreResourceNotFound();
PropertySourceFactory factory = (descriptor.propertySourceFactory() != null ? instantiateClass(descriptor.propertySourceFactory()) : DEFAULT_PROPERTY_SOURCE_FACTORY);
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
//#########省略其他程式碼################
}
}
}
可以看到,在processPropertySource()方法中,會透過@PropertySource註解的屬性值解析出配置檔案的內容,並且透過factory物件的createPropertySource()方法來建立PropertySource物件。
(5)解析DefaultPropertySourceFactory類的createPropertySource(String name, EncodedResource resource)方法
原始碼詳見:org.springframework.core.io.support.DefaultPropertySourceFactory#createPropertySource(String name, EncodedResource resource)。
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
createPropertySource()方法的原始碼比較簡單,不再贅述。
(6)回到PropertySourceProcessor類的processPropertySource(PropertySourceDescriptor descriptor)方法
在PropertySourceProcessor類的processPropertySource()方法中,建立完PropertySource物件後,會呼叫addPropertySource()方法將獲取到的屬性值新增到Spring的環境變數中。
(7)解析PropertySourceProcessor類的addPropertySource(PropertySource<?> propertySource)方法
原始碼詳見:org.springframework.core.io.support.PropertySourceProcessor#addPropertySource(PropertySource<?> propertySource)。
private void addPropertySource(org.springframework.core.env.PropertySource<?> propertySource) {
String name = propertySource.getName();
MutablePropertySources propertySources = this.environment.getPropertySources();
if (this.propertySourceNames.contains(name)) {
org.springframework.core.env.PropertySource<?> existing = propertySources.get(name);
if (existing != null) {
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
return;
}
}
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}
可以看到,在PropertySourceProcessor類的addPropertySource()方法中,會將解析出的配置檔案的內容新增到Spring的環境變數中。具體就是在PropertySourceProcessor類的addPropertySource()方法中,獲取到ConfigurableEnvironment中的MutablePropertySources物件,用來儲存解析出的配置檔案中的配置項內容。如果有相同的配置項內容,將existing物件強轉為CompositePropertySource型別,把新舊相同的配置項進行合併,再放到MutablePropertySources物件中。
後續就可以透過Spring的環境變數,來獲取到配置檔案中的配置項內容。
至此,@PropertySource註解在Spring原始碼中的執行流程分析完畢。
六、總結
@PropertySource註解講完了,我們一起總結下吧!
本章,首先介紹了@PropertySource註解的原始碼和使用場景,隨後,簡單給出了一個@PropertySource註解的使用案例。接下來,詳細分析了@PropertySource註解的原始碼時序圖和@PropertySource註解在Spring原始碼層面的執行流程。
七、思考
既然學完了,就開始思考幾個問題吧?
關於@PropertySource註解,通常會有如下幾個經典面試題:
@PropertySource註解的執行流程? @PropertySource註解是如何將配置檔案載入到環境變數的? @PropertySource註解有哪些使用場景? Spring中為何會設計一個@PropertySource註解來載入配置檔案? 從@PropertySource註解的設計中,你得到了哪些啟發?
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2938388/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 《Spring核心技術》第5章:三萬字深度解析@Import註解SpringImport
- Spring註解系列——@PropertySourceSpring
- Spring的@PropertySource註解使用Spring
- 註解@PropertySource的使用
- 註解@PropertySource使用 springbootSpring Boot
- Spring Boot 2.0深度實踐之核心技術篇Spring Boot
- [Spring 深度解析]第5章 Spring之DAOSpring
- Spring Boot核心技術Spring Boot
- Spring5原始碼深度解析(一)之理解Configuration註解Spring原始碼
- #純註解小專案:JdbcTemplate、SpEL@Transactional@PropertySource@EnableTransactionManagement#spring-tx@FDDLCJDBCSpring
- 【spring 註解】第2篇:@ComponentScanSpring
- 【spring 註解】第3篇:@Scope、@Lazy和@Conditional註解Spring
- Spring原始碼深度解析(郝佳)-學習-原始碼解析-基於註解注入(二)Spring原始碼
- @PropertySource 註解實現讀取 yml 檔案
- 智慧雲解析有哪些核心技術?
- 《深度學習Python》核心技術實戰深度學習Python
- 深度學習DeepLearning核心技術實戰深度學習
- 智慧語音技術的深度解析
- 深度強化學習核心技術實戰強化學習
- Spring @Profile註解使用和原始碼解析Spring原始碼
- 譯 - Spring 核心技術之 Spring 容器擴充套件點Spring套件
- 【Spring註解驅動開發】AOP核心類解析,這是最全的一篇了!!Spring
- 搞懂分散式技術14:Spring Boot使用註解整合Redis快取分散式Spring BootRedis快取
- Spring 原始碼(7)Spring的註解是如何解析的?Spring原始碼
- 【Spring註解驅動開發】使用@PropertySource載入配置檔案,我只看這一篇!!Spring
- 深度解讀DBSCAN聚類演算法:技術與實戰全解析聚類演算法
- 深度學習、強化學習核心技術實戰深度學習強化學習
- 《深度學習DeepLearning核心技術實戰培訓班》深度學習
- Spring Boot 最核心的 25 個註解,都是乾貨!Spring Boot
- 深度解析Spring AI:請求與響應機制的核心邏輯SpringAI
- 深度解析混合開發技術成熟度曲線
- 數倉安全:資料脫敏技術深度解析
- JVM CPU Profiler技術原理及原始碼深度解析JVM原始碼
- spring原始碼解析:元註解功能的實現Spring原始碼
- Spring註解Spring
- 【java深入學習第4章】精通 Java 微服務:Spring Boot 與 Spring Cloud 的核心技術與設計準則Java微服務Spring BootCloud
- 深度學習核心技術實踐與圖神經網路新技術應用深度學習神經網路
- Docker映象構建:技術深度解析與實踐指南Docker