Spring自定義類掃描器

在我們剛開始接觸Spring的時(shí)候锭魔,要定義bean的話需要在xml中編寫例证,比如:

<bean id="myBean" class="your.pkg"/>

后來發(fā)現(xiàn)如果bean比較多赂毯,會(huì)需要寫很多的bean標(biāo)簽战虏,太麻煩了党涕。于是出現(xiàn)了一個(gè)component-scan注解。這個(gè)注解直接指定包名就可以巡社,它會(huì)去掃描這個(gè)包下所有的class膛堤,然后判斷是否解析:

<context:component-scan base-package="your.pkg"/>

再后來,由于注解Annotation的流行晌该,出現(xiàn)了@ComponentScan注解,作用跟component-scan標(biāo)簽一樣朝群,跟@Configuration注解配合使用:

@ComponentScan(basePackages = {"your.pkg", "other.pkg"})
public class Application { ... }

不論是component-scan標(biāo)簽,還是@ComponentScan注解誉帅。它們掃描或解析的bean只能是Spring內(nèi)部所定義的,比如@Component蚜锨、@Service、@Controller或@Repository亚再。如果有一些自定義的注解,比如@Consumer晨抡、這個(gè)注解修飾的類是不會(huì)被掃描到的。這個(gè)時(shí)候我們就得自定義掃描器完成這個(gè)操作如捅。

Spring內(nèi)置的掃描器

component-scan標(biāo)簽底層使用ClassPathBeanDefinitionScanner這個(gè)類完成掃描工作的。@ComponentScan注解配合@Configuration注解使用伪朽,底層使用ComponentScanAnnotationParser解析器完成解析工作。

ComponentScanAnnotationParser解析器內(nèi)部使用了ClassPathBeanDefinitionScanner掃描器烈涮,ClassPathBeanDefinitionScanner掃描器內(nèi)部的處理過程整理如下:

  1. 遍歷basePackages,根據(jù)每個(gè)basePackage找出這個(gè)包下的所有的class坚洽。比如basePackage為your/pkg,會(huì)找出your.pkg包下所有的class讶舰。找出之后封裝成Resource接口集合,這個(gè)Resource接口是Spring對(duì)資源的封裝跳昼,有FileSystemResource、ClassPathResource鹅颊、UrlResource實(shí)現(xiàn)等
  2. 遍歷找到的Resource集合,通過includeFilters和excludeFilters判斷是否解析堪伍。這里的includeFilters和excludeFilters是TypeFilter接口類型的集合,是ClassPathBeanDefinitionScanner內(nèi)部的屬性帝雇。TypeFilter接口是一個(gè)用于判斷類型是否滿足要求的類型過濾器涮俄。excludeFilters中只要有一個(gè)TypeFilter滿足條件,這個(gè)Resource就會(huì)被過濾尸闸。includeFilters中只要有一個(gè)TypeFilter滿足條件彻亲,這個(gè)Resource就不會(huì)被過濾
  3. 如果沒有被過濾。把Resource封裝成ScannedGenericBeanDefinition添加到BeanDefinition結(jié)果集中
  4. 返回最后的BeanDefinition結(jié)果集

TypeFilter接口的定義:

public interface TypeFilter {
    boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException;
}

TypeFilter接口目前有AnnotationTypeFilter實(shí)現(xiàn)類(類是否有注解修飾)室叉、RegexPatternTypeFilter(類名是否滿足正則表達(dá)式)等睹栖。

ClassPathBeanDefinitionScanner繼承ClassPathScanningCandidateComponentProvider類。

ClassPathScanningCandidateComponentProvider內(nèi)部的構(gòu)造函數(shù)提供了一個(gè)useDefaultFilters參數(shù):

public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
  this(useDefaultFilters, new StandardEnvironment());
}

useDefaultFilters這個(gè)參數(shù)表示是否使用默認(rèn)的TypeFilter茧痕,如果設(shè)置為true野来,會(huì)添加默認(rèn)的TypeFilter:

