SpringBoot之@EnableAutoConfiguration註解
首先Spring Boot專案中都會如下啟動類:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
從上面程式碼可以看出,註解@SpringBootApplication
和SpringApplication.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模式執行,可以看到我們的配置:
啟動專案,開啟瀏覽器,執行結果如下: