@ComponentScan註解 -【Spring底層原理】

案例已上傳GitHub,歡迎star:github.com/oneStarLR/s…java

1、註解用法

1. 背景知識

什麼是組件?git

組件也是抽象的概念,能夠理解爲一些符合某種規範的類組合在一塊兒就構成了組件,他能夠提供某些特定的功能,但實際他們都是類,只不過有他們特殊的規定。組件和類的關係:符合某種規範的類的組合構成組件。github

2. @ComponentScan註解做用

1. 將組件自動加載到容器

加了包掃描@ComponentScan註解後,只要標註了@Controller、@Service、@Repository、@Component註解中的任何一個,其組件都會被自動掃描,加入到容器中。正則表達式

2. 經過屬性指定掃描

【1】value:指定要掃描的包spring

【2】excludeFilters=Filter[ ]:設置排除的過濾條件,指定掃描的時候按照什麼規則排除哪些組件,不掃描哪些包數組

【3】includeFilters=filter[ ]:設置掃描過濾條件,指定掃描的時候按照什麼規則包含哪些組件,知足該條件才進行掃描markdown

【4】自定義過濾規則:經過實現TypeFilter接口,自定義過濾規則app

下面我們經過實例來分析一下@ComponentScan註解的做用dom

2、實例分析

以maven項目爲例,經過@ComponentScan、@Controller、@Service、@Repository註解獲取容器中的對象:maven

// 啓動類
@Test
public void TestMain(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    String[] beanNames = applicationContext.getBeanDefinitionNames();
    for(String beanName : beanNames){
        System.out.println("容器中對象名稱:"+beanName);
    }
}

// 配置類
@Configuration
@ComponentScan(value = "com.onestar")
public class AppConfig {
}

// 控制層
@Controller
public class UserController {
}

// 業務層
@Service
public class UserService {
}

// 持久層
@Repository
public class UserDao {
}
複製代碼

運行測試類,能夠看到以下打印信息,除了spring啓動時注入到容器的對象外,還有咱們本身加了註解,被spring掃描,加入到容器中

  • @ComponentScan(value = "com.onestar"):value表示指定要掃描的包

image-20210120110132056

點進@ComponentScan註解源碼,咱們能夠看到,使用value能夠指定要掃描的包,咱們還能夠排除要掃描的包,包含要掃描的包,甚至還能夠自定義過濾規則

  • excludeFilters=Filter[ ]:指定掃描的時候按照什麼規則排除哪些包
  • includeFilters=filter[ ]:指定掃描的時候是須要包含哪些包

【@ComponentScan源碼】

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    // 掃描路徑
    @AliasFor("basePackages")
    String[] value() default {};

    // 掃描路徑
    @AliasFor("value")
    String[] basePackages() default {};

    // 指定掃描類
    Class<?>[] basePackageClasses() default {};

    /** * 命名註冊的Bean,能夠自定義實現命名Bean, * 一、@ComponentScan(value = "spring.annotation.componentscan",nameGenerator = MyBeanNameGenerator.class) * MyBeanNameGenerator.class 須要實現 BeanNameGenerator 接口,全部實現BeanNameGenerator 接口的實現類都會被調用 * 二、使用 AnnotationConfigApplicationContext 的 setBeanNameGenerator方法注入一個BeanNameGenerator * BeanNameGenerator beanNameGenerator = (definition,registry)-> String.valueOf(new Random().nextInt(1000)); * AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); * annotationConfigApplicationContext.setBeanNameGenerator(beanNameGenerator); * annotationConfigApplicationContext.register(MainConfig2.class); * annotationConfigApplicationContext.refresh(); * 第一種方式只會重命名@ComponentScan掃描到的註解類 * 第二種只有是初始化的註解類就會被重命名 * 列如第一種方式不會重命名 @Configuration 註解的bean名稱,而第二種就會重命名 @Configuration 註解的Bean名稱 */
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    /** * 用於解析@Scope註解,可經過 AnnotationConfigApplicationContext 的 setScopeMetadataResolver 方法從新設定處理類 * ScopeMetadataResolver scopeMetadataResolver = definition -> new ScopeMetadata(); 這裏只是new了一個對象做爲演示,沒有作實際的邏輯操做 * AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); * annotationConfigApplicationContext.setScopeMetadataResolver(scopeMetadataResolver); * annotationConfigApplicationContext.register(MainConfig2.class); * annotationConfigApplicationContext.refresh(); * 也能夠經過@ComponentScan 的 scopeResolver 屬性設置 *@ComponentScan(value = "spring.annotation.componentscan",scopeResolver = MyAnnotationScopeMetadataResolver.class) */
    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    // 設置類的代理模式
    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    /** * 掃描路徑 如 resourcePattern = "**/*.class" * 使用 includeFilters 和 excludeFilters 會更靈活 */ String resourcePattern() default "**/*.class"; /** * 指示是否應啓用對帶有{@code @Component},{@ code @Repository}, * {@ code @Service}或{@code @Controller}註釋的類的自動檢測。 */
    boolean useDefaultFilters() default true;

    /** * 對被掃描的包或類進行過濾,若符合條件,不論組件上是否有註解,Bean對象都將被建立 * @ComponentScan(value = "com.onestar",includeFilters = { * @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}), * @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {UserDao.class}), * @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}), * @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "spring.annotation..*"), * @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$") * },useDefaultFilters = false) * useDefaultFilters 必須設爲 false */
    ComponentScan.Filter[] includeFilters() default {};

    // 設置排除的過濾條件,指定掃描的時候按照什麼規則排除哪些組件,排除要掃描的包,用法參考includeFilters
    ComponentScan.Filter[] excludeFilters() default {};

    /** * 指定是否應註冊掃描的Bean以進行延遲初始化。 * @ComponentScan(value = "com.onestar",lazyInit = true) */
    boolean lazyInit() default false;

    // @Filter註解,用於 includeFilters 或 excludeFilters 的類型篩選器
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
       /** * 要使用的過濾器類型,默認爲 ANNOTATION 註解類型 * @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}) */
        FilterType type() default FilterType.ANNOTATION;

       /** * 過濾器的參數,參數必須爲class數組,單個參數能夠不加大括號 * 只能用於 ANNOTATION 、ASSIGNABLE_TYPE 、CUSTOM 這三個類型 * @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class, Service.class}) * @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {UserDao.class}) * @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) */
        @AliasFor("classes")
        Class<?>[] value() default {};

       /** * 做用同上面的 value 相同 * ANNOTATION 參數爲註解類,如 Controller.class, Service.class, Repository.class * ASSIGNABLE_TYPE 參數爲類,如 UserDao.class * CUSTOM 參數爲實現 TypeFilter 接口的類 ,如 MyTypeFilter.class * MyTypeFilter 同時還能實現 EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware,ResourceLoaderAware * 這四個接口 * EnvironmentAware * 此方法用來接收 Environment 數據 ,主要爲程序的運行環境,Environment 接口繼承自 PropertyResolver 接口, * 詳細內容在下方 * @Override * public void setEnvironment(Environment environment) { * String property = environment.getProperty("os.name"); * } * * BeanFactoryAware * BeanFactory Bean容器的根接口,用於操做容器,如獲取bean的別名、類型、實例、是否單例的數據 * @Override * public void setBeanFactory(BeanFactory beanFactory) throws BeansException { * Object bean = beanFactory.getBean("BeanName") * } * * BeanClassLoaderAware * ClassLoader 是類加載器,在此方法裏只能獲取資源和設置加載器狀態 * @Override * public void setBeanClassLoader(ClassLoader classLoader) { * ClassLoader parent = classLoader.getParent(); * } * * ResourceLoaderAware * ResourceLoader 用於獲取類加載器和根據路徑獲取資源 * public void setResourceLoader(ResourceLoader resourceLoader) { * ClassLoader classLoader = resourceLoader.getClassLoader(); * } */
        @AliasFor("value")
        Class<?>[] classes() default {};

       /** * 這個參數是 classes 或 value 的替代參數,主要用於 ASPECTJ 類型和 REGEX 類型 * ASPECTJ 爲 ASPECTJ 表達式 * @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "com.onestar..*") * REGEX 參數爲 正則表達式 * @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$") */
        String[] pattern() default {};
    }
}
複製代碼

1. excludeFilters=Filter[ ]

經過excludeFilters=Filter[ ]來排除要掃描的包,在配置類註解中修改:

@Configuration
@ComponentScan(value = "com.onestar",excludeFilters = { @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})})
public class AppConfig {
}
複製代碼
  • type:表示過濾類型,這裏是按照註解進行過濾
  • classes:表示過濾器參數,這裏是過濾掉有@Controller和@Service註解的組件

查看源碼咱們能夠得知,經過實現Filter接口的type屬性用於設置過濾類型,默認值爲FilterType.ANNOTATION,提供了這幾個過濾類型:

  • FilterType.ANNOTATION:按照註解過濾
  • FilterType.ASSIGNABLE_TYPE:按照給定的類型過濾
  • FilterType.ASPECTJ:按照ASPECTJ表達式過濾
  • FilterType.REGEX:按照正則表達式過濾
  • FilterType.CUSTOM:按照自定義規則過濾

classes和value屬性爲過濾器的參數,必須爲class數組,類只能爲如下三種類型:

  • ANNOTATION 參數爲註解類,如 Controller.class, Service.class, Repository.class
  • ASSIGNABLE_TYPE 參數爲類,如 UserDao.class
  • CUSTOM 參數爲實現 TypeFilter 接口的類 ,如 MyTypeFilter.class

經過上面的excludeFilters註解配置,我們再來運行一下測試類,打印信息以下,能夠看到,標有@Controller和@Service註解的組件不被掃描:

image-20210120115812025

2. includeFilters=filter[ ]

經過includeFilters=Filter[ ]只須要包含要掃描的包,在配置類註解中修改,默認的規則是掃描value下全部的包,因此咱們要禁用這個規則:

@Configuration
@ComponentScan(value = "com.onestar",includeFilters = { @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})},useDefaultFilters = false)
public class AppConfig {
}
複製代碼
  • useDefaultFilters = false:禁用默認掃描規則

經過上面的includeFilters註解配置,我們再來運行一下測試類,打印信息以下,能夠看到,標有@Controller和@Service註解的組件才被掃描:

image-20210120133305643

3. FilterType.CUSTOM

上面提到,能夠使用FilterType.CUSTOM按照自定義規則進行過濾,經過源碼,咱們知道,能夠實現TypeFilter的實現類來進行自定義過濾

public class MyTypeFilter implements TypeFilter {
    /** * @description TODO * @author ONESTAR * @date 2021/1/20 14:29 * @param metadataReader:讀取到當前正在掃描的類的信息 * @param metadataReaderFactory:能夠獲取到其餘任何類的信息 * @throws * @return boolean */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 獲取當前類註解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 獲取當前正在掃描的類的信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 獲取當前類資源
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        // 指定包含「serv」的組件進行掃描
        if(className.contains("serv")){
            return true;
        }
        return false;
    }
}
複製代碼

該實現類經過獲取當前全部資源組件,並進行指定掃描,這裏包含「serv」的組件進行掃描,而後修改配置類指定自定義掃描規則:

@Configuration
@ComponentScan(value = "com.onestar",includeFilters = { @Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})},useDefaultFilters = false)
public class AppConfig {
}
複製代碼
  • MyTypeFilter.class:指定自定義過濾參數

運行測試類,能夠看到打印信息,包含「serv」的組件進行掃描:

image-20210120150558862

3、源碼追蹤

一樣,我們以AnnotationConfigApplicationContext類爲入口,點進去跟蹤源碼:

image-20210120153916014

經過refresh()跟蹤Bean 工廠的後置處理器調用:

image-20210120154049710

繼續跟蹤,查看源碼,這裏實例化並調用全部已註冊的BeanFactoryPostProcessor

image-20210120154529999

繼續跟蹤,主方法中有一些加載順序之類的,找到invokeBeanDefinitionRegistryPostProcessors,查看調用Bean的註冊定義的後置處理器的方法,進入源碼查看:

image-20210120154800082

繼續跟蹤,這裏有個循環,會把全部的處理器拿出來進行處理:

image-20210120154908717

繼續跟蹤,能夠看到是一個接口,進入查看實現類,咱們能夠看到一個配置Bean的定義,就是指咱們Configuration配置的裏面的一些Bean的定義:

image-20210120155028837

繼續跟蹤,processConfigBeanDefinitions類中能夠看到Configuration類的解析器,經過parser.parse(candidates);把要掃描的東西進行解析

image-20210120155811376

繼續跟蹤,判斷咱們聲明的是不是一個註解Bean,是的話,再進行解析

image-20210120155951900

繼續跟蹤:

image-20210120160043671

繼續跟蹤,在processConfigurationClass方法中找到和配置相關的方法:

image-20210120160159810

繼續跟蹤,能夠看到和@ComponentScan註解註解相關的東西了,咱們進入componentScanParser.parse看其具體的解析方法:

image-20210120160559967

繼續跟蹤,查看構造方法:

image-20210120160754435

繼續跟蹤,前面咱們講到useDefaultFilters表示默認掃描規則,默認是true:

image-20210120160857561

跟蹤到這就能夠了,就是在這裏將全部標有@Component註解的組件都添加到咱們的includeFiltes裏面去,而@Controller、@Service、@Repository註解都標有@Component註解,因此只要標註了@Controller、@Service、@Repository、@Component註解中的任何一個,其組件都會被自動掃描,加入到容器中

image-20210120161124699

相關文章
相關標籤/搜索