Spring 框架中有很多可用的注解,其中有一類注解稱模式注解(Stereotype Annotations),包括 @Component, @Service, @Controller, @Repository 等。只要在相應(yīng)的類上標(biāo)注這些注解,就能成為 Spring 中組件(Bean)。
需要配置開(kāi)啟自動(dòng)掃描。如在 XML 中配置` 或使用注解 @ComponentScan。
從最終的效果上來(lái)看, @Component, @Service, @Controller, @Repository 起到的作用完全一樣,那為何還需要多個(gè)不同的注解?
從官方 wiki 我們可以看到原因。
A?stereotype annotation?is an annotation that is used to declare the role that a component plays within the application. For example, the @Repository annotation in the Spring Framework is a marker for any class that fulfills the role or stereotype of a repository (also known as Data Access Object or DAO).
不同的模式注解雖然功能相同,但是代表含義卻不同。
標(biāo)注 @Controller 注解,這類組件就可以表示為 WEB 控制層 ,處理各種 HTTP 交互。標(biāo)注 @Service 可以表示為內(nèi)部服務(wù)層 ,處理內(nèi)部服務(wù)各種邏輯。而 @Repository 可以代表示為數(shù)據(jù)控制層,代表數(shù)據(jù)庫(kù)增刪改查動(dòng)作。
這樣一來(lái)不同模式注解帶來(lái)了不同的含義,清晰將服務(wù)進(jìn)行分層。
除了上面的作用,特定的模式注解,Spring 可能會(huì)在未來(lái)增加額外的功能語(yǔ)義。如現(xiàn)在 @Repository 注解,可以增加異常的自動(dòng)轉(zhuǎn)換功能。
所以,對(duì)于分層服務(wù)最好使用各自特定語(yǔ)義的模式注解,如 WEB 層就使用 @Controller注解。
模式注解原理
在 Spring 中任何標(biāo)注 @Component 的組件都可以成為掃描的候選對(duì)象。另外任何使用 @Component 標(biāo)注的注解,如 @Service,當(dāng)其標(biāo)注組件時(shí),也能被當(dāng)做掃描的候選對(duì)象。。
@Component is a generic stereotype for any Spring-managed component. Any component annotated with @Component is a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with @Component is also a candidate for component scanning. For example, @Service is meta-annotated with @Component.
如果想使自定義的注解也能如 @Service 注解功能一樣,只要在自定義注解上標(biāo)注 @Component 就可以。
AnnotationMetadata
從上面文檔看出只要在類上存在 @Component注解,即使存在于注解的注解上,Spring 都將能其成為候選組件。
注解上的注解 Spring 將其定義為元注解(meta-annotation),如 @Component標(biāo)注在@Service上, @Component 就被稱作為元注解。后面我們就將注解的注解稱為元注解。
A?meta-annotation?is an annotation that is declared on another annotation. An annotation is therefore meta-annotated if it is annotated with another annotation. For example, any annotation that is declared to be documented is meta-annotated with @Documented from the java.lang.annotation package.
那么對(duì)于一個(gè)類是否可以成為 Spring 組件,需要判斷這個(gè)類是否包含 @Component 注解,或者類上元注解中是否包含 @Component。
在 Spring 中可以通過(guò) MetadataReader 獲取 ClassMetadata 以及 AnnotationMetadata,然后獲取相應(yīng)元數(shù)據(jù)。
ClassMetadata 可以獲取類的各種元數(shù)據(jù),比如類名,接口等。
而 AnnotationMetadata 可以獲取當(dāng)前類上注解的元數(shù)據(jù),如注解名字,以及元注解信息等。
所以只要獲取到 AnnotationMetadata,就可以判斷是否存在 @Component。判斷方式如下
獲取 AnnotationMetadata
這里我們從 XML 配置開(kāi)啟掃描開(kāi)始講起。
<context:component-scanbase-package="xxx.xxx.xx"/>
首先在 META-INF 下查找 spring.handles 文件。
不明白小伙伴們可以查看上一篇文章 緣起 Dubbo ,講講 Spring XML Schema 擴(kuò)展機(jī)制
context 標(biāo)簽在 ContextNamespaceHandler 注冊(cè) XML 解析器。在ContextNamespaceHandler中其使用了 ComponentScanBeanDefinitionParser真正解析 XML。
在 ComponentScanBeanDefinitionParser#parse 方法中,首先獲取 XML 中配置 base-package屬性,獲取掃描的范圍,然后調(diào)用 ClassPathBeanDefinitionScanner#doScan 獲取 base-package 所有 BeanDefinition。
在 doScan 方法中最終會(huì)調(diào)用 ClassPathScanningCandidateComponentProvider#scanCandidateComponents 獲取掃描范圍內(nèi)所有 BeanDefinition
<img src="https://img.hacpai.com/file/2019/06/carbon44-3fe12346.png" alt=" doScan" />
在 scanCandidateComponents中首先獲取掃描包范圍內(nèi)資源對(duì)象,然后迭代從可讀取資源對(duì)象中MetadataReaderFactory#getMetadataReader(resource) 獲取MetadataReader` 對(duì)象。
<img src="https://img.hacpai.com/file/2019/06/carbon45-ad7207f0.png" alt=" scanCandidateComponents" />
上文已經(jīng)講到 MetadataReader 對(duì)象作用,這里查看如何使用 MetadataReader 進(jìn)行判斷。
篩選組件
在 isCandidateComponent方法中將會(huì)傳入 MetadataReader 到 TypeFilter#match進(jìn)行判斷。
<img src="https://img.hacpai.com/file/2019/06/carbon46-3c58666c.png" alt=" isCandidateComponent" />
條件的判斷主要使用 excludeFilters與 includeFilters 兩個(gè)字段決定。那兩個(gè)字段從何處生成?
原來(lái)在 ComponentScanBeanDefinitionParser中調(diào)用 ClassPathBeanDefinitionScanner構(gòu)造方法時(shí),默認(rèn)傳入 useDefaultFilters=true。
在 registerDefaultFilters 注冊(cè)默認(rèn)的過(guò)濾器,生成 excludeFilters與 includeFilters初始值。
<img src="https://img.hacpai.com/file/2019/06/carbon47-e68d111c.png" alt=" registerDefaultFilters" />
默認(rèn)情況下, excludeFilters 將會(huì)是個(gè)空集,而 includeFilters 集合中增加一個(gè)包含 @Component 類型信息的 AnnotationTypeFilter 實(shí)例,以及另外兩個(gè)包含 Java EE 注解 AnnotationTypeFilter 實(shí)例。
跳到 AnnotationTypeFilter#match 方法中。AnnotationTypeFilter 類圖如下。
AnnotationTypeFilter#match 方法在抽象類 AbstractTypeHierarchyTraversingFilter中實(shí)現(xiàn)。
match 方法首先調(diào)用了 matchSelf,而該方法最終由 AnnotationTypeFilter 重寫。
<img src="https://img.hacpai.com/file/2019/06/carbon49-d6c9313a.png" alt=" matchSelf" />
可以看到這里最終使用 AnnotationMetadata 方法判斷是否存在指定注解。
歡迎工作一到五年的Java工程師朋友們加入Java架構(gòu)開(kāi)發(fā):828697593
本群提供免費(fèi)的學(xué)習(xí)指導(dǎo) 架構(gòu)資料 以及免費(fèi)的解答
不懂得問(wèn)題都可以在本群提出來(lái) 之后還會(huì)有職業(yè)生涯規(guī)劃以及面試指導(dǎo)
同時(shí)大家可以多多關(guān)注一下小編 純干貨 大家一起學(xué)習(xí)進(jìn)步