微服務 SpringBoot 2.0(三):啟動剖析之@SpringBootApplication
我原以為就一個註解,背後竟然還有3個 —— Java面試必修
引言
前面兩章我們先後認識了SpringBoot和它的極簡配置,為新手入門的學習降低了門檻,會基本的使用後,接下來我們將進一步認識SpringBoot,它為何能做到服務秒開,就來跟隨我一起分析SpringBoot執行啟動的原理吧。
啟動原理分2章講解,本章講解@SpringBootApplication
註解部分,若需瞭解SpringApplication.run方法部分請點選此處
執行啟動
工具
- SpringBoot版本:2.0.4
- 開發工具:IDEA 2018
- Maven:3.3 9
- JDK:1.8
首先我們看一段啟動程式碼
@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
這不就是個啟動類嘛?從字面理解我都知道Spring啟動的入口,有啥好看的。可別小瞧了這幾行程式碼
開始推理
從上面程式碼來看,@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 { ... }
不要被那麼多元註解宣告所嚇到,仔細檢視可以看出其中有3個重要的註解
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
所以我覺得,這3個才是真正的幕後主使,@SpringBootApplication
只是他們找來擋槍口的,他們合體應該等價於@SpringBootApplication
,如下程式碼正常執行
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
哈哈,經過查詢資料發現,Spring Boot 1.2版之前,真的是由這3個註解出面,之後呢,才隱居幕後,由小嘍嘍在前面擋槍
@SpringBootConfiguration
首先我們來分析這個,點開看到原始碼之後我慌了,這特麼什麼都沒有,唯一的疑點在@Configuration。好吧,還挺會偽裝,我們就分析@Configuration
這個註解吧
- 用之前spring的思維,我推斷這個應該是解決當前Class的XML配置問題,凡是經過該註解修飾的,均被例項化到Spring應用程式上下文中,由spring統一管理生命週期,我說IoC大家應該熟悉了吧。
- 舉個例子傳統的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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" default-lazy-init="true"> <!--bean定義--> <bean id="userService" class="..UserServiceImpl"> ... </bean> </beans>
使用@Configuration
之後的寫法
@Configuration public class SpringConfiguration{ @Bean public UserService userService(){ return new UserServiceImpl(); } }
任何一個標註了@Configuration
的Java類定義都是一個JavaConfig配置類。任何一個標註了@Bean的方法,其返回值將作為一個bean定義註冊到Spring的IoC容器,方法名將預設成該bean定義的id。
- 表達依賴注入關係層面
<bean id="userService" class="..UserServiceImpl"> <propery name ="dependencyService" ref="dependencyService" /> </bean> <bean id="dependencyService" class="DependencyServiceImpl"></bean>
@Configuration public class SpringConfiguration{ @Bean public UserService userService(){ return new UserServiceImpl(dependencyService()); } @Bean public DependencyService dependencyService(){ return new DependencyServiceImpl(); } }
如果一個bean的定義依賴其他bean,則直接呼叫對應的JavaConfig類中依賴bean的建立方法即可,如上方的dependencyService()
。
@ComponentScan
這個註解在Spring中很重要,它對應XML配置中的元素,@ComponentScan
的功能其實就是自動掃描並載入符合條件的元件(比如@Component
和@Repository
等)或者bean定義,最終將這些bean定義載入到IoC容器中。
我們可以通過basePackages
等屬性來細粒度的定製@ComponentScan
自動掃描的範圍,如果不指定,則預設Spring框架實現會從宣告@ComponentScan
所在類的package進行掃描。
以前xml中是這麼定義的,如下
<context:component-scan base-package="com.platform.fox.html.**.controller"/> <context:component-scan base-package="com.platform.fox.html.**.repository"/> <context:component-scan base-package="com.platform.fox.html.**.service"/> <context:component-scan base-package="com.platform.fox.html.**.wshandler"/>
注:所以SpringBoot的啟動類最好是放在root package下,因為預設不指定basePackages
。
@EnableAutoConfiguration
Enable,通常來說我們認為它一定是在開啟或者支援什麼功能,比如:@EnableScheduling
、@EnableCaching
,所以他們要做的事情應該都是相似的,根據原始碼判斷,簡單概括一下就是,藉助@Import的支援,收集和註冊特定場景相關的bean定義。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
我理解就是要開船了,EnableAutoConfigurationImportSelector
根據名單把水手,舵手、安檢員都統一叫過來各就各位。幫助SpringBoot應用將所有符合條件的@Configuration
配置都載入到當前SpringBoot建立並使用的IoC容器。就像一管理員一樣
藉助於Spring框架原有的一個工具類:SpringFactoriesLoader
的支援,@EnableAutoConfiguration
可以智慧的自動配置功效才得以大功告成!
EnableAutoConfigurationImportSelector
中自動配置的關鍵方法如下
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //獲取符合條件的帶有Configuration的註解示例類 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }
SpringFactoriesLoader所以真正的工作者是SpringFactoriesLoader
,SpringFactoriesLoader
屬於Spring框架私有的一種擴充套件方案,其主要功能就是從指定的配置檔案META-INF/spring.factories
載入配置
public abstract class SpringFactoriesLoader { //... public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) { ... } public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { .... } }
配合@EnableAutoConfiguration
使用的話,它更多是提供一種配置查詢的功能支援,即根據@EnableAutoConfiguration
的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration
作為查詢的Key,獲取對應的一組@Configuration類
上圖就是從SpringBoot的autoconfigure
依賴包中的META-INF/spring.factories
配置檔案中摘錄的一段內容,可以很好地說明問題。我從中隨機挑取一個類檢視原始碼
所以,其底層真正實現是從classpath中搜尋所有的META-INF/spring.factories
配置檔案,並將其中org.springframework.boot.autoconfigure.EnableutoConfiguration
對應的配置項通過反射例項化為對應的標註了@Configuration的JavaConfig形式的IoC容器配置類,然後彙總為一個並載入到IoC容器。
@SpringBootApplication
註解說完了,接下來我們來分析SpringApplication.run
方法
作者有話說:喜歡的話就請關注Java面試必修 ,請自備水,更多幹、幹、乾貨等著你