一.說明
1.本次源碼解析是基於2.3.3.RELEASE版本的
2.本文主要分析Spring的自動配置
二.原碼分析
1.創建一個普通的springboot項目如下:
只有一個配置文件和一個啟動類。
配置文件中只配了一個redis,配置其他組件都行,這里以redis為例展開說明自動注入。
2.打開啟動類
對於springboot來說,最強大的地方就是沒有復雜的配置文件,創建springboot后只有一個啟動類,那就從啟動類入手,Ctrl + 左鍵 點擊
@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 {
可以看到,
@SpringBootApplication注解上面又有七個注解,其實,這種包含了很多注解的注解就是組合注解。前四個是元注解(在JDK 1.5中提供了4個標准的用來對注解類型進行注解的注解類,我們稱之為 meta-annotation(元注解)),我們先分析前四個注解:
-
@Target : 描述注解的使用范圍,括號里有個ElementType.TYPE,點進去,如下:
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, /** Field declaration (includes enum constants) */ FIELD, /** Method declaration */ METHOD, /** Formal parameter declaration */ PARAMETER, /** Constructor declaration */ CONSTRUCTOR, /** Local variable declaration */ LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
Target注解用來說明那些被它所注解的注解類可修飾的對象范圍:注解可以用於修飾 packages、types(類、接口、枚舉、注解類)、類成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數),在定義注解類時使用了@Target 能夠更加清晰的知道它能夠被用來修飾哪些對象,它的取值范圍定義在ElementType 枚舉中。
-
@Retention 同理,點擊去 RetentionPolicy類,它的主要作用是:用來限定那些被它所注解的注解類在注解到其他類上以后,可被保留到何時,一共有三種策略,定義在RetentionPolicy枚舉中。
public enum RetentionPolicy { SOURCE, // 源文件保留 CLASS, // 編譯期保留,默認值 RUNTIME // 運行期保留,可通過反射去獲取注解信息
- @Documented ,它的作用是:描述在使用 javadoc 工具為類生成幫助文檔時是否要保留其注解信息
-
@Inherited,這是個比較重要的注解,它表示注解會被子類自動繼承。
接下來就剩三個注解了,其中@EnableAutoConfiguration是最核心的注解,我們放到最后面說,先說其他兩個注解:
- @SpringBootConfiguration : 它的作用就是:繼承自@Configuration,二者功能也一致,標注當前類是配置類, 並會將當前類內聲明的一個或多個以@Bean注解標記的方法的實例納入到spring容器中,並且實例名就是方法名。可以點擊去看下,如下:其實,@Configuration和 @SpringBootConfiguration 是具有相同功能的。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
-
@ComponentScan,也是比較復雜的,點進去,如下圖:
這也就是為什么,所有的代碼都要放在啟動類所在的包及子包里面。
接下來就是最重要的注解了,@EnableAutoConfiguration ,先點進去,如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }
前四個注解就不說了,
@AutoConfigurationPackage的作用是導入自定義的類的,
@Import(AutoConfigurationImportSelector.class)是導入框架本身的一些類的
在這里,這兩個注解僅僅是找到需要導入的類,並沒有實例化,實例化依然需要spring容器去做。
先看@AutoConfigurationPackage,點進去,如下圖:
有個Registrar,點進去,如下圖:
從方法名可以看出是個注冊的方法,打上斷點,啟動,當代碼停住后,Alt + F8,查看new PackageImports(metadata).getPackageNames().toArray(new String[0])的值,如下圖:
從上圖可以看出,這個方法其實就是就是把自定義包下的類掃描並注冊到容器中。
再看@Import(AutoConfigurationImportSelector.class),它是注入框架本身使用的和自動配置相關的類,點擊去,找到getCandidateConfigurations方法。
注釋的意思的是;找到可能的自動配置的類名,進入loadFactoryNames方法,
繼續進入loadSpringFactories,
圖中有個FACTORIES_RESOURCE_LOCATION,點進去,
原來是一個文件,也就是說,springboot框架本省要導入的類就在這個文件中,那么這個spring.factories在哪呢?
接下來打開pom文件,找到spring-boot-starter-web,點進去,
找到spring-boot-starter,點進去
就能發現有一個spring-boot-autoconfigure,表示自動配置,如圖。
打開pom文件引入的包,在工程窗口,如下圖:
找到:spring-boot-autoconfigure,如下圖:
發現有一個spring.properties文件,打開:
發現,類似於redis這種組件所對應的類就在這個文件中。
到此為止,springboot只是將可能用到的類加載進來了,但是僅僅知識加載了類名,怎么能根據我們在yml文件中的配置來使用呢?也就是說,springboot怎么能知道我們要使用哪些類,不使用哪些類呢,比如我們現在要是用redis,首先我們需要在yml文件中配置redis的連接信息,如下圖:
想到這里,我們就應該想到,springboo肯定是通過加載這個yml文件開讀取的,接下來跟原碼:
打開啟動類,進入run方法,只要是run方法,就一直往下走,直到org.springframework.context.ConfigurableApplicationContext這個方法,如下圖:
進入:prepareEnvironment方法這個方法表示環境的准備,如下圖;
進入environmentPrepared,表示添加監聽:
進入environmentPrepared方法,表示初始化:繼續往下走,方法順序為
multicastEvent->
multicastEvent->
multicastEvent->
invokeListener(listener, event))->
doInvokeListener——>如圖:
進入onApplicationEvent接口的配置文件實現類 ConfigFileApplicationListener
進入實現方法:
再按下順序往下走:
onApplicationEnvironmentPreparedEvent ->
postProcessEnvironment->如圖:
再進入postProcessEnvironment的實現類:ConfigFileApplicationListener
實現方法如下:
再進入addPropertySources方法:
在進入load方法:
再進入load方法:
再進入load方法:
點擊getFileExtensions()進入接口,
這個接口有兩個實現類,分別是properties和yml,太熟悉了,這不就是配置文件嗎,這個接口其實就是配置文件的擴展名,
重新進入load方法:
進入loadForFileExtension方法:
再進入load方法:
再進入loadDocuments方法:
再進入load接口方法:
發現又有兩個實現類,進入yml的實現類:
····················終於完了,這是最后一個方法了,在犯法最后一行打上斷點:啟動:
把配置文件中的所有信息都加載進來了。
但是。。。,這也只是把配置文件加載進來了,那么redis怎么起作用呢?
打開前面說的spring.properties文件找到 RedisAutoConfiguration類,進入這個類,如下
要使redis起作用,圖中的四個注解不可少,進入注解括號中的RedisProperties類:
發現,又是熟悉的感覺,這不就對飲配置文件中的屬性嗎?
再看RedisTemplate<Object, Object> template = new RedisTemplate<>(); 這行代碼,直接就new了一個對象,這就是創建了一個RedisTemplate,因此,我們就可以使用RedisTemplate來操作redis了。
所以springboot,也就是通過這種方式進行類的自動裝配的。