SpringBoot框架已經很流行了,筆者作項目也一直在用,使用久了,愈來愈以爲有必要理解SpringBoot框架中的一些原理了,目前的面試幾乎都會用問到底層原理。咱們在使用過程當中基本上是搭建有一個框架拿來現用,在此過程當中遇到問題就去百度來解決相應的問題,可是,對其原理不理解的狀況下,雖然問題可以解決,仍是不會有多大收穫。下次再遇到問題的時候仍感受力不從心。在瞭解了相關問題及解決方案以後,筆者總結了一些原理,這裏做爲學習筆記,與你們共勉。面試
1、Springboot環境搭建spring
這裏我是用的環境及開發工具是JDK8+IntelliJIDEA數組
(1)建立Spring Boot項目springboot
名稱根據須要進行更改。框架
輸入項目名稱或默認,點Finish。至此,SpringBoot工程建立完畢,結構以下:ide
2、Spring Boot使用工具
工程建立完畢以後,咱們在項目中建立一個類:Animal,學習
那麼若是想要把這個類交給Spring去管理,怎麼辦呢?使用過SpringBoot的人都會,咱們經過Animal類上面加上@component註解開發工具
加了這個註解以後,Spring在啓動的時候就會掃描該類,完成初始化,並把它放入Spring容器中,啓動,看效果以下:this
很是簡單的方式就實現了,僅僅是經過一個註解,那麼,這個Animal類,經過這樣一個註解是怎麼交給Spring去管理的呢,中間的過程經歷了什麼呢?內部又是怎樣的一種機制呢?爲了看到它的實現過程,這裏面主要是兩個註解,@SpringBootApplication 和@EnableAutoConfiguration。這兩個註解都是複合註解。
2.1 @SpringBootApplication實現原理
點擊進去看@SpringBootApplication的實現過程:
這裏面有不少註解,首先來看@SpringBootConfiguration這個註解,從字面來看,他應該是一個配置註解,經過實現過程,咱們知道,他應該就是Spring提供的一個配置註解。
,
那麼,這個註解有什麼做用呢?其實咱們也用到過,來演示一下他的使用。首先,創建一個config包,而後咱們在包裏建立兩個類:User,Car,如圖:
這兩個類沒有加入任何註解,咱們在MyConfig這個配置類中,經過手動裝配的方式,進行類的初始化,即便用@Configuration這個註解和@Bean註解的方式,啓動項目運行,效果以下:如圖:
經過這樣的方式,咱們照樣能夠將自定義Bean交給Spring容器去管理。這裏須要注意的是config這個包必定要和啓動類在同一個文件夾下,不然,不加指定掃描的包,Spring默認是不會掃描到的。其實,將@Configuration換成@SpringBootConfiguration效果是同樣的,由於後者是一個複合註解,只不過是多包裝了一層而已。那麼該註解的做用就很明顯了,就是將@configuration註解下面全部帶有Bean註解的對象進行裝配就交給Spring容器去管理。這就是SpringBoot框架自動裝配的一部分。SpringBoot在啓動的時候,會將不少的Bean進行自動裝配,經過什麼方式呢?打開源碼:
咱們看到,其實這裏面配置了不少自動裝配的類,當咱們啓動服務的時候,他會掃描這個配置文件當中的全部配置項進行自動裝配,這裏麪包含咱們幾乎能用到的全部組件、包括Redis、Elasticsearch、JDBC等。可是咱們知道SpringBoot在啓動的時候並不會把全部的類都一塊兒初始化加入到容器當中,這個是有前提條件的,咱們隨便點進去一個:
咱們注意到,哪一個類會被自動裝配是會有條件的,從@ConditionalOnClass來看,條件就是該類存在,而且在Spring容器中存在實例的狀況下才會進行裝配進而運行裝配類。好比說,我在項目中用到了Elastic search,那麼咱們經過@Configuration下的@Bean配置就會加載該實例,這是一個實際應用:
2.2 @EnableAutoConfiguration實現原理
從上面的分析可知,咱們啓動類中的@EnableAutoConfiguration註解它會將全部知足條件的Bean進行自動裝配。那麼問題又來了,@EnableAutoConfiguration它又是如何實現的呢?繼續看實現過程:
即,經過@import將全部SpringBoot須要裝配的類導入進來。那麼@Import該如何理解呢,來看個例子,咱們把MyConfig中的@Configuration註解去掉,這時候,啓動項目,User類和Car類都不會被加載
可是,咱們還想使用這兩個類,怎麼辦呢?經過@Import註解來實現。首先咱們看@Import它的實現過程:須要傳入Class類型的參數,是一個數組,那麼在啓動類中加入這個註解看一下運行效果:
經過這種方式,一樣,實現了Bean的初始化。那麼這樣的原理是什麼呢?也就是說SpringBoot在啓動的時候會默認拿到和啓動類在同一級的文件夾(包路徑),而後對其全部帶有註解的類進行掃描,接着,使用AutoConfigurationImportSelector.class這個類加載,該類實現了DeferredImportSelector,DeferredImportSelector繼承了ImportSelector,這就是自動導入的原理了。
這個類中有一個方法:selectImports,該方法返回一個String類型的數組,該數組中放的就是自動裝配進來的包路徑了,做用找到知足配置的全部帶有註解的類,而後交由Spring去管理。
瞭解了這個原理以後,那麼,咱們就能夠本身來實現把某一些類交給Spring去管理的方法了。
首先,去掉配置類中的@Configuration註解以及類中的@compoent註解,這時候運行項目,咱們的自定義類是沒法交給Spring去管理的,接下來,咱們本身來實現,模擬SpringBoot的類加載過程:
而後,建立自定義類ModelImportSelector並實現ImportSelector接口並重寫selectImports方法,重寫該方法的目的就是把返回結果中的每個元素,即類的全名稱,交給Spring容器去管理。咱們放兩個類進去,分別是
Animal,Car,而後在啓動類中經過import註解引入,過程以下:
public class ModelImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{ "com.springboot.demo.model.Animal", "com.springboot.demo.model.Car" }; } }
@SpringBootApplication @Import(ModelImportSelector.class) public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args ); context.close(); } }
運行結果:
這種方式實現了將咱們想要交給Spring管理的類進行了託管,可是,若是有N多個類的話,這種寫法會累死的,所以,須要經過遞歸的方式加載包路徑名,而後統一初始化,將該包路徑下的全部類進行溼實例化。首先咱們本身建立一個註解:MyImport,並加入Spring的@Import註解,而後將啓動類中的@Import註解改爲咱們自定義註解。
目的:掃描 "com.springboot.demo.model"下面的全部類,初始化並交給Spring容器管理
自定義註解:
** * Created by ${USRE} on 2019/6/20 on 14:46 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(ModelImportSelector.class) public @interface MyImport { String [] packages(); }
啓動類加入自定義註解:
@SpringBootApplication @MyImport(packages = "com.springboot.demo.model") public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args ); context.close(); }
重寫selectImports方法:
private List<String> classList=new ArrayList <>(); @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { //經過字節碼文件解讀,獲取元素據信息,經過註解名稱,返回註解所對應的屬性的Map集合 String [] packages = (String [])annotationMetadata.getAnnotationAttributes( MyImport.class.getName() ).get( "packages" ); if (packages == null) { return null; } scanPackagesRecursion(packages); //獲取註解中packages中配置的內容 //獲取須要掃描的包的全部類的路徑 return !classList.isEmpty()?classList.toArray( new String[classList.size()] ):null; } private void scanPackagesRecursion(String [] packages){ for(String path : packages){ doScanPackages(path); } } /** * 方法遞歸 * @param path */ private void doScanPackages(String path){ URL resource = this.getClass().getClassLoader(). getResource( path.replaceAll( "\\.", "/" ) ); File file = new File( resource.getFile() ); File[] files=file.listFiles(); for (File fileSub : files) { if(fileSub.isDirectory()){ doScanPackages(path+"."+fileSub.getName()); }else{ String fileName=fileSub.getName(); System.out.println("fileName:"+fileName); if(fileName.endsWith( ".class" )){ String classPath=path+"."+fileName.replaceAll( "\\.class","" ); this.classList.add( classPath ); System.out.println("classPath:"+classPath); } } } }
運行效果:
3、總結
這裏總結了幾種將咱們自定類交給Spring管理的方式,分別是:1、經過@Component註解,讓Spring自動掃描完成配置管理自動裝配;2、經過@Import註解,加入包路徑,讓Spring掃描,而後進行自動裝配;3、經過,建立配置類,經過@Configuration和@Bean完成類的加載和自動裝配。不論是哪種方式,咱們均可以完成自定義類由Spring容器去管理,理解了這種方式以後,咱們不再須要,使用類的時候new對象了,須要哪些對象,只須要從Spring容器中獲取即可。
源碼地址:https://files-cdn.cnblogs.com/files/10158wsj/SpringbootDemo.zip