繼續總結吧,沒有面試就繼續夯實自己的基礎,前陣子的在面試過程中遇到的各種問題陸陸續續都會總結出來分享給大家,這次要說的也是面試中被問到的一個高頻的問題,我當時其實沒答好,因為很早之前是看過springboot啟動過程的源碼,但是時間隔得有點久了(兩年多沒用過springboot),所以當時也沒答好。這次好好總結這部分知識。
java線程start和run的區別、我看網上好多介紹springboot自動裝配過的文章時,上來就直接說@SpringBootApplication
注解是一個復合注解,從這個注解開始介紹springboot是如何將配置項進行加載的。其實我覺得難道不應該是先啟動了spring的容器,然后才能掃到注解,然后才能解析注解嗎?也可能是大家覺得創建容器刷新容器這些基礎操作都默認知道的,所以就都沒說。
但我在分析springboot自動裝配的時候,要先從SpringApplication.run()
方法開始。
我們進入到SpringApplication
這個類中看一下run()
方法的核心實現,差不多每一行我都加上了注釋了。
面試可以問面試官哪些問題,
SpringApplication.run()
方法中,我把關鍵點用序號標識出來了。
在創建ApplicationContext時,會根據用戶是否明確設置了ApplicationContextClass
類型以及初始化階段的推斷結果,決定為當前SpringBoot應用創建什么類型的ApplicationContext。
面試時面試官夸你、
創建完成ApplicationContext容器后,我們接著回到SpringApplication.run()
方法中。
下面開始初始化各種插件在異常失敗后給出的提示。
然后執行準備刷新上下文的一些操作。其實prepareContext()
方法也是非常關鍵的,它起到了一個承上啟下的作用。下面我們來看一下prepareContext()
方法里面具體執行了什么。
jeans什么意思?
關鍵的地方我也標注出來了,主要就是getAllSoures()
方法,這個方法中,獲取到的一個source就是啟動類DemoApplication。
這樣就通過獲取這個啟動類就可以在后load()方法中取加載這個啟動類到容器中。
然后,后面再通過listeners.contextLoaded(context)
;
將所有監聽器加載到ApplicationContext容器中。
最后就是我們上面說的核心的第二部刷新ApplicationContext容器操作,如果沒有這一步操作上面的內容也都白做的,通過SpringApplication的refreshContext(context)
方法完成最后一道工序將啟動類上的注解配置,刷新到當前運行的容器環境中。
上面我們說到在SpringApplication的run()
方法中,通過調用自己的prepareContext()
方法,在prepareContext()
方法中又調用getAllSources()
方法,然后去獲取啟動類,然后通過SpringApplication的load()
方法,去加載啟動類,然后在刷新容器的時候就會去將啟動類在容器中進行實例化。
在刷新ApplicationContext容器時,就開始解析啟動類上的注解了。
啟動類DemoApplication
就只有一個注解@SpringBootApplication
,那么下面來看一下這個注解:
可以看到這個注解是一個復合注解,有三個關鍵注解需要說明一下。
@SpringBootConfiguration
這個注解說明再點進去查看詳情發現就是一個@Configuration
注解,這說明啟動類就是一個配置類。支持Spring以JavaConfig的形式啟動。
這個注解,從字面的意思上也能看出來,就是組件掃描的意思,即默認掃描當前package以及其子包下面的spring的注解,例如:@Controller
、@Service
、@Component
等等注解。
@EnableAutoConfiguration
這個注解也是一個復合注解:
這個注解是比較核心的一個注解,springboot的主要自動配置原理基本上都來自@EnableAutoConfiguration這個注解的配置,那么我們通過看這個注解的源碼可以發現有兩個注解比較重要的。
@AutoConfigurationPackage
,自動配置包。@Import(AutoConfigurationImportSelector.class)
,自動引入組件。@AutoConfigurationPackage
這個注解字面的意思是自動配置包,那么我們點進去看看里面是什么樣的。
還是一個復合注解,但是最終依賴的確實@Import
這個注解,這個注解后面我們會介紹,現在先明白它就是給Spring容器引入組件的功能的一個注解。
那么我們接著來看看AutoConfigurationPackages.Registrar.class
這個類里面的代碼。
這兩張圖就是這個AutoConfigurationPackages.Registrar
這個類的關鍵部分,說實話,我是沒看出來什么東西。但是網上搜到的是這個register()方法的作用是,用來自動注冊一些組件中的配置,例如JPA的@Entity
這個注解,這里就是會開啟自動掃描這類注解的功能。
我們接著回來看@EnableAutoConfiguration
下的@Import(AutoConfigurationImportSelector.class)
這個注解的功能。進入到AutoConfigurationImportSelector
這個類里面后源碼如下:
然后我們進入getAutoConfigurationEntry()
方法來看看:
我們繼續進入getCandidateConfigurations()
方法:
看來最核心的方法是SpringFactroiesLoader.loadFactoryNames()
方法了,我們再進入看看:
包的好深,居然還有一層,那么繼續進入loadSpringFactories()
方法。
終于到最后一層了,算是“撥開云霧見天日,守得云開見月明”,下面就來梳理一下loadSpringFactories()方法。
首先FACTORIES_RESOURCE_LOCATION
這個常量的值是:"META-INF/spring.factories"
/*** The location to look for factories.* <p>Can be present in multiple JAR files.*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
所以第一個端核心代碼的意思是:
啟動的時候會掃描所有jar包下META-INF/spring.factories
這個文件。第二段代碼的意思是將這些掃描到的文件轉成Properties對象,后面兩個核心代碼的意思就是說將加載到的Properties對象放入到緩存中。
然后getCandidateConfigurations()
方法,是只獲取了key是EnableAutoConfiguration.class
的配置。
我們看到getCandidateConfigurations()
方法,通過SpringFactoriesLoader.loadFactoryNames()
獲取到了118個配置。
那么我們來看一個spring.factories
文件中的內容是什么樣子的呢?
原來是這種形式的,看來這和上一篇文章中講解的Java中的SPI機制加載接口實現很像啊,其實通過查閱資料發現,這就是一種自定義SPI的實現方式的功能。
那么我們以第一個配置類:org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
來看一下,這些類都是如果實現的。
打開org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
的源碼:
我們看到這個類有三個注解@Configuration
、@AutoConfigureAfter
、@ConditionalOnProperty
、因為有@Configuration
注解所以它也是一個配置類,然后第二注解中的參數類JmxAutoConfiguration.class
進入之后是這樣的:
也是存在@ConditionalOnProperty
注解的。那看來關鍵點就是@ConditionalOnProperty
這個注解了。
這個注解其實是一個條件判斷注解,這個條件注解后面的參數的意思是當存在系統屬性前綴為spring.application.admin
,并且屬性名稱為enabled
,并且值為true
時,才加載當前這個Bean并進行實例化。
這種spring4.0后面出現的的條件注解,可以極大的增加了框架的靈活性和擴展性,可以保證很多組件可以通過后期配置,而且閱讀源碼的人,通過這些注解就能明白在什么情況下才會實例化當前Bean。
后面還有不少這種條件注解呢:
@ConditionalOnBean:當容器里有指定Bean的條件下
@ConditionalOnClass:當類路徑下有指定的類的條件下
@ConditionalOnExpression:基于SpEL表達式為true的時候作為判斷條件才去實例化
@ConditionalOnJava:基于JVM版本作為判斷條件
@ConditionalOnJndi:在JNDI存在的條件下查找指定的位置
@ConditionalOnMissingBean:當容器里沒有指定Bean的情況下
@ConditionalOnMissingClass:當容器里沒有指定類的情況下
@ConditionalOnWebApplication:當前項目時Web項目的條件下
@ConditionalOnNotWebApplication:當前項目不是Web項目的條件下
@ConditionalOnProperty:指定的屬性是否有指定的值
@ConditionalOnResource:類路徑是否有指定的值
@ConditionalOnOnSingleCandidate:當指定Bean在容器中只有一個,或者有多個但是指定首選的Bean
這些注解其實都是通過@Conditional注解擴展而來的,只是使用了不同的組合條件來判斷是否需要加載和初始化當前Bean。
好了,最后總結一下,當面試官問springboot的自動裝配原理的時候,不能這么長篇大論的說吧,畢竟這么多內容也記不住啊。
所以總結:
springboot啟動時,是依靠啟動類的main方法來進行啟動的,而main方法中執行的是SpringApplication.run()
方法,而SpringApplication.run()
方法中會創建spring的容器,并且刷新容器。而在刷新容器的時候就會去解析啟動類,然后就會去解析啟動類上的@SpringBootApplication
注解,而這個注解是個復合注解,這個注解中有一個@EnableAutoConfiguration
注解,這個注解就是開啟自動配置,這個注解中又有@Import
注解引入了一個AutoConfigurationImportSelector
這個類,這個類會進過一些核心方法,然后去掃描我們所有jar包下的META-INF
下的spring.factories
文件,而從這個配置文件中取找key為EnableAutoConfiguration
類的全路徑的值下面的所有配置都加載,這些配置里面都是有條件注解的,然后這些條件注解會根據你當前的項目依賴的jar包以及是否配置了符合這些條件注解的配置來進行裝載的。
這就是springboot自動配置的過程。
SpringBoot在啟動的時候會調用run()方法,run()方法會刷新容器,刷新容器的時候,會掃描classpath下面的的包中META-INF/spring.factories文件,在這個文件中記錄了好多的自動配置類,在刷新容器的時候會將這些自動配置類加載到容器中,然后在根據這些配置類中的條件注解,來判斷是否將這些配置類在容器中進行實例化,這些條件主要是判斷項目是否有相關jar包或是否引入了相關的bean。這樣springboot就幫助我們完成了自動裝配。
作者:紀莫
原文鏈接:https://www.cnblogs.com/jimoer/p/14131406.html
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态