SpringBoot自動裝配原理


SpringBoot自動裝配原理

SpringBoot如果問到的話就說說

  1. 自動裝配(最主要的)
  2. run方法(次要)

pom.xml

pom.xml主要有四個部分:
項目元數據信息:創建時候輸入的Project Metadata部分,也就是Maven項目的基本元素,包括:groupId、artifactId、version、name、description等
parent:繼承spring-boot-starter-parent的依賴管理,控制版本與打包等內容
dependencies:項目具體依賴,這里包含了spring-boot-starter-web用於實現HTTP接口(該依賴中包含了Spring MVC),官網對它的描述是:使用Spring MVC構建Web(包括RESTful)應用程序的入門者,使用Tomcat作為默認嵌入式容器。;spring-boot-starter-test用於編寫單元測試的依賴包。更多功能模塊的使用我們將在后面逐步展開。
build:構建配置部分。默認使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot應用打包成JAR來直接運行。

  • spring-boot-dependencies: 核心工程在父工程中
    也就是說我們的項目主要是依賴一個父項目
<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.1.9.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

進入父項目

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.1.9.RELEASE</version>
  <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

這里才是真正管理SpringBoot應用里面所有依賴版本的地方,SpringBoot的版本控制中心;

以后我們導入依賴默認是不需要寫版本;但是如果導入的包沒有在依賴中管理着就需要手動配置版本了;

  • 我們在寫或者引入一些springboot依賴的時候,不需要指定版本,因為在父工程中有了那些版本倉庫

啟動器

  •   <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
      </dependency>
    
  • 啟動器實際上就是SpringBoot的啟動場景。
  • 比如spring-boot-starter-web, 就會幫助項目自動導入Web環境所有的依賴。
  • SpringBoot會將所有的功能場景都變成一個個的啟動器。
  • SpringBoot將所有的功能場景都抽取出來,做成一個個的starter (啟動器),只需要在項目中引入這些starter即可,所有相關的依賴都會導入進來 , 我們要用什么功能就導入什么樣的場景啟動器即可 。官網Starters

主程序

// @SpringBootApplication 標注下面的這個類是一個SpringBoot的應用
@SpringBootApplication
public class HalftownApplication {
    public static void main(String[] args) {
            // 將SpringBoot 應用啟動
	    SpringApplication.run(HalftownApplication.class, args);
    }
}

注解

@SpringBootConfiguration : SpringBoot的配置類,標注在某個類上, 表示這是一個SpringBoot的配置類
    @Configuration: Spring配置類,配置類上來標注這個注解,說明這是一個配置類 ,配置類---即----配置文件;
    @Componet: 說明這是一個Spring的組件,啟動類本身也是Spring中的一個組件而已,負責啟動應用
    
@EnableAutoConfiguration:開啟自動配置功能,以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置,@EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效
    @AutoConfigurationPackage: 自動配置包
        @Import({Registrar.class}): 自動配置包注冊器
    @Import({AutoConfigurationImportSelector.class}): 自動配置導入選擇器
    (@import :Spring底層注解@import , 給容器中導入一個組件)
    Registrar.class 將主配置類 【即@SpringBootApplication標注的類】的所在包及包下面所有子包里面的所有組件掃描到Spring容器 ;

@ComponentScan:這個注解在Spring中很重要 , 它對應XML配置中的元素。@ComponentScan的功能就是自動掃描並加載符合條件的組件或者bean , 將這個bean定義加載到IOC容器中 ;

最終所有的資源都會加載到配置類中
Properties properties=PropertiesLoaderUtils. LoadProperties(resource);

1、@SpringBootApplication
意義:SpringBoot應用標注在某個類上說明這個類是SpringBoot的主配置類 , SpringBoot就應該運行這個類的main方法來啟動SpringBoot應用;

進入這個注解:可以看到上面還有很多其他注解!

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    //.....
}

2、@ComponentScan
​ 這個注解在Spring中很重要 , 它對應XML配置中的元素。@ComponentScan的功能就是自動掃描並加載符合條件的組件或者bean , 將這個bean定義加載到IOC容器中 ;

3、@SpringBootConfiguration
意義:SpringBoot的配置類 ;標注在某個類上 , 表示這是一個SpringBoot的配置類;

我們繼續進去這個注解查看

@Configuration //點進去得到下面的 @Component
public @interface SpringBootConfiguration {
}
@Component
public @interface Configuration {
}

@Configuration:配置類上來標注這個注解,說明這是一個配置類 ,配置類---即----配置文件;

我們繼續點進去,發現配置類也是容器中的一個組件。@Component 。這就說明,啟動類本身也是Spring中的一個組件而已,負責啟動應用!

4、@EnableAutoConfiguration
我們回到 SpringBootApplication 注解中繼續看。

