1. 程式人生 > 程式設計 >Spring Boot註解學習之@SpringBootApplication(二)

Spring Boot註解學習之@SpringBootApplication(二)

@ComponentScan屬性basePackages 與 valuebasePackageClassesincludeFiltersexcludeFilters新增自定義過濾規則@Component@ComponentScanscontext:component-scanSpringBootApplication 註解中4個方法小結參考文獻

@ComponentScan

在講述 @Configuration 啟動容器+@Component 註冊 Bean 小節中簡單介紹了@ComponentScan 註解的使用。

@ComponentScan 的功能其實就是自動掃描並載入符合條件的元件或 bean 定義,最終將這些 bean 定義載入到容器中。我們可以通過 backPackages 等屬性指定@ComponentScan 自動掃描的範圍,如果不指定,則預設 Spring 框架實現從宣告@ComponentScan 所在類的 package 進行掃描,預設情況下是不指定的,所以 SpringBoot 的啟動類最好在 root package 下。

首先看下@ComponentScan的原始碼:

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

    "value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default
 {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern
() default "**/*.class";

    boolean useDefaultFilters() default true;

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}
複製程式碼

重點有以下幾個屬性:

  • basePackages 與 value:用於指定包的路徑進行掃描;
  • basePackageClasses:用於指定某個類的包的路徑進行掃描;
  • nameGenerator:bean 的名稱的生成器;
  • useDefaultFilters:是否開啟對@Component,@Repository,@Service,@Controller 的類進行檢測,預設為 true;
  • includeFilters:包含的過濾條件 FilterType.ANNOTATION:按照註解過濾; FilterType.ASSIGNABLE_TYPE:按照給定的型別;FilterType.ASPECTJ:使用 ASPECTJ 表示式;FilterType.REGEX:正則;FilterType.CUSTOM:自定義規則 。
  • excludeFilters:排除的過濾條件,用法和 includeFilters 一樣。

屬性

basePackages 與 value
@ComponentScan(basePackages = “”)     //單個
@ComponentScan(basePackages = {“com.example.dao”,“aaa”,“…”})   //多個
//value 同理
複製程式碼

注意:可以省略 “ basePackages = ”。

@Configuration
@ComponentScan("com.example.dao")
public class MyConfig {}

"com.example.dao","com.example.service")
"com.example.*")   //萬用字元匹配所有的包
MyConfig {}
複製程式碼
basePackageClasses
@ComponentScan(basePackageClasses = “”)   @ComponentScan(basePackageClasses = {“HelloController.class”,“bbb”,“…”})  //多個
複製程式碼

注意:不可以省略“basePackageClasses =”

@ComponentScan(basePackageClasses = HelloController.class)
MyConfig {
}
複製程式碼
includeFilters

接下來我們用以下程式碼進行測試。

Repository 的註解類:

package com.example.dao;
@Repository
BusinessDAO {

    public void update(){
        System.out.println("呼叫了 dao 層的 update 方法....");
    }
}
複製程式碼

Service 的註解類:

package com.example.service;
@Service
BusinessService {
    @Autowired
    private BusinessDAO businessDAO;

    service(){
        System.out.println("呼叫了 service 層的 service() 方法 .....");
        businessDAO.update();
    }
}
複製程式碼

Controller 的註解類:

package com.example.controller;
@Controller
BusinessController {

    private BusinessService service;

    request() {
        System.out.println(" 呼叫了 Controller 的 request() 方法...");
        service.service();
    }
}
複製程式碼

Configuration 配置類:

package com.example.configuration;
//@ComponentScan(basePackages = {"com.example.dao","com.example.service","com.example.controller"})
@ComponentScan(value = {"com.example.service",159); word-wrap: inherit !important; word-break: inherit !important;" class="hljs-string">"com.example.controller"},
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)},
        useDefaultFilters = false)
ScanConfig {

}
複製程式碼

測試類程式碼:

