1. 程式人生 > >SpringBoot之@EnableAutoConfiguration註解

SpringBoot之@EnableAutoConfiguration註解

首先Spring Boot專案中都會如下啟動類:

@SpringBootApplication  
public class Application {  
    public static void main(String[] args) {  
        SpringApplication.run(Application.class, args);  
    }  
} 

從上面程式碼可以看出,註解@SpringBootApplicationSpringApplication.run()方法是最為重要的部分。這裡主要來看看@SpringBootApplication註解部分。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class
} )} ) public @interface SpringBootApplication { ... }

雖然定義使用了多個Annotation進行了原資訊標註,但實際上重要的只有三個Annotation:

  • @Configuration(@SpringBootConfiguration點開檢視發現裡面還是應用了@Configuration)
  • @EnableAutoConfiguration
  • @ComponentScan

如果在啟動類使用這個三個註解,整個SpringBoot應用依然可以與之前的啟動類功能一樣。但每次寫這3個比較囉嗦,所以寫一個@SpringBootApplication方便點。

這三個註解中@Configuration和@ComponentScan對我們來說並不陌生,今天我們的主角是@EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

    String[] excludeName() default {};
}

其中最關鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),藉助EnableAutoConfigurationImportSelector@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都載入到當前SpringBoot建立並使用的IoC容器。

藉助於Spring框架原有的一個工具類:SpringFactoriesLoader的支援,@EnableAutoConfiguration可以智慧的自動配置功效才得以大功告成!

在AutoConfigurationImportSelector類中可以看到通過 SpringFactoriesLoader.loadFactoryNames()
把 spring-boot-autoconfigure.jar/META-INF/spring.factories中每一個xxxAutoConfiguration檔案都載入到容器中,spring.factories檔案裡每一個xxxAutoConfiguration檔案一般都會有下面的條件註解:

  • @ConditionalOnClass : classpath中存在該類時起效
  • @ConditionalOnMissingClass : classpath中不存在該類時起效
  • @ConditionalOnBean : DI容器中存在該型別Bean時起效
  • @ConditionalOnMissingBean : DI容器中不存在該型別Bean時起效
  • @ConditionalOnSingleCandidate : DI容器中該型別Bean只有一個或@Primary的只有一個時起效
  • @ConditionalOnExpression : SpEL表示式結果為true時
  • @ConditionalOnProperty : 引數設定或者值一致時起效
  • @ConditionalOnResource : 指定的檔案存在時起效
  • @ConditionalOnJndi : 指定的JNDI存在時起效
  • @ConditionalOnJava : 指定的Java版本存在時起效
  • @ConditionalOnWebApplication : Web應用環境下起效
  • @ConditionalOnNotWebApplication : 非Web應用環境下起效

SpringFactoriesLoader

SpringFactoriesLoader屬於Spring框架私有的一種擴充套件方案(類似於Java的SPI方案java.util.ServiceLoader),其主要功能就是從指定的配置檔案META-INF/spring-factories載入配置,spring-factories是一個典型的java properties檔案,只不過Key和Value都是Java型別的完整類名,比如:

example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

對於@EnableAutoConfiguration來說,SpringFactoriesLoader的用途稍微不同一些,其本意是為了提供SPI擴充套件的場景,而在@EnableAutoConfiguration場景中,它更多提供了一種配置查詢的功能支援,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfig.EnableAutoConfiguration作為查詢的Key,獲得對應的一組@Configuration類。

SpringFactoriesLoader是一個抽象類,類中定義的靜態屬性定義了其載入資源的路徑public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories",此外還有三個靜態方法:

  • loadFactories:載入指定的factoryClass並進行例項化。
  • loadFactoryNames:載入指定的factoryClass的名稱集合。
  • instantiateFactory:對指定的factoryClass進行例項化。

在loadFactories方法中呼叫了loadFactoryNames以及instantiateFactory方法。

public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
   Assert.notNull(factoryClass, "'factoryClass' must not be null");
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
   if (logger.isTraceEnabled()) {
      logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
   }
   List<T> result = new ArrayList<T>(factoryNames.size());
   for (String factoryName : factoryNames) {
      result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
   }
   AnnotationAwareOrderComparator.sort(result);
   return result;
}

loadFactories方法首先獲取類載入器,然後呼叫loadFactoryNames方法獲取所有的指定資源的名稱集合、接著呼叫instantiateFactory方法例項化這些資源類並將其新增到result集合中。最後呼叫AnnotationAwareOrderComparator.sort方法進行集合的排序。

一個例子

上面介紹了很多原理的知識,下面結合一個例子來加深理解,例子展示的是當專案啟動時如果某個類存在就自動配置這個Bean,並且這個屬性可以在application.properties中配置

新建一個Maven專案,pom.xml檔案如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.chm.test</groupId>
    <artifactId>spring-boot-starter-hello</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-boot-starter-hello</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>1.5.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>1.5.4.RELEASE</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

專案目錄結構如下:
這裡寫圖片描述

Hello.java

public class Hello {

    private String msg;

    public String sayHello() {
        return "hello " + msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

HelloProperties.java

@ConfigurationProperties(prefix = "hello") //獲取屬性值
public class HelloProperties {

    private static final String MSG = "world";

    private String msg = MSG ;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

}

HelloAutoConfiguration.java

@Configuration
//為帶有@ConfigurationProperties註解的Bean提供有效的支援。
// 這個註解可以提供一種方便的方式來將帶有@ConfigurationProperties註解的類注入為Spring容器的Bean。
@EnableConfigurationProperties(HelloProperties.class)//開啟屬性注入,通過@autowired注入
@ConditionalOnClass(Hello.class)//判斷這個類是否在classpath中存在,如果存在,才會例項化一個Bean
// The Hello bean will be created if the hello.enable property exists and has a value other than false
// or the property doesn't exist at all.
@ConditionalOnProperty(prefix="hello", value="enabled", matchIfMissing = true)
public class HelloAutoConfiguration {

    @Autowired
    private HelloProperties helloProperties;

    @Bean
    @ConditionalOnMissingBean(Hello.class)//容器中如果沒有Hello這個類,那麼自動配置這個Hello
    public Hello hello() {
        Hello hello = new Hello();
        hello.setMsg(helloProperties.getMsg());
        return hello;
    }

}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.chm.test.HelloAutoConfiguration

最後使用mvn package將上面專案打包,使用mvn install:install-file命令將打包檔案上傳到本地Maven倉庫進行測試。下面再新建一個Maven專案用於測試。

pom.xml檔案

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.chm.test</groupId>
  <artifactId>test-starter</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>test-starter</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <boot.version>1.5.4.RELEASE</boot.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.chm.test</groupId>
      <artifactId>spring-boot-starter-hello</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>${boot.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <version>${boot.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

App.java

@SpringBootApplication
@RestController
public class App {

    @Autowired
    private Hello hello;

    @RequestMapping("/")
    public String index() {
        return hello.sayHello();
    }

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

application.properties

#可以不配置
hello.enabled=true

hello.msg=charmingfst

#以debug模式執行
debug=true

以debug模式執行,可以看到我們的配置:
這裡寫圖片描述

啟動專案,開啟瀏覽器,執行結果如下:
這裡寫圖片描述