protected void registerDefaultFilters() {
  this.includeFilters.add(new AnnotationTypeFilter(Component.class));
  ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
  try {
    this.includeFilters.add(new AnnotationTypeFilter(
        ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
    logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
  }
  catch (ClassNotFoundException ex) {
    // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
  }
  try {
    this.includeFilters.add(new AnnotationTypeFilter(
        ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
    logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
  }
  catch (ClassNotFoundException ex) {
    // JSR-330 API not available - simply skip.
  }
}

我們看到這里includeFilters加上了AnnotationTypeFilter,并且對(duì)應(yīng)的注解是@Component曼氛。@Service、@Controller或@Repository注解它們內(nèi)部都是被@Component注解所修飾的舀患,所以它們也會(huì)被識(shí)別气破。

自定義掃描功能

一般情況下,我們要自定義掃描功能的話,可以直接使用ClassPathScanningCandidateComponentProvider完成低匙,加上一些自定義的TypeFilter即可∑劭梗或者寫個(gè)自定義掃描器繼承ClassPathScanningCandidateComponentProvider强重,并在內(nèi)部添加自定義的TypeFilter绞呈。后者相當(dāng)于對(duì)前者的封裝间景。

我們就以一個(gè)簡單的例子說明一下自定義掃描的實(shí)現(xiàn),直接使用ClassPathScanningCandidateComponentProvider秉溉。

項(xiàng)目結(jié)構(gòu)如下:

    ./
    └── spring
        └── study
            └── componentprovider
                ├── annotation
                │   └──  Consumer.java
                ├── bean
                │   ├── ConsumerWithComponentAnnotation.java
                │   ├── ConsumerWithConsumerAnnotation.java
                │   ├── ConsumerWithInterface.java
                │   ├── ConsumerWithNothing.java
                │   └── ProducerWithInterface.java
                └── interfaze
                    ├── IConsumer.java
                    └── IProducer.java

我們直接使用ClassPathScanningCandidateComponentProvider掃描spring.study.componentprovider.bean包下的class:

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); // 不使用默認(rèn)的TypeFilter
provider.addIncludeFilter(new AnnotationTypeFilter(Consumer.class));
provider.addIncludeFilter(new AssignableTypeFilter(IConsumer.class));
Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("spring.study.componentprovider.bean");

這里掃描出來的類只有2個(gè)碗誉,分別是ConsumerWithConsumerAnnotation(被@Consumer注解修飾)和ConsumerWithInterface(實(shí)現(xiàn)了IConsumer接口)父晶。ConsumerWithComponentAnnotation使用@Component注解,ConsumerWithNothing沒實(shí)現(xiàn)任何借口甲喝,沒使用任何注解,ProducerWithInterface實(shí)現(xiàn)了IProducer接口埠胖;所以這3個(gè)類不會(huì)被識(shí)別。

如果我們要自定義ComponentProvider非竿,繼承ClassPathScanningCandidateComponentProvider類即可谋竖。

RepositoryComponentProvider這個(gè)類是SpringData模塊提供的红柱,繼承自ClassPathScanningCandidateComponentProvider蓖乘,主要是為了識(shí)別SpringData相關(guān)的類。

它內(nèi)部定義了一些自定義TypeFilter零聚,比如InterfaceTypeFilter(識(shí)別接口的TypeFilter,目標(biāo)比較是個(gè)接口隶症,而不是實(shí)現(xiàn)類)、AllTypeFilter(保存存儲(chǔ)TypeList集合沿腰,這個(gè)集合內(nèi)部所有的TypeFilter必須全部滿足條件才能被識(shí)別)等。

有興趣的讀者可以查看源碼颂龙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末措嵌,一起剝皮案震驚了整個(gè)濱河市躲叼,隨后出現(xiàn)的幾起案子企巢,更是在濱河造成了極大的恐慌,老刑警劉巖浪规,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笋婿,死亡現(xiàn)場(chǎng)離奇詭異誉裆,居然都是意外死亡缸濒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門斩跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捞慌,“玉大人,你說我怎么就攤上這事卿闹。” “怎么了锻霎?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吏口。 經(jīng)常有香客問我,道長产徊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任戈盈,我火速辦了婚禮,結(jié)果婚禮上塘娶,老公的妹妹穿的比我還像新娘痊夭。我一直安慰自己刁岸,他們只是感情好她我,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酝碳,像睡著了一般恨狈。 火紅的嫁衣襯著肌膚如雪击敌。 梳的紋絲不亂的頭發(fā)上拴事,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天圣蝎,我揣著相機(jī)與錄音,去河邊找鬼徘公。 笑死,一個(gè)胖子當(dāng)著我的面吹牛坦袍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捂齐,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼缩抡,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起娩嚼,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤滴肿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后泼差,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡春瞬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年套啤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宽气。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片潜沦。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涝影,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燃逻,我是刑警寧澤臂痕,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站握童,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏澡绩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一溪掀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膨桥,春花似錦、人聲如沸只嚣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至调鲸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間即供,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工逗嫡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留株依,地道東北人驱证。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓恋腕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荠藤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容