SpringBoot框架已經很流行了,筆者做項目也一直在用,使用久了,越來越覺得有必要理解SpringBoot框架中的一些原理了,目前的面試幾乎都會用問到底層原理。我們在使用過程中基本上是搭建有一個框架拿來現用,在此過程中遇到問題就去百度來解決相應的問題,但是,對其原理不理解的情況下,雖然問題能夠解決,還是不會有多大收獲。下次再遇到問題的時候仍感覺力不從心。在了解了相關問題及解決方案之后,筆者總結了一些原理,這里作為學習筆記,與大家共勉。
一、Springboot環境搭建
這里我是用的環境及開發工具是JDK8+IntelliJIDEA
(1)創建Spring Boot項目
名稱根據需要進行更改。
輸入項目名稱或默認,點Finish。至此,SpringBoot工程創建完畢,結構如下:
二、Spring Boot使用
工程創建完畢之后,我們在項目中創建一個類:Animal,
那么如果想要把這個類交給Spring去管理,怎么辦呢?使用過SpringBoot的人都會,我們通過Animal類上面加上@component注解
加了這個注解之后,Spring在啟動的時候就會掃描該類,完成初始化,並把它放入Spring容器中,啟動,看效果如下:
非常簡單的方式就實現了,僅僅是通過一個注解,那么,這個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); } } } }
運行效果:
三、總結
這里總結了幾種將我們自定類交給Spring管理的方式,分別是:一、通過@Component注解,讓Spring自動掃描完成配置管理自動裝配;二、通過@Import注解,加入包路徑,讓Spring掃描,然后進行自動裝配;三、通過,創建配置類,通過@Configuration和@Bean完成類的加載和自動裝配。不管是哪一種方式,我們都可以完成自定義類由Spring容器去管理,理解了這種方式之后,我們再也不需要,使用類的時候new對象了,需要哪些對象,只需要從Spring容器中獲取便可。
源碼地址:https://files-cdn.cnblogs.com/files/10158wsj/SpringbootDemo.zip