@EnableAutoConfiguration :開啟自動配置功能

​ 以前我們需要自己配置的東西,而現在SpringBoot可以自動幫我們配置 ; @EnableAutoConfiguration告訴SpringBoot開啟自動配置功能,這樣自動配置才能生效;

我們點擊去查看。發現注解@AutoConfigurationPackage : 自動配置包

@AutoConfigurationPackage //自動配置包
@Import({AutoConfigurationImportSelector.class}) //導入哪些組件的選擇器
public @interface EnableAutoConfiguration {
}

再點進去看到一個 @Import({Registrar.class})

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

@import :Spring底層注解@import , 給容器中導入一個組件

Registrar.class 將主配置類 【即@SpringBootApplication標注的類】的所在包及包下面所有子包里面的所有組件掃描到Spring容器 ;

退到上一步,繼續看:@Import({AutoConfigurationImportSelector.class}) :給容器導入組件 ;

AutoConfigurationImportSelector : 自動配置導入選擇器,那么它會導入哪些組件的選擇器呢? ;

我們點擊去這個類看源碼:

這個類中有一個這樣的方法

// 獲得候選的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //這里的getSpringFactoriesLoaderFactoryClass()方法
    //返回的就是我們最開始看的啟動自動導入配置文件的注解類;EnableAutoConfiguration
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

這個方法又調用了 SpringFactoriesLoader 類的靜態方法!我們進入SpringFactoriesLoader類loadFactoryNames() 方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //這里它又調用了 loadSpringFactories 方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

我們繼續點擊查看 loadSpringFactories 方法

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //獲得classLoader , 我們返回可以看到這里得到的就是EnableAutoConfiguration標注的類本身
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            //去獲取一個資源 "META-INF/spring.factories"
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            //將讀取到的資源遍歷,封裝成為一個Properties
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}

我們根據源頭打開spring.factories的配置文件 , 看到了很多自動配置的文件;這就是自動配置根源所在!

WebMvcAutoConfiguration

我們在上面的自動配置類隨便找一個打開看看,比如 : WebMvcAutoConfiguration


可以看到這些一個個的都是JavaConfig配置類,而且都注入了一些Bean,可以找一些自己認識的類,看着熟悉一下!

所以,
META-INF/spring.factories: 自動配置的的核心文件

springboot所有自動配置都是在啟動的時候掃描並加載:spring.factories所有的自動配置類都在這里面,但是不一定生效,要判斷條件是否成立,只要導入了對應的start,就有對應的啟動器了,有了啟動器,我們自動裝配就會生效,然后就配置成功!

所以,自動配置真正實現是從classpath中搜尋所有的META-INF/spring.factories配置文件 ,並將其中對應的 org.springframework.boot.autoconfigure. 包下的配置項,通過反射實例化為對應標注了 @Configuration的JavaConfig形式的IOC容器配置類 , 然后將這些都匯總成為一個實例並加載到IOC容器中。

下圖請放大認真食用

結論:

  1. SpringBoot在啟動的時候從類路徑下的META-INF/spring.factories中獲取EnableAutoConfiguration指定的值
  2. 將這些值作為自動配置類導入容器 , 自動配置類就生效 , 幫我們進行自動配置工作;
  3. 以前我們需要自己配置的東西 , 自動配置類都幫我們解決了
  4. 整個J2EE的整體解決方案和自動配置都在springboot-autoconfigure的jar包中;
  5. 它將所有需要導入的組件以全類名的方式返回 , 這些組件就會被添加到容器中 ;
  6. 它會給容器中導入非常多的自動配置類 (xxxAutoConfiguration), 就是給容器中導入這個場景需要的所有組件 , 並配置好這些組件 ;
  7. 有了自動配置類 , 免去了我們手動編寫配置注入功能組件等的工作;

Run
我最初以為就是運行了一個main方法,沒想到卻開啟了一個服務;

@SpringBootApplication
public class SpringbootDemo02Application {

public static void main(String[] args) {
    //該方法返回一個ConfigurableApplicationContext對象
    //參數一:應用入口的類     參數類:命令行參數
    SpringApplication.run(SpringbootDemo02Application.class, args);
}

}

SpringApplication.run分析

分析該方法主要分兩部分:

  • 一是SpringApplication的實例化,
  • 二是run方法的執行;

SpringApplication
這個類主要做了以下四件事情

  • 推斷應用的類型是普通的項目還是Web項目
  • 查找並加載所有可用初始化器 , 設置到initializers屬性中
  • 找出所有的應用程序監聽器,設置到listeners屬性中
  • 推斷並設置main方法的定義類,找到運行的主類

查看構造器

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

run方法

部分內容來自於秦疆のJava世界


免責聲明!

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



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