Spring 框架中有很多可用的註解,其中有一類註解稱模式註解(Stereotype Annotations),包括 @Component
, @Service
,@Controller
,@Repository
等。只要在相應的類上標註這些註解,就能成為 Spring 中元件(Bean)。
需要配置開啟自動掃描。如在 XML 中配置
` 或使用註解 @ComponentScan。
從最終的效果上來看,@Component
, @Service
,@Controller
,@Repository
起到的作用完全一樣,那為何還需要多個不同的註解?
從官方 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).
不同的模式註解雖然功能相同,但是代表含義卻不同。
標註@Controller
註解,這類元件就可以表示為 WEB 控制層 ,處理各種 HTTP 互動。標註 @Service
可以表示為內部服務層 ,處理內部服務各種邏輯。而 @Repository
可以代表示為資料控制層,代表資料庫增刪改查動作。
這樣一來不同模式註解帶來了不同的含義,清晰將服務進行分層。
除了上面的作用,特定的模式註解,Spring 可能會在未來增加額外的功能語義。如現在 @Repository
註解,可以增加異常的自動轉換功能。
所以,對於分層服務最好使用各自特定語義的模式註解,如 WEB 層就使用 @Controller
註解。
模式註解原理
在 Spring 中任何標註 @Component
的元件都可以成為掃描的候選物件。另外任何使用 @Component
標註的註解,如 @Service
,當其標註元件時,也能被當做掃描的候選物件。。
@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
註解功能一樣,只要在自定義註解上標註 @Component
就可以。
AnnotationMetadata
從上面文件看出只要在類上存在 @Component
註解,即使存在於註解的註解上,Spring 都將能其成為候選元件。
註解上的註解 Spring 將其定義為元註解(meta-annotation),如
@Component
標註在@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 thejava.lang.annotation
package.
那麼對於一個類是否可以成為 Spring 元件,需要判斷這個類是否包含 @Component
註解,或者類上元註解中是否包含 @Component
。
在 Spring 中可以通過 MetadataReader
獲取 ClassMetadata
以及 AnnotationMetadata
,然後獲取相應後設資料。
ClassMetadata
可以獲取類的各種後設資料,比如類名,介面等。
而 AnnotationMetadata
可以獲取當前類上註解的後設資料,如註解名字,以及元註解資訊等。
所以只要獲取到 AnnotationMetadata,就可以判斷是否存在 @Component
。判斷方式如下
獲取 AnnotationMetadata
這裡我們從 XML 配置開啟掃描開始講起。
<context:component-scan base-package="xxx.xxx.xx"/>
首先在 META-INF 下查詢 spring.handles 檔案。
不明白小夥伴們可以檢視上一篇文章 緣起 Dubbo ,講講 Spring XML Schema 擴充套件機制
context 標籤在 ContextNamespaceHandler
註冊 XML 解析器。在 ContextNamespaceHandler
中其使用了 ComponentScanBeanDefinitionParser
真正解析 XML。
在 ComponentScanBeanDefinitionParser#parse
方法中,首先獲取 XML 中配置 base-package
屬性,獲取掃描的範圍,然後呼叫 ClassPathBeanDefinitionScanner#doScan
獲取 base-package
所有 BeanDefinition
。
在 doScan
方法中最終會呼叫ClassPathScanningCandidateComponentProvider#scanCandidateComponents
獲取掃描範圍內所有 BeanDefinition
在 scanCandidateComponents 中首先獲取掃描包範圍內資源物件,然後迭代從可讀取資源物件中
MetadataReaderFactory#getMetadataReader(resource)獲取
MetadataReader` 物件。
上文已經講到 MetadataReader
物件作用,這裡檢視如何使用MetadataReader
進行判斷。
篩選元件
在 isCandidateComponent
方法中將會傳入 MetadataReader
到TypeFilter#match
進行判斷。
條件的判斷主要使用 excludeFilters
與 includeFilters
兩個欄位決定。那兩個欄位從何處生成?
原來在ComponentScanBeanDefinitionParser
中呼叫 ClassPathBeanDefinitionScanner
構造方法時,預設傳入 useDefaultFilters=true
。
在 registerDefaultFilters
註冊預設的過濾器,生成 excludeFilters
與 includeFilters
初始值。
預設情況下,excludeFilters
將會是個空集,而 includeFilters
集合中增加一個包含@Component
型別資訊的 AnnotationTypeFilter
例項,以及另外兩個包含 Java EE 註解AnnotationTypeFilter
例項。
跳到 AnnotationTypeFilter#match
方法中。AnnotationTypeFilter 類圖如下。
AnnotationTypeFilter#match
方法在抽象類 AbstractTypeHierarchyTraversingFilter
中實現。
match
方法首先呼叫了 matchSelf
,而該方法最終由 AnnotationTypeFilter 重寫。
可以看到這裡最終使用 AnnotationMetadata
方法判斷是否存在指定註解。
原始碼分析就到此為止,下篇文章將會深入 AnnotationMetadata
,檢視其實如何獲取後設資料的。
幫助文件
Spring Annotation Programming Model
beans-stereotype-annotations
『Spring Boot 程式設計思想』