個人博客網:https://wushaopei.github.io/ (你想要這里多有)
3、Spring Boot 的自動配置原理
上述是主啟動類的代碼,我們從中可以看到在類名前面添加了一個注解@SpringBootApplication;在前面的SpringBoot 的自啟動配置一文中,我們可以得知,在主啟動類中,main方法中的 SpringApplication.run(DemoApplication.class,args);是對主啟動類進行初始化並啟動IOC容器進行配置bean的管理。
在這里,我們可以發現,SpringBoot 中比 Spring 要優秀的一點 自動配置 的特性並沒有表現在main方法中,從代碼中,我們可以看出,也許 @SpringBootApplication 這一個注解會與SpringBoot 的自動配置有關,本文將從這方面下手,根據源碼進行分析。
使用過Spring Boot 的我們都知道,SpringBoot 中有一個全局配置文件: application.properties 或 application.yml。
我們在項目開發過程中對程序配置的 端口、數據庫等屬性都可以在這個文件中進行配置,最常見的配置有: server.port 、logging.level.* 等等,然而我們實際用到的往往只是很少的一部分,那么這些屬性是否有據可依呢?答案是肯定的,這些屬性都可以在官方文檔中查找到:
在Appendix A. Common application properties小節下:
我們對自動配置大膽做個猜測,在Spring Boot 中有個機制,它將通過pom.xml 依賴加載到的諸如 mybatis、mysql、activemq等的jar 包創建對應的 bean,然后將bean放到容器中,並通過在 application.properties 或 application.yml 文件中進行配置屬性參數映射到對應的 bean 中的屬性上,從而實現如mysql 的數據庫鏈接地址、密碼、用戶名的配置等操作。
那么,在這里我們針對猜測的自動配置過程提出三個關鍵性問題,分別是:
- SpringBoot 中怎么將引入的依賴包的類創建bean並加入到容器中的?
- springboot 中怎么獲取到配置文件 application.properties 或application.yml 中的屬性參數?
- springboot 怎么將獲取到的配置文件參數映射到bean中的屬性上?
3.1 配置加載依賴並創建bean加入到容器中:
(1)引入 jar
spring-boot-starter-web 包自動幫我們引入了web模塊開發需要的相關jar包,
mybatis-spring-boot-starter 幫我們引入了dao開發相關的jar包。
spring-boot-starter-xxx 是官方提供的starter,xxx-spring-boot-starter是第三方提供的starter。
如下圖中:
從以下的截圖可以看出在這個mybatis-spring-boot-starter 中,並沒有任何源碼,只有一個pom文件,它的作用就是幫我們引入了相關jar包。
在這里我們看一下SpringBoot 中數據源的配置方案:
在這里,starter機制幫我們完成了項目起步所需要的相關jar包。我們知道,傳統的spring應用中需要在 application.xml 中配置很多bean的,比如 dataSource 的配置,transactionManager的配置...... springboot是如何幫我們完成這些bean的配置的呢?
自動配置
(2)基於java代碼的bean的配置
以mybatis為例,引入的mybatis 包不只有一個pom.xml文件, 還有一個關鍵性的包:mybatis-spring-boot-autoconfigure這個包
從mybatis-spring-boot-autoconfigure包下的文件名我們可以知道MybatisAutoConfiguration類是自動配置的核心文件;我們進入該文件看看,
從截圖中我們可以看到,類名和方法名前分別添加了@Configuration 、@Bean兩個注解,了解Spring的都知道,這兩個注解一起使用就可以創建一個基於java代碼的配置類,可以用來替代相應的xml配置文件。
@Configuration注解的類可以看作是能生產讓Spring IoC容器管理的Bean實例的工廠。
@Bean注解告訴Spring,一個帶有@Bean的注解方法將返回一個對象,該對象應該被注冊到spring容器中。
傳統的基於xml的bean配置方法如下:
相當於用基於java代碼的配置方式:
由此,我們可以知道,上面的 MybatisAutoConfiguration這個類,自動幫我們生成了SqlSessionFactory 這些 Mybatis 的重要實例並叫個spring容器管理,從而完成bean的自動注冊。
(3)springboot 特有的常見條件依賴注解:
到這里,我們知道了,要想完成Mybatis的自動配置,需要在類路徑中存在 SqlSessionFactory.class、SqlSessionFactoryBean.class 這兩個類,同時也需要有DataSource這個bean存在且已經自動注冊到容器中。
根據MybatisAutoConfiguration的名稱我們可以得知,在Springboot 中,引入自動配置的DataSource的包的名稱應該是 DataSourceAutoConfiguration;
通過查看該包所在目錄我們知道這個包又屬於spring-boot-autoconfigure-2.0.4.RELEASE.jar這個包,自動配置這個包幫們引入了jdbc、kafka、logging、mongo、quartz等包。很多包需要我們引入相應jar后自動配置才生效。
3.2 獲取到配置文件 application.properties 或application.yml 中的參數
Bean 參數的獲取
在這里,我們要解決的是怎么從 .properties或yml文件中獲取參數,讓springboot知道配置文件中有哪些參數?
在DataSourceAutoConfiguration類里面,我們注意到使用了EnableConfigurationProperties這個注解。在該注解中引入了 DataSourceProperties.class 這個字節碼文件。
點擊進入查看,DataSourceProperties中封裝了數據源的各個屬性,且使用了注解ConfigurationProperties指定了配置文件的前綴。
@EnableConfigurationProperties與@ConfigurationProperties這兩個注解有什么用呢?我們先看一個例子:
運行結果:
從運行結果可以看出@ConfigurationProperties與@EnableConfigurationPropertie的作用就是:
@ConfigurationProperties注解的作用是把yml或者properties配置文件轉化為bean。
@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在spring容器中是獲取不到yml或者properties配置文件轉化的bean的。
通過這種方式,把yml或者properties配置參數轉化為bean,這些bean又是如何被發現與加載的?
3.3 發現bean 並進行加載
到了這里,我們解決了兩個問題,知道了通過pom.xml加載到程序中的jar包是怎么被Springboot進行自動配置,也知道了Springboot中是怎么讀取並使用 .properties或 .yml 文件的參數的。接下來我們需要將兩者進行關聯,參數Bean是怎么被發現,並加載到容器中與自動配置的bean進行關聯映射的?
Bean的發現
從前面Springboot的自啟動機制中可以知道,Springboot 默認掃描啟動類所在的包下的主類與子類的所有組件,但並沒有包括依賴包中的類,那么依賴包中的bean是如何被發現和加載的?
我們在前面說過,SpringBoot 的自動配置是由@SpringBootApplication這個注解起得作用,具體的實現原理我們點進去看一下:
在SpringBootApplication注解類中,實際上重要的只有三個Annotation:
@Configuration(@SpringBootConfiguration里面還是應用了@Configuration)
@EnableAutoConfiguration
@ComponentScan
@Configuration的作用上面我們已經知道了,被注解的類將成為一個bean配置類。
@ComponentScan的作用就是自動掃描並加載符合條件的組件,比如@Component和@Repository等,最終將這些bean定義加載到spring容器中。
@EnableAutoConfiguration 這個注解的功能很重要,借助@Import的支持,收集和注冊依賴包中相關的bean定義。
這里進入到 @EnableAutoConfiguration 注解中看看:
如上源碼,@EnableAutoConfiguration注解引入了@AutoConfigurationPackage和@Import這兩個注解。@AutoConfigurationPackage的作用就是自動配置的包,@Import導入需要自動配置的組件。
進入@AutoConfigurationPackage,發現也是引入了@Import注解
點進去 @Import 引用的 Registrar.class 的Registrar類去看看:
從上述源碼我們可以解決一個小困惑,如下:
這兩句代碼的作用是用來加載啟動類所在的包下的主類與子類的所有組件注冊到spring 容器,這就是前文所說的springboot默認掃描啟動類所在的包下的主類與子類的所有組件。
這里進入AutoConfigurationPackages 包去看看,
簡單介紹一下,AnnotationMetadata
有兩種重要的實現方案,一種基於 Java 反射,另一種基於 ASM 框架。
兩種實現方案適用於不同場景。StandardAnnotationMetadata
基於 Java 反射,需要加載類文件。而 AnnotationMetadataReadingVisitor
基於 ASM 框架無需提前加載類,所以適用於 Spring 應用掃描指定范圍內模式注解時使用。
下面是metadata 的相關接口和類的關系圖:
獲取並返回包名,PackageImport
根據元數據中返回的類名獲取其所在的包目錄,將該包目錄返回給容器,用於自定義掃描包或默認配置掃描。
回到原來的問題上,搜集並注冊到spring 容器的那些bean來自哪里?
進入@EnableAutoConfiguration注解的EnableAutoConfigurationImportSelector 類中去看看,該類繼承了AutoConfigurationImportSelector 類,進入該類進行看下:
在 selectImports 方法中,SpringFactoriesLoader.loadFactoryNames 方法掃描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一個這樣的spring.factories文件。
注意:在啟動loadFactoryNames()方法前,SpringFactoriesLoader.loadFactoryNames方法調用loadSpringFactories方法從所有的jar包中讀取META-INF/spring.factories文件信息。
loadFactories會將加載到的jar中的類加載並保存,以提供loadFactoryNames中進行配置添加到執行序列
這個spring.factories文件也是一組一組的key=value的形式,其中一個key是EnableAutoConfiguration類的全類名,而它的value是一個xxxxAutoConfiguration的類名的列表,這些類名以逗號分隔,如下圖所示:
這個@EnableAutoConfiguration注解通過@SpringBootApplication被間接的標記在了Spring Boot的啟動類上。在SpringApplication.run(...)的內部就會執行selectImports()方法,找到所有JavaConfig自動配置類的全限定名對應的class,然后將所有自動配置類加載到Spring容器中。
其中有一個key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定義了需要自動配置的bean,通過讀取這個配置獲取一組@Configuration類。
注意:每個xxxAutoConfiguration都是一個基於java的bean配置類。實際上,這些xxxAutoConfiguratio不是所有都會被加載,會根據xxxAutoConfiguration上的@ConditionalOnClass等條件判斷是否加載。
如上代碼段,通過反射機制將spring.factories中@Configuration類實例化為對應的java實列。到此我們已經知道怎么發現要自動配置的bean了,最后一步就是怎么樣將這些bean加載到spring容器。
bean的加載
如果要讓一個普通類交給Spring容器管理,通常有以下方法:
- 使用 @Configuration與@Bean 注解
- 使用@Controller @Service @Repository @Component 注解標注該類,然后啟用@ComponentScan自動掃描
- 使用@Import 方法
springboot中使用了@Import 方法
@EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector實現了DeferredImportSelector接口,
DeferredImportSelector接口繼承了ImportSelector接口,ImportSelector接口只有一個selectImports方法。
我們先通過一個簡單例子看看@Import注解是如何將bean導入到spring容器的。
1、新建一個bean
2、創建一個UserSelecter類繼承ImportSelector接口並實現selectImports方法
3、創建ImportConfig類,使用@Configuration、@Import(ItpscSelector.class)注解。
4、從容器獲取bean
運行結果:
從結果可以看出:selectImports方法返回一組bean,@EnableAutoConfiguration注解借助@Import注解將這組bean注入到spring容器中,springboot正式通過這種機制來完成bean的注入的。
總結:
Spring Boot啟動的時候會通過@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自動配置類,並對其進行加載,而這些自動配置類都是以AutoConfiguration結尾來命名的,它實際上就是一個JavaConfig形式的Spring容器配置類,它能通過以Properties結尾命名的類中取得在全局配置文件中配置的屬性如:server.port,而XxxxProperties類是通過@ConfigurationProperties注解與全局配置文件中對應的屬性進行綁定的。
圖片來自於王福強老師的博客:https://afoo.me/posts/2015-07-09-how-spring-boot-works.html