ScanConfigTest {

    static main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class);
        String[] definitionNames = context.getBeanDefinitionNames();
        for(String name:definitionNames){
            System.out.println(name);
        }
    }
}
複製程式碼

執行結果為:

19:22:48.157 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig'
19:22:48.162 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO'
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
scanConfig
businessDAO
複製程式碼

根據結果分析: 除了 spring 本身註冊的一些 bean 之外,可以看到最後一行,已經將 ScanConfig 這個類和 BusinessDAO 註冊進容器中了。

includeFilters 的引數是一個 Filter[] 陣列,然後指定 FilterType 的型別為 ANNOTATION,也就是通過註解來過濾,最後的 value 則是 Repository 註解類。配置之後,在 spring 掃描的時候,就會篩選 com.example 下的三個包中,所有被 @Repository 註解標註的類。

注意:需要設定 useDefaultFilters 為 false,否則@ComponentScan 註解會將 被 @Component、@Repository、@Service 和 @Controller 標註的類都註冊進容器中。同時因為我們在 Controller 和 Service 註解的類中關聯了其他的類(@Autowired),所以最好在 includeFilters 的 Filter 屬性中不要設定為 Controller.class 或 Service.class。

excludeFilters

修改配置類:

ScanConfig {

}
複製程式碼

同 excludeFilters 屬性類似, 配置之後,在 spring 掃描的時候,就會跳過 com.example 下的三個包中,所有被 @Controller 註解標註的類。

執行結果為:

19:44:39.191 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig'
19:44:39.195 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO'
19:44:39.195 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessService'
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
scanConfig
businessDAO
businessService
複製程式碼
新增自定義過濾規則

在前面使用過 @Filter 註解,裡面的 type 屬性是一個 FilterType 的列舉型別:

enum FilterType {

    ANNOTATION,
    ASSIGNABLE_TYPE,
    ASPECTJ,
    REGEX,
    CUSTOM
}
複製程式碼

使用 CUSTOM 型別,就可以實現自定義過濾規則。

package com.example.filter;
CustomTypeFilter implements TypeFilter {
    @Override
    boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 獲取當前掃描到的類的註解元資料
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 獲取當前掃描到的類的元資料
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 獲取當前掃描到的類的資源資訊
        Resource resource = metadataReader.getResource();

        if (classMetadata.getClassName().contains("Business")){
            return true;
        }
        false;
    }
}
複製程式碼

增加 Service 註解修飾的類:

UserService {
}
複製程式碼

修改配置類:

@ComponentScan.Filter(type = FilterType.CUSTOM,classes = CustomTypeFilter.class)})
ScanConfig {

}
複製程式碼

執行測試類程式碼,結果為:

19:56:25.419 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig'
19:56:25.424 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userService'
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
scanConfig
userService
複製程式碼

這裡簡單對掃描到的類名進行判斷,如果類名包含”Business“的就符合條件,就會被剔除,不會注入到容器中。

@Component

1、不指定 bean 的名稱,預設為類名首字母小寫 university

@Component
BeanWithComponent {

    sayHello(){

        System.out.println("BeanWithComponent sayHello...");
    }

    start(){
        System.out.println("BeanWithComponent 初始化。。。");
    }

    cleanUp(){
        System.out.println("BeanWithComponent 銷燬。。。");
    }
}
複製程式碼

獲取 bean 方式:

@Autowired
BeanWithComponent beanWithComponent;
複製程式碼

ApplicationContext context = new AnnotationConfigApplicationContext(ComfigureWithScan.class);
BeanWithComponent bean = (BeanWithComponent) context.getBean("beanWithComponent");
複製程式碼

2、指定 bean 的名稱

@Component("bean2")
BeanWithComponent {

}
複製程式碼

獲取 bean 方式:

@Autowired
BeanWithComponent bean2;
複製程式碼

"bean2");
複製程式碼

總結

@ComponentScan 註解有以下特性:

  • 自定義掃描路徑下邊帶有@Controller@Service@Repository@Component 註解加入 spring 容器

  • 通過 includeFilters 加入掃描路徑下沒有以上註解的類加入 spring 容器

  • 通過 excludeFilters 過濾出不用加入 spring 容器的類

  • 自定義增加了@Component 註解的註解方式

接下來講述一下關於@ComponentScan 註解的兩個擴充套件。

@ComponentScans

原始碼如下:

@Documented
@interface ComponentScans {
    ComponentScan[] value();
}
複製程式碼

配置類:

@ComponentScans(value = "com.example.dao"))
ScanConfig {

}
複製程式碼

執行結果為:

20:07:45.908 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'scanConfig'
20:07:45.914 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO'
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
scanConfig
businessDAO
複製程式碼

context:component-scan

上述程式碼我們是通過@Configuration + @ComponentScan 註解來實現 spring 載入,當然也可以在配置檔案中加上掃描的配置。

spring-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>

    <!--<context:component-scan base-package="com.example.dao" annotation-config="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>-->

    <context:component-scan base-package="com.example.dao" />
    <context:component-scan base-package="com.example.controller" />
    <context:component-scan base-package="com.example.service" />
</beans>
複製程式碼

修改測試類程式碼:

main(String[] args) {
        ApplicationContext  context = new ClassPathXmlApplicationContext("spring-context.xml");
        String[] definitionNames = context.getBeanDefinitionNames();
        for(String name:definitionNames){
            System.out.println(name);
        }
    }
}
複製程式碼

執行得到以下結果:

14:59:12.773 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessDAO'
14:59:12.778 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessController'
14:59:12.789 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'businessService'
14:59:12.789 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'userService'
businessDAO
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
businessController
businessService
userService
複製程式碼

關於 的使用,關於屬性的詳細講解可以參考:詳解

注意: 中有一個 annotation-config 屬性,該屬性主要是隱式地向 Spring 容器註冊 internalConfigurationAnnotationProcessorinternalAutowiredAnnotationProcessorinternalCommonAnnotationProcessorinternalEventListenerProcessor 以及 internalEventListenerFactory 這五個 bean 類。

當 annotation-config 屬性值設為 false 時,即不會註冊這五個類。但是@ComponentScan 註解沒有該屬性。 這是因為在幾乎所有情況下,使用@ComponentScan 時,都假定使用預設的註釋配置處理(例如,處理@Autowired 和 friends )。 此外,在使用AnnotationConfigApplicationContext 時,註釋配置處理器始終會被註冊,這意味著在@ComponentScan 級別禁用它們的任何嘗試都將被忽略。

SpringBootApplication 註解中4個方法

@SpringBootApplication不僅包括上面的三個重要註解,還包含有4個方法:

  • Class[] exclude() default {}; 根據 Class 來排除特定的類加入 Spring 容器,傳入引數是 class 型別;
  • String[] excludeName() default {}; 根據 Class Name 排除特定的類加入 Spring 容器,傳入引數是 class 的全類名字字串陣列;
  • String[] scanBasePackages() default {}; 指定掃描包,引數是包名的字串陣列;
  • Class[] scanBasePackageClasses() default {}; 指定掃描包,引數是 Class 型別陣列。

小結

這裡總結下@SpringBootApplication 中的三個重要註解的特徵:

  • @Configuration

定義 Spring Ioc 容器的配置類。

  • @EnableAutoConfiguration

從 classpath 中搜尋所有 META/spring.factories 配置檔案,並將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration 對應的配置項,也就是一個自動配置類列表載入到 Ioc 容器中。 簡單說,就是@EnawebleAutoConfiguration 讓 Spring Boot 根據類路徑下的 jar 包依賴為當前專案進行自動配置,例如,添加了 spring-boot-starter-web 依賴,會自動新增 Tomcat 和 Spring MVC 的依賴。而對於所有標註@Configuration 的配置類,統一使用ConfigurationClassParser解析的。

  • @ComponentScan

自動掃描並載入符合條件的元件或者 bean 定義。

參考文獻

spring4.0之二:@Configuration的使用

springboot系列文章之SpringBootApplication註解