SpringBoot-自動裝配對象及源碼ImportSelector分析


 

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"
        };
    }
}
View Code
@SpringBootApplication
@Import(ModelImportSelector.class)
public class DemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run( DemoApplication.class, args );
        context.close();
    }
}
View Code

運行結果:

這種方式實現了將我們想要交給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();
}
View Code

啟動類加入自定義注解:

@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();
    }
View Code

重寫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);
                }
            }
        }
    }
View Code

運行效果:

三、總結

  這里總結了幾種將我們自定類交給Spring管理的方式,分別是:一、通過@Component注解,讓Spring自動掃描完成配置管理自動裝配;二、通過@Import注解,加入包路徑,讓Spring掃描,然后進行自動裝配;三、通過,創建配置類,通過@Configuration和@Bean完成類的加載和自動裝配。不管是哪一種方式,我們都可以完成自定義類由Spring容器去管理,理解了這種方式之后,我們再也不需要,使用類的時候new對象了,需要哪些對象,只需要從Spring容器中獲取便可。

源碼地址:https://files-cdn.cnblogs.com/files/10158wsj/SpringbootDemo.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM