Spring 框架中有很多可用的注解,其中有一類注解稱模式注解(Stereotype Annotations),包括 @Component, @Service,@Controller,@Repository 等。只要在相應的類上標注這些注解,就能成為 Spring 中組件(Bean)。
需要配置開啟自動掃描。如在 XML 中配置 <context:component-scan base-package="xxx.xxx.xx"/>` 或使用注解 @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
@Repositoryannotation 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,當其標注組件時,也能被當做掃描的候選對象。。
@Componentis a generic stereotype for any Spring-managed component. Any component annotated with@Componentis a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with@Componentis also a candidate for component scanning. For example,@Serviceis 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
@Documentedfrom thejava.lang.annotationpackage.
那么對於一個類是否可以成為 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 編程思想』

