Spring Boot源碼剖析之Spring Boot源碼剖析

Spring Boot 源碼剖析

Spring Boot依賴管理

問題:(1)為什么導入dependency時不需要指定版本睛挚?
Spring Boot項目的父項目依賴spring-boot-starter-parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

spring-boot-starter-parent中定義了jdk版本,源文件編碼方式,Maven打包編譯版本等乌询。

<properties>
  <java.version>1.8</java.version>
  <resource.delimiter>@</resource.delimiter>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  <maven.compiler.target>${java.version}</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

并且在build標簽中定義了resource資源和pluginManagement

<resources>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <filtering>true</filtering>
    <includes>
      <include>**/application*.yml</include>
      <include>**/application*.yaml</include>
      <include>**/application*.properties</include>
    </includes>
  </resource>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <excludes>
      <exclude>**/application*.yml</exclude>
      <exclude>**/application*.yaml</exclude>
      <exclude>**/application*.properties</exclude>
    </excludes>
  </resource>
</resources>

里面定義了資源過濾,針對 application 的 yml 、 properties 格式進行了過濾,可以支持不同環(huán)境的配置舌缤,比如 application-dev.yml 、 application-test.yml 某残、 application-dev.properties 国撵、 application-dev.properties 等等。

spring-boot-starter-parent項目的父項目是spring-boot-dependencies

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.5.1</version>
</parent>

在這個項目中定義了大量的依賴項目的版本

<properties>
  <activemq.version>5.16.2</activemq.version>
  <antlr2.version>2.7.7</antlr2.version>
  <appengine-sdk.version>1.9.89</appengine-sdk.version>
  <artemis.version>2.17.0</artemis.version>
  <aspectj.version>1.9.6</aspectj.version>
  <assertj.version>3.19.0</assertj.version>
  <atomikos.version>4.0.6</atomikos.version>
  <awaitility.version>4.0.3</awaitility.version>
  <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
  <byte-buddy.version>1.10.22</byte-buddy.version>
......
</properties>

spring-boot-dependencies的dependencyManagement節(jié)點
在這里玻墅,dependencies定義了SpringBoot版本的依賴的組件以及相應版本介牙。

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-amqp</artifactId>
      <version>${activemq.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-blueprint</artifactId>
      <version>${activemq.version}</version>
    </dependency>
    .......
</dependencyManagement>

因為存在這個依賴關(guān)系所以在我們創(chuàng)建的Spring Boot項目中部分依賴不需要寫版本號。

(2)問題2: spring-boot-starter-parent父依賴啟動器的主要作用是進行版本統(tǒng)一管理澳厢,那么項目
運行依賴的JAR包是從何而來的环础?

spring-boot-starter-web
查看spring-boot-starter-web依賴文件源碼,核心代碼具體如下

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.5.1</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
  <version>2.5.1</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <version>2.5.1</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.3.8</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.3.8</version>
  <scope>compile</scope>
</dependency>

可見在spring-boot-starter-web依賴啟動器中打包了web開發(fā)所需要的底層所有依賴剩拢。

因此在引入spring-boot-starter-web依賴啟動器時线得,就可以實現(xiàn)web場景開發(fā),而不需要額外導入tomcat服務(wù)器及其他web依賴文件徐伐。

Spring Boot除了提供Web依賴啟動器之外贯钩,還提供了其他很多依賴啟動器。具體可以到官網(wǎng)查找办素。

自動配置

自動配置:根據(jù)我們添加的jar包依賴角雷,Spring Boot會將一些配置類的bean注冊進ioc容器,我們可以在需要的地方直接使用性穿。

Spring Boot是如何進行自動配置的勺三,都把哪些組件進行了自動配置?

Spring Boot的啟動入口是一個被@SpringBootApplication注解的類的main方法季二。

@SpringBootApplication

查看@SpringBootApplication源碼:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;

@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 {
    // 根據(jù)class來排除特定的類檩咱,使其不能加入spring容器揭措,傳入?yún)?shù)value類型是class類型胯舷。 
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    // 根據(jù)classname 來排除特定的類刻蚯,使其不能加入spring容器,傳入?yún)?shù)value類型是class的全 類名字符串數(shù)組桑嘶。 
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    // 指定掃描包炊汹,參數(shù)是包名的字符串數(shù)組。
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    // 掃描特定的包逃顶,參數(shù)類似是Class類型數(shù)組讨便。
    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

從源碼可以看出,@SpringBootApplication是一個組合注解以政,前面4個是注解的元數(shù)據(jù)信息霸褒,后面三個注解是核心:@SpringBootConfiguration、@EnableAutoConfiguration盈蛮、
@ComponentScan

@SpringBootConfiguration

查看源碼:


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

從源碼看到废菱,它是一個被@Configuration標記的注解,沒有其他的代碼抖誉。說明它其實就是一個@Configuration的包裝殊轴,重新命名,功能相同袒炉。

被@SpringBootConfiguration 標記的類就是一個配置類旁理。

@EnableAutoConfiguration

查看源碼:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自動配置包 
@AutoConfigurationPackage
// Spring的底層注解@Import,給容器中導入一個組件我磁; 
// 導入的組件是AutoConfigurationPackages.Registrar.class
@Import({AutoConfigurationImportSelector.class})
// 告訴SpringBoot開啟自動配置功能孽文,這樣自動配置才能生效。 
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    // 返回不會被導入到 Spring 容器中的類
    Class<?>[] exclude() default {};
    // 返回不會被導入到 Spring 容器中的類名 
    String[] excludeName() default {};
}

@EnableAutoConfiguration是一個組合注解:是@AutoConfigurationPackage 和@Import的組合夺艰。


image.png

Spring中很多以Enable開頭的注解叛溢,其作用就是借助@Import來收集并注冊特定場景相關(guān)的Bean,并加載到IOC容器劲适。

@EnableAutoConfiguration就是借助@Import來收集所有符合自動配置條件的bean定義楷掉,并加載到IoC容器。
它引入了AutoConfigurationImportSelector類霞势。

下面我們繼續(xù)深入到@AutoConfigurationPackage 和@Import這兩個注解中去看看它們做了什么工作烹植。

@AutoConfigurationPackage

查看源碼:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})// 導入Registrar中注冊的組件 
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

@AutoConfigurationPackage 注解引入Registrar類

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    /**
     *    注冊BeanDefinition
     */
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 根據(jù)注解元信息,拿到要掃描的包愕贡,掃描并注冊到BeanDefinitionRegistry中
        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
    }
}

register方法:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 第一次進到這個方法的時候private static final String BEAN = AutoConfigurationPackages.class.getName(); 并沒有在register中注冊草雕。
    if (registry.containsBeanDefinition(BEAN)) {
        AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
        beanDefinition.addBasePackages(packageNames);
    } else {
        // 注冊BeanDefinition
        registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
    }

}

在register中注冊的是AutoConfigurationPackages的內(nèi)部類BasePackagesBeanDefinition。
BasePackagesBeanDefinition源碼:


static final class BasePackagesBeanDefinition extends GenericBeanDefinition {
    private final Set<String> basePackages = new LinkedHashSet();

    BasePackagesBeanDefinition(String... basePackages) {
        // Class對象是BasePackages
        this.setBeanClass(AutoConfigurationPackages.BasePackages.class);
        this.setRole(2);
        // 保存基礎(chǔ)包
        this.addBasePackages(basePackages);
    }

    public Supplier<?> getInstanceSupplier() {
        return () -> {
            return new AutoConfigurationPackages.BasePackages(StringUtils.toStringArray(this.basePackages));
        };
    }

    private void addBasePackages(String[] additionalBasePackages) {
        this.basePackages.addAll(Arrays.asList(additionalBasePackages));
    }
}

static final class BasePackages {
    private final List<String> packages;
    private boolean loggedBasePackageInfo;

    BasePackages(String... names) {
        List<String> packages = new ArrayList();
        String[] var3 = names;
        int var4 = names.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String name = var3[var5];
            if (StringUtils.hasText(name)) {
                packages.add(name);
            }
        }

        this.packages = packages;
    }

    List<String> get() {
        if (!this.loggedBasePackageInfo) {
            if (this.packages.isEmpty()) {
                if (AutoConfigurationPackages.logger.isWarnEnabled()) {
                    AutoConfigurationPackages.logger.warn("@EnableAutoConfiguration was declared on a class in the default package. Automatic @Repository and @Entity scanning is not enabled.");
                }
            } else if (AutoConfigurationPackages.logger.isDebugEnabled()) {
                String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages);
                AutoConfigurationPackages.logger.debug("@EnableAutoConfiguration was declared on a class in the package '" + packageNames + "'. Automatic @Repository and @Entity scanning is enabled.");
            }

            this.loggedBasePackageInfo = true;
        }

        return this.packages;
    }
}

BasePackagesBeanDefinition 繼承了GenericBeanDefinition固以,說明它就是一個Spring的BeanDefinition墩虹。只是在這個BeanDefinition中保存的Class并不是AutoConfigurationPackages嘱巾,而是
BasePackages。

因此@EnableAutoConfiguration中的@AutoConfigurationPackage使用@Import({Registrar.class})注解诫钓,向Ioc容器中注冊了一個BasePackages的Bean定義菩浙。

用圖來描述一下整個過程:


image.png

@Import({AutoConfigurationImportSelector.class})

@Import({AutoConfigurationImportSelector.class}) :將
AutoConfigurationImportSelector 這個類導入到 Spring 容器中勉痴,
AutoConfigurationImportSelector 可以幫助 Springboot 應用將所有符合條件的 @Configuration 配置都加載到當前 SpringBoot 創(chuàng)建并使用的 IOC 容器( ApplicationContext )中吨娜。


image

可以看到 AutoConfigurationImportSelector 重點是實現(xiàn)了 DeferredImportSelector 接口和各種 Aware 接口巷折,然后 DeferredImportSelector 接口又繼承了 ImportSelector 接口。
其不光實現(xiàn)了 ImportSelector 接口惧所,還實現(xiàn)了很多其它的 Aware 接口骤坐,分別表示在某個時機會被回調(diào)。

自動配置實現(xiàn)邏輯的入口方法:

AutoConfigurationImportSelector將會被放到DeferredImportSelectorGrouping中的deferredImports集合中下愈。并且在DeferredImportSelectorGrouping的getImports()方法中進行統(tǒng)一處理纽绍。

跟自動配置邏輯相關(guān)的入口方法在 DeferredImportSelectorGrouping 類的 getImports 方法處.

public Iterable<Entry> getImports() {
    Iterator var1 = this.deferredImports.iterator();
    // 遍歷DeferredImportSelectorGrouping的deferredImports集合。AutoConfigurationImportSelector也在這個集合中势似。
    while(var1.hasNext()) {
        ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var1.next();
        
        // (1)利用AutoConfigurationGroup的process方法來處理自動配置的相關(guān)邏輯拌夏,決定導入哪些配置類
        this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
    }
    // (2)經(jīng)過上面的處理后,然后再進行選擇導入哪些配置類
    return this.group.selectImports();
}

其中重要的兩個步驟:

  • 利用AutoConfigurationGroup的process方法來處理自動配置的相關(guān)邏輯叫编,決定導入哪些配置類
  • 經(jīng)過上面的處理后辖佣,然后再進行選擇導入哪些配置類
分析自動配置的主要邏輯

AutoConfigurationImportSelector的process方法:

// 這里用來處理自動配置類,比如過濾掉不符合匹配條件的自動配置類 
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
        return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
    });
    // (1)調(diào)用getAutoConfigurationEntry方法得到自動配置類放入 autoConfigurationEntry對象中 
    AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
    // (2)又將封裝了自動配置類的autoConfigurationEntry對象裝進autoConfigurationEntries集合 
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
    // (3)遍歷剛獲取的自動配置類搓逾,放入entries
    while(var4.hasNext()) {
        String importClassName = (String)var4.next();
        // 這里符合條件的自動配置類作為key卷谈,annotationMetadata作為值放進entries集合
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }

}

其中重要的三個步驟:

  • 調(diào)用getAutoConfigurationEntry方法得到自動配置類放入 autoConfigurationEntry對象中
  • 又將封裝了自動配置類的autoConfigurationEntry對象裝進autoConfigurationEntries集合
  • 遍歷剛獲取的自動配置類,放入entries

獲取自動配置類方法getAutoConfigurationEntry:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // (1)得到spring.factories文件配置的所有自動配置類
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // 利用LinkedHashSet移除重復的配置類 
        configurations = this.removeDuplicates(configurations);
        // 得到要排除的自動配置類霞篡,比如注解屬性exclude的配置類 
        // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class) 
        // 將會獲取到exclude = FreeMarkerAutoConfiguration.class的注解數(shù)據(jù)
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        // 檢查要被排除的配置類世蔗,因為有些不是自動配置類,故要拋出異常
        this.checkExcludedClasses(configurations, exclusions);
        // (2)將要排除的配置類移除 
        configurations.removeAll(exclusions);
        // (3)對加載到的自動配置類按需要進行過濾
        configurations = this.getConfigurationClassFilter().filter(configurations);
        // (4)獲取了符合條件的自動配置類后朗兵,此時觸發(fā)AutoConfigurationImportEvent事件 
        // 目的是告訴ConditionEvaluationReport條件評估報告器對象來記錄符合條件的自動配置類 
        // 該事件什么時候會被觸發(fā)污淋?--> 在刷新容器時調(diào)用invokeBeanFactoryPostProcessors后置處理器時觸發(fā)
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        // (5)將符合條件和要排除的自動配置類封裝進AutoConfigurationEntry對象,并返回 
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

其中重要的五個步驟:

  • 得到spring.factories文件配置的所有自動配置類
  • 將要排除的配置類移除
  • 對加載到的自動配置類按需要進行過濾
  • 告訴ConditionEvaluationReport條件評估報告器對象來記錄符合條件的自動配置類
  • 將符合條件和要排除的自動配置類封裝進AutoConfigurationEntry對象余掖,并返回

getCandidateConfigurations獲取配置文件中的自動配置類過程分析
這個方法中重要的方法是loadFactoryNames,這個方法的作用是加載一些組件的名字寸爆。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 加載組件名字
    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;
}

進入loadFactoryNames方法,它只是做了簡單驗證盐欺,就委托另外一個方法loadSpringFactories 去加載組件名赁豆。


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }

    String factoryTypeName = factoryType.getName();
    // 真正去加載組件名的方法
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

繼續(xù)進入loadSpringFactories方法:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = (Map)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        HashMap result = new HashMap();

        try {
        
            // 加載類路徑下spring.factories文件,將其中設(shè)置的配置類的全路徑信息封裝 為Enumeration類對象
            Enumeration urls = classLoader.getResources("META-INF/spring.factories");

            //循環(huán)Enumeration類對象冗美,根據(jù)相應的節(jié)點信息生成Properties對象魔种,通過傳入 的鍵獲取值,在將值切割為一個個小的字符串轉(zhuǎn)化為Array粉洼,方法result集合中
            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 factoryTypeName = ((String)entry.getKey()).trim();
                    String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    String[] var10 = factoryImplementationNames;
                    int var11 = factoryImplementationNames.length;

                    for(int var12 = 0; var12 < var11; ++var12) {
                        String factoryImplementationName = var10[var12];
                        ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                            return new ArrayList();
                        })).add(factoryImplementationName.trim());
                    }
                }
            }

            result.replaceAll((factoryType, implementations) -> {
                return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
            });
            cache.put(classLoader, result);
            return result;
        } catch (IOException var14) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
        }
    }
}

這個方法的主要作用是遍歷整個ClassLoader下所有的jar包下的Spring.factories文件节预。


image

而在spring-boot-autoconfigure包的META-INF下的spring.factories中保存著springboot的默認提供的自動配置類叶摄。

image

我們下面總結(jié)下 getAutoConfigurationEntry 方法主要做的事情:
【1】從 spring.factories 配置文件中加載 EnableAutoConfiguration 自動配置類),獲取的自動配
置類如圖所示。
【2】若 @EnableAutoConfiguration 等注解標有要 exclude 的自動配置類安拟,那么再將這個自動配置類排除掉蛤吓;
【3】排除掉要 exclude 的自動配置類后,然后再調(diào)用 filter 方法進行進一步的過濾去扣,再次排除一些
不符合條件的自動配置類柱衔;
【4】經(jīng)過重重過濾后樊破,此時再觸發(fā) AutoConfigurationImportEvent 事件愉棱,告訴
ConditionEvaluationReport 條件評估報告器對象來記錄符合條件的自動配置類;
【5】 最后再將符合條件的自動配置類返回哲戚。

總結(jié)了 getAutoConfigurationEntry 方法主要的邏輯后奔滑,我們再來細看一下
AutoConfigurationImportSelector 的 filter 方法:

List<String> filter(List<String> configurations) {
    long startTime = System.nanoTime();
    
    // 將從spring.factories中獲取的自動配置類轉(zhuǎn)出字符串數(shù)組 
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    Iterator var6 = this.filters.iterator();

    int i;
    while(var6.hasNext()) {
        AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var6.next();
        
        // 判斷各種filter來判斷每個candidate(這里實質(zhì)要通過candidate(自動配置類)拿到其標注的 
// @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里面的注解值)是否匹配, 
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);

        // 遍歷match數(shù)組顺少,注意match順序跟candidates的自動配置類一一對應
        for(i = 0; i < match.length; ++i) {
            // 若有不匹配的話 
            if (!match[i]) {
                candidates[i] = null;
                // 標注skipped為true 
                skipped = true;
            }
        }
    }

    // 這里表示若所有自動配置類經(jīng)過OnBeanCondition朋其,OnClassCondition和 OnWebApplicationCondition過濾后,全部都匹配的話脆炎,則全部原樣返回
    if (!skipped) {
        return configurations;
    } else {
        // 建立result集合來裝匹配的自動配置類 
        List<String> result = new ArrayList(candidates.length);
        String[] var12 = candidates;
        int var14 = candidates.length;

        for(i = 0; i < var14; ++i) {
            String candidate = var12[i];
            // 符合條件的自動配置類梅猿,此時添加到result集合中 
            if (candidate != null) {
                result.add(candidate);
            }
        }

        if (AutoConfigurationImportSelector.logger.isTraceEnabled()) {
            int numberFiltered = configurations.size() - result.size();
            AutoConfigurationImportSelector.logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
        }
        // 最后返回符合條件的自動配置類
        return result;
    }
}

AutoConfigurationImportSelector 的 filter 方法主要做的事情就是調(diào)用AutoConfigurationImportFilter 接口的match 方法來判斷每一個自動配置類上的條件注解(若有的話) @ConditionalOnClass ,@ConditionalOnBean 或 @ConditionalOnWebApplication 是否滿足條件,若滿足秒裕,則返回true袱蚓,說明匹配,若不滿足,則返回false說明不匹配几蜻。

我們現(xiàn)在知道 AutoConfigurationImportSelector 的 filter 方法主要做了什么事情就行了喇潘,現(xiàn)在先不用研究的過深

有選擇的導入自動配置類
this.group.selectImports 方法是如何進一步有選擇的導入自動配置類的。

public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    } else {
        // 這里得到所有要排除的自動配置類的set集合
        Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
        // 這里得到經(jīng)過濾后所有符合條件的自動配置類的set集合
        Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
        
        // 移除掉要排除的自動配置類 
        processedConfigurations.removeAll(allExclusions);
        // 對標注有@Order注解的自動配置類進行排序
        return (Iterable)this.sortAutoConfigurations(processedConfigurations, this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
            return new Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
        }).collect(Collectors.toList());
    }
}

selectImports 方法的自動配置類再進一步排除 exclude 的自動配置類梭稚,然后再排序
最后颖低,我們再總結(jié)下SpringBoot自動配置的原理,主要做了以下事情:

  1. 從spring.factories配置文件中加載自動配置類弧烤;
  2. 加載的自動配置類中排除掉 @EnableAutoConfiguration 注解的 exclude 屬性指定的自動配置類忱屑;
  3. 然后再用 AutoConfigurationImportFilter 接口去過濾自動配置類是否符合其標注注解(若有標注的話) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的條件,若都符合的話則返回匹配結(jié)果暇昂;
  4. 然后觸發(fā) AutoConfigurationImportEvent 事件莺戒,告訴 ConditionEvaluationReport 條件評估報告器對象來分別記錄符合條件和 exclude 的自動配置類。
  5. 最后spring再將最后篩選后的自動配置類導入IOC容器中

畫圖梳理一下這個流程


image.png

總結(jié):

  • 在當前Spring Boot工程中话浇,各個組件(jar包)脏毯,都會存在一個名稱為META-INF的目錄,目錄中有一個sping.factories文件幔崖。
  • 在這個文件中會配置工廠類的全路徑如MyBatis中配置了org.mybatis.spring.boot.autoconfigure.MyBatisAutoConfiguration食店。
  • Spring Boot 通過@EnableAutoConfiguration注解收集配置工廠類
  • 由Spring創(chuàng)建bean實例存到IoC容器中渣淤。
image

條件注解

@Conditional是Spring4新提供的注解,它的作用是按照一定的條件進行判斷吉嫩,滿足條件給容器注冊bean价认。

  • @ConditionalOnBean:僅僅在當前上下文中存在某個對象時,才會實例化一個Bean自娩。
  • @ConditionalOnClass:某個class位于類路徑上用踩,才會實例化一個Bean。
  • @ConditionalOnExpression:當表達式為true的時候忙迁,才會實例化一個Bean脐彩。基于SpEL表達式 的條件判斷姊扔。
  • @ConditionalOnMissingBean:僅僅在當前上下文中不存在某個對象時惠奸,才會實例化一個Bean。
  • @ConditionalOnMissingClass:某個class類路徑上不存在的時候恰梢,才會實例化一個Bean佛南。
  • @ConditionalOnNotWebApplication:不是web應用,才會實例化一個Bean嵌言。
  • @ConditionalOnWebApplication:當項目是一個Web項目時進行實例化嗅回。
  • @ConditionalOnNotWebApplication:當項目不是一個Web項目時進行實例化。
  • @ConditionalOnProperty:當指定的屬性有指定的值時進行實例化摧茴。
  • @ConditionalOnJava:當JVM版本為指定的版本范圍時觸發(fā)實例化绵载。
  • @ConditionalOnResource:當類路徑下有指定的資源時觸發(fā)實例化。
  • @ConditionalOnJndi:在JNDI存在的條件下觸發(fā)實例化蓬蝶。
  • @ConditionalOnSingleCandidate:當指定的Bean在容器中只有一個尘分,或者有多個但是指定了首 選的Bean時觸發(fā)實例化。

@ComponentScan

  • @ConponentScan用于配置掃描Spring的掃包基礎(chǔ)路徑的注解丸氛。默認掃描被這個注解標記的類的目錄及子目錄培愁。
  • @SpringBootApplication中包含了@ConponentScan,因此它具備@ConponentScan相同的功能缓窜,因此被@SpringBootApplication注解標記了的類的類路徑及其子路徑上的bean都能被掃描到定续。這也是為什么非這個目錄及其子目錄中的bean不能被掃描的原因。

SpringApplication初始化過程

@SpringBootApplication中的@ComponentScan是在什么時候被掃描到的禾锤?
SpringBoot項目的main方法:


@SpringBootApplication
@EnableConfigurationProperties
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }

}

從run方法開始追蹤:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}

調(diào)用了另外一個run方法:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}

這個run方法構(gòu)建了一個SpringApplication類的實例私股,我們繼續(xù)來看下SpringApplication類的構(gòu)造函數(shù)

SpringApplication構(gòu)造函數(shù)

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 = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 1、推斷應用類型:servlet恩掷、reactive倡鲸、none
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
    // 2、初始化META-INF/spring.factories文件中配置的ApplicationContextInitializer
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 3黄娘、初始化classpath下的所有已配置的ApplicationListener
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 4峭状、推斷出main方法的類名克滴。
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

判斷應用類型

private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
        return REACTIVE;
    } else {
        String[] var0 = SERVLET_INDICATOR_CLASSES;
        int var1 = var0.length;

        for(int var2 = 0; var2 < var1; ++var2) {
            String className = var0[var2];
            if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                return NONE;
            }
        }

        return SERVLET;
    }
}

判斷應用類型主要分三個步驟:

  • 1、判斷類路徑中有沒有DispatcherHandler,DispatcherServlet,ServletContainer:如果有DispatcherHandler优床,沒有DispatcherServlet,ServletContainer則是REACTIVE(響應式編程)類型的應用劝赔。
  • 2、如果不滿足條件1胆敞,如果類路徑不包含Servlet類或不包含ConfigurableWebApplicationContext類着帽,則是NONE(不是一個web應用)類型應用。
  • 如果條件1移层、2都不滿足仍翰,那它就是一個Servlet(web應用,需要啟動內(nèi)置servlet容器)應用幽钢。

初始化ApplicationContextInitializer

this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = this.getClassLoader();
    // 從MATA-INF/spring.factories文件中讀取所有ApplicationContextInitializer類全路徑為key的所有類全路徑
    Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 反射實例化這些ApplicationContextInitializer類
    List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

主要完成了兩步操作:

  • 從MATA-INF/spring.factories文件中讀取所有ApplicationContextInitializer類全路徑為key的所有類全路徑歉备。
  • 反射實例化上一步獲取到的類傅是,并放入到一個集合中匪燕。

初始化classpath下的所有ApplicationListener

this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
``
加載過程跟初始化器一樣
主要完成了兩步操作:
- 從MATA-INF/spring.factories文件中讀取所有ApplicationListener類全路徑為key的所有類全路徑。
- 反射實例化上一步獲取到的類喧笔,并放入到一個集合中帽驯。

#### 根據(jù)調(diào)用棧,推斷出main方法的類
```java
this.mainApplicationClass = this.deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
        StackTraceElement[] var2 = stackTrace;
        int var3 = stackTrace.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            StackTraceElement stackTraceElement = var2[var4];
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    } catch (ClassNotFoundException var6) {
    }

    return null;
}

這一步的主要功能是:

  • 根據(jù)調(diào)用棧书闸,找到main方法所在類的class對象尼变,存儲到成員變量。

SpringApplication.run方法流程介紹

SpringApplication對象構(gòu)建完后調(diào)用run方法浆劲,run方法代碼如下:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    //從META-INF/spring.factories中獲取監(jiān)聽器 
    //1嫌术、獲取并啟動監(jiān)聽器
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //2、構(gòu)造應用上下文環(huán)境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        //3牌借、初始化應用上下文
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        //4度气、刷新應用上下文前的準備階段 
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //5、刷新應用上下文
        this.refreshContext(context);
        //6膨报、刷新應用上下文后的擴展接口
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        listeners.started(context);
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

在以上的代碼中磷籍,啟動過程中的重要步驟共分為六步 :

  • 1、獲取并啟動監(jiān)聽器
  • 2现柠、構(gòu)造應用上下文環(huán)境
  • 3院领、初始化應用上下文
  • 4、刷新應用上下文前的準備階段
  • 5够吩、刷新應用上下文
  • 6比然、刷新應用上下文后的擴展接口

獲取并啟動監(jiān)聽器

事件機制在Spring是很重要的一部分內(nèi)容,通過事件機制我們可以監(jiān)聽Spring容器中正在發(fā)生的一些事件周循,同樣也可以自定義監(jiān)聽事件强法。Spring的事件為Bean和Bean之間的消息傳遞提供支持扒寄。當一個對象處理完某種任務(wù)后,通知另外的對象進行某些處理拟烫,常用的場景有進行某些操作后發(fā)送通知该编,消息、郵件等情況硕淑。

獲取監(jiān)聽器的入口代碼:

SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);

進入到方法中:


private SpringApplicationRunListeners getRunListeners(String[] args) {
   Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
   return new SpringApplicationRunListeners(logger,
         // 從META-INF/spring.factories文件中獲取監(jiān)聽器
         getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
         this.applicationStartup);
}

從代碼中看到课竣,在構(gòu)建SpringApplicationRunListeners實例之前調(diào)用了一個我們非常熟悉的方法:getSpringFactoriesInstances,該方法的主要作用是從spring.factories文件中加載SpringApplicationRunListener類全路徑為key的所有類,并實例化置媳。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   // 根據(jù)傳入的type類型于樟,使用type的類名全路徑作為key,到META-INF/spring.factories文件中去查找值拇囊,并放到names中
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   // 將查找到的類通過反射實例化
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   // 按order注解順序排序
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

獲取到監(jiān)聽器后迂曲,啟動監(jiān)聽器:

listeners.starting(bootstrapContext, this.mainApplicationClass);

構(gòu)造應用上下文環(huán)境

用上下文環(huán)境包括什么呢?包括計算機的環(huán)境寥袭,Java環(huán)境路捧,Spring的運行環(huán)境,Spring項目的配置(在SpringBoot中就是那個熟悉的application.properties/yml)等等传黄。


private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    //創(chuàng)建并配置相應的環(huán)境 
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    //根據(jù)用戶配置杰扫,配置 environment系統(tǒng)環(huán)境 
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    // 啟動相應的監(jiān)聽器,其中一個重要的監(jiān)聽器 ConfigFileApplicationListener 就是加載項目配置文件的監(jiān)聽器膘掰。
    ConfigurationPropertySources.attach((Environment)environment);
    listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
    DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
    Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }

    ConfigurationPropertySources.attach((Environment)environment);
    return (ConfigurableEnvironment)environment;
}

getOrCreateEnvironment方法做了什么章姓?

private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   switch (this.webApplicationType) {
   case SERVLET:
      // 如果應用類型是SERVLET,則創(chuàng)建Servlet類型的環(huán)境
      return new ApplicationServletEnvironment();
   case REACTIVE:
      // 如果應用類型是響應式編程的,則創(chuàng)建Reactive類型的環(huán)境
      return new ApplicationReactiveWebEnvironment();
   default:
      return new ApplicationEnvironment();
   }
}

ApplicationServletEnvironment類型的繼承關(guān)系:


image
/**
 * Template method delegating to
 * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
 * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
 * Override this method for complete control over Environment customization, or one of
 * the above for fine-grained control over property sources or profiles, respectively.
 * @param environment this application's environment
 * @param args arguments passed to the {@code run} method
 * @see #configureProfiles(ConfigurableEnvironment, String[])
 * @see #configurePropertySources(ConfigurableEnvironment, String[])
 */
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
   if (this.addConversionService) {
      environment.setConversionService(new ApplicationConversionService());
   }
   // 將main方法的參數(shù)分組到SimpleCommandLinePropertySource中
   configurePropertySources(environment, args);
   // 激活相關(guān)的配置文件
   configureProfiles(environment, args);
}

初始化應用上下文

/**
 *用于創(chuàng)建 {@link ApplicationContext} 的策略方法
 * Strategy method used to create the {@link ApplicationContext}. By default this
 * method will respect any explicitly set application context class or factory before
 * falling back to a suitable default.
 * @return the application context (not yet refreshed)
 * @see #setApplicationContextClass(Class)
 * @see #setApplicationContextFactory(ApplicationContextFactory)
 */
protected ConfigurableApplicationContext createApplicationContext() {
    // 根據(jù)應用類型創(chuàng)建對應的 ApplicationContext實例
   return this.applicationContextFactory.create(this.webApplicationType);
}

調(diào)試進入:

org.springframework.boot.ApplicationContextFactory

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
   try {
      switch (webApplicationType) {
      case SERVLET:
          // 應用類型是Servlet時识埋,new一個AnnotationConfigServletWebServerApplicationContext上下文實例對象
         return new AnnotationConfigServletWebServerApplicationContext();
      case REACTIVE:
         return new AnnotationConfigReactiveWebServerApplicationContext();
      default:
         return new AnnotationConfigApplicationContext();
      }
   }
   catch (Exception ex) {
      throw new IllegalStateException("Unable create a default ApplicationContext instance, "
            + "you may need a custom ApplicationContextFactory", ex);
   }
};

AnnotationConfigServletWebServerApplicationContext的類繼承關(guān)系:


image

GenericApplicationContext是AnnotationConfigServletWebServerApplicationContext的父類凡伊。

創(chuàng)建對象的時候,創(chuàng)建了一個reader和一個scanner:

/**
 * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
 * to be populated through {@link #register} calls and then manually
 * {@linkplain #refresh refreshed}.
 */
public AnnotationConfigServletWebServerApplicationContext() {
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

另外在調(diào)用AnnotationConfigServletWebServerApplicationContext構(gòu)造函數(shù)之前,要先調(diào)用父類的構(gòu)造函數(shù):

public GenericApplicationContext() {
    this.customClassLoader = false;
    this.refreshed = new AtomicBoolean();
    this.beanFactory = new DefaultListableBeanFactory();
}

AnnotationConfigServletWebServerApplicationContext中的IoC容器在其父類的構(gòu)造方法中被實例化窒舟。

image

刷新應用上下文前的準備階段

刷新應用上下文前的準備階段系忙,就是想上下文context中設(shè)置一些屬性,完成bean對象的創(chuàng)建辜纲。

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   // 設(shè)置容器環(huán)境
   context.setEnvironment(environment);
   // 執(zhí)行容器后置處理器笨觅,向context中設(shè)置值
   postProcessApplicationContext(context);
   // 遍歷初始化器,執(zhí)行初始化
   applyInitializers(context);
   // 向各個監(jiān)聽器去發(fā)送容器準備好的事件
   listeners.contextPrepared(context);
   bootstrapContext.close(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
   // 獲取所有啟動類
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);
}

Set<Object> sources = getAllSources();拿到啟動類耕腾,我們來看下它是怎么拿到的见剩?

public Set<Object> getAllSources() {
   Set<Object> allSources = new LinkedHashSet<>();
   // primarySources在SpringApplication的run方法中傳入的
   if (!CollectionUtils.isEmpty(this.primarySources)) {
      allSources.addAll(this.primarySources);
   }
   if (!CollectionUtils.isEmpty(this.sources)) {
      allSources.addAll(this.sources);
   }
   return Collections.unmodifiableSet(allSources);
}

從源碼中看到:它是將primarySources和sources兩個集合合并后返回。因此primarySources和sources中存放的就是啟動類扫俺。
下面看看它們在哪里被賦值的:
在SpringApplication的構(gòu)造方法中,將構(gòu)造方法中的參數(shù)直接添加到了primarySources集合中苍苞。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

另外有一個方法也涉及到往這個集合中添加值:

public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {
   this.primarySources.addAll(additionalPrimarySources);
}

但是通過findUseage發(fā)現(xiàn),在啟動流程中,這個方法還沒有被調(diào)用過羹呵。因此骂际,當前狀態(tài)下primarySources集合中只有SpringApplication.run(SpringBootDemoApplication.class, args);
參數(shù)中傳入的啟動類。

另外一個集合sources的只在一個方法中賦值:

public void setSources(Set<String> sources) {
   Assert.notNull(sources, "Sources must not be null");
   this.sources = new LinkedHashSet<>(sources);
}

這個方法只被SampleSpringXmlApplication類中的main方法調(diào)用冈欢,而Spring Boot的啟動類是通過SpringApplication的run方法啟動的歉铝。因此sources集合是空的。

Set<Object> sources = getAllSources();在Spring Boot工程啟動過程中凑耻,拿到的是SpringBoot啟動類太示,就是傳入到SpringApplication.run()方法的第一個參數(shù)。

驗證:
斷點打在run方法


image

primarySources 集合中只有一個SpringBootDemoApplication類對象


image

這個類對象正好是我們的啟動類


image

斷點打到getAllResource


image

allSources中只有我們的啟動類對象香浩,sources集合為空类缤,驗證了我們的結(jié)論。
image

接著看load()方法

/**
 * Load beans into the application context.
 * @param context the context to load beans into
 * @param sources the sources to load
 */
protected void load(ApplicationContext context, Object[] sources) {
   if (logger.isDebugEnabled()) {
      logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
   }

   // 創(chuàng)建BeanDefinitionLoader
   BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
   if (this.beanNameGenerator != null) {
      loader.setBeanNameGenerator(this.beanNameGenerator);
   }
   if (this.resourceLoader != null) {
      loader.setResourceLoader(this.resourceLoader);
   }
   if (this.environment != null) {
      loader.setEnvironment(this.environment);
   }
   loader.load();
}

在創(chuàng)建BeanDefinitionLoader之前邻吭,先獲取BeanDefinitionRegistery:

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
   if (context instanceof BeanDefinitionRegistry) {
      return (BeanDefinitionRegistry) context;
   }
   if (context instanceof AbstractApplicationContext) {
      return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
   }
   throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}

其實就是將context中的IOC容器直接拿出來餐弱,并且轉(zhuǎn)成BeanDefinitionRegistry。在前面看AnnotationConfigServletWebApplicationContext類繼承關(guān)系的時候知道囱晴,它實現(xiàn)了BeanDefinitionRegistry接口膏蚓,因此可以強轉(zhuǎn)。并且可以驗證這里就是AnnotationConfigServletWebApplicationContext速缆。


image

BeanDefinitionRegistry定義了很重要的方法registerBeanDefinition()降允,該方法將BeanDefinition 注冊進DefaultListableBeanFactory容器的beanDefinitionMap中。

現(xiàn)在再來看創(chuàng)建BeanDefinitionLoader的過程:

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
   return new BeanDefinitionLoader(registry, sources);
}

構(gòu)造方法:

/**
 * Create a new {@link BeanDefinitionLoader} that will load beans into the specified
 * {@link BeanDefinitionRegistry}.
 * @param registry the bean definition registry that will contain the loaded beans
 * @param sources the bean sources
 */
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
   Assert.notNull(registry, "Registry must not be null");
   Assert.notEmpty(sources, "Sources must not be empty");
   this.sources = sources;
   //注解形式的Bean定義讀取器 比如:@Configuration @Bean @Component @Controller @Service等等
   this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
   //XML形式的Bean定義讀取器
   this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
   this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
   //類路徑掃描器
   this.scanner = new ClassPathBeanDefinitionScanner(registry);
   //掃描器添加排除過濾器
   this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

在該方法中創(chuàng)建了reader和scanner艺糜,這兩個很熟悉的東西,后面肯定會使用reader去讀取配置幢尚,scaner進行包掃描破停。

接著看loader.load();方法:

/**
 * Load the sources into the reader.
 */
void load() {
    // 遍歷啟動類,分別進行l(wèi)oad
   for (Object source : this.sources) {
      load(source);
   }
}

private void load(Object source) {
   Assert.notNull(source, "Source must not be null");
   // 從calss加載
   if (source instanceof Class<?>) {
      load((Class<?>) source);
      return;
   }
   // 從resource加載
   if (source instanceof Resource) {
      load((Resource) source);
      return;
   }
   // 從package加載
   if (source instanceof Package) {
      load((Package) source);
      return;
   }
   // 從CharSequence加載
   if (source instanceof CharSequence) {
      load((CharSequence) source);
      return;
   }
   throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

Spring Boot應用啟動時會從class加載尉剩。

private void load(Class<?> source) {
   if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
      // Any GroovyLoaders added in beans{} DSL can contribute beans here
      GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
      ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
   }
   
   // 判斷啟動類是否符合注冊條件
   if (isEligible(source)) {
      this.annotatedReader.register(source);
   }
}

什么是符合注冊條件的:

private boolean isEligible(Class<?> type) {
    // 不是匿名的真慢,就是沒有名字的類。不是groovy閉包理茎。不是沒有構(gòu)造方法的
   return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type));
}

這里黑界,啟動類是符合注冊條件的。
所以啟動類會被注冊到IoC容器中皂林。

this.annotatedReader.register(source);

而annotatedReader是BeanDefinitionLoader構(gòu)造函數(shù)中被實例化的朗鸠,并且它是Spring Framework中的類,所以注冊過程是由Spring Framework完成的

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
   Assert.notNull(registry, "Registry must not be null");
   Assert.notEmpty(sources, "Sources must not be empty");
   this.sources = sources;
   // AnnotatedBeanDefinitionReader是Spring Framework中的組件
   this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
   this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
   this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
   this.scanner = new ClassPathBeanDefinitionScanner(registry);
   this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

刷新應用上下文

刷新上下文代碼:

private void refreshContext(ConfigurableApplicationContext context) {
   if (this.registerShutdownHook) {
      shutdownHook.registerApplicationContext(context);
   }
   // 核心方法
   refresh(context);
}

從源碼可以看到础倍,刷新上下文的核心方法是refresh方法

/**
 * Refresh the underlying {@link ApplicationContext}.
 * @param applicationContext the application context to refresh
 */
protected void refresh(ConfigurableApplicationContext applicationContext) {
   applicationContext.refresh();
}

直接直接調(diào)用了application的refresh方法

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        // 第?步:刷新前的預處理
        this.prepareRefresh();
        /* 
         第?步: 
         獲取BeanFactory烛占;默認實現(xiàn)是DefaultListableBeanFactory 
         加載BeanDefition 并注冊到 BeanDefitionRegistry 
         */
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        // 第三步:BeanFactory的預準備?作(BeanFactory進??些設(shè)置,?如context的類加載器等)
        this.prepareBeanFactory(beanFactory);

        try {
            // 第四步:BeanFactory準備?作完成后進?的后置處理?作
            this.postProcessBeanFactory(beanFactory);
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // 第五步:實例化并調(diào)?實現(xiàn)了BeanFactoryPostProcessor接?的Bean
            this.invokeBeanFactoryPostProcessors(beanFactory);
            // 第六步:注冊BeanPostProcessor(Bean的后置處理器),在創(chuàng)建bean的前后等執(zhí)?
            this.registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();
            // 第七步:初始化MessageSource組件(做國際化功能忆家;消息綁定犹菇,消息解析);
            this.initMessageSource();
            // 第?步:初始化事件派發(fā)器
            this.initApplicationEventMulticaster();
            // 第九步:?類重寫這個?法芽卿,在容器刷新的時候可以?定義邏輯
            this.onRefresh();
            // 第?步:注冊應?的監(jiān)聽器揭芍。就是注冊實現(xiàn)了ApplicationListener接?的監(jiān)聽器bean
            this.registerListeners();
            /* 
             第??步: 
             初始化所有剩下的?懶加載的單例bean 
             初始化創(chuàng)建?懶加載?式的單例Bean實例(未設(shè)置屬性) 
             填充屬性 
             初始化?法調(diào)?(?如調(diào)?afterPropertiesSet?法、init-method?法) 
             調(diào)?BeanPostProcessor(后置處理器)對實例bean進?后置處 
             */
            this.finishBeanFactoryInitialization(beanFactory);
            /* 
             第??步: 
             完成context的刷新卸例。主要是調(diào)?LifecycleProcessor的onRefresh()?法沼沈,并且發(fā)布事 
            件 (ContextRefreshedEvent) 
             */
            this.finishRefresh();
        } catch (BeansException var10) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
            }

            this.destroyBeans();
            this.cancelRefresh(var10);
            throw var10;
        } finally {
            this.resetCommonCaches();
            contextRefresh.end();
        }

    }
}

invokeBeanFactoryPostProcessors(beanFactory);(重點)
IoC容器的初始化過程包括三個步驟,在invokeBeanFactoryPostProcessors()方法中完成了IoC容
器初始化過程的三個步驟币厕。
1列另,第一步:Resource定位
在SpringBoot中,我們都知道他的包掃描是從主類所在的包開始掃描的旦装,prepareContext()
方法中页衙,會先將主類解析成BeanDefinition,然后在refresh()方法的
invokeBeanFactoryPostProcessors()方法中解析主類的BeanDefinition獲取basePackage的路
徑阴绢。這樣就完成了定位的過程店乐。其次SpringBoot的各種starter是通過SPI擴展機制實現(xiàn)的自動裝
配,SpringBoot的自動裝配同樣也是在invokeBeanFactoryPostProcessors()方法中實現(xiàn)的呻袭。還有
一種情況眨八,在SpringBoot中有很多的@EnableXXX注解,細心點進去看的應該就知道其底層是
@Import注解左电,在invokeBeanFactoryPostProcessors()方法中也實現(xiàn)了對該注解指定的配置類的
定位加載廉侧。
常規(guī)的在SpringBoot中有三種實現(xiàn)定位,第一個是主類所在包的篓足,第二個是SPI擴展機制實現(xiàn)
的自動裝配(比如各種starter)段誊,第三種就是@Import注解指定的類。(對于非常規(guī)的不說了)

2栈拖,第二步:BeanDefinition的載入
在第一步中說了三種Resource的定位情況连舍,定位后緊接著就是BeanDefinition的分別載入。
所謂的載入就是通過上面的定位得到的basePackage涩哟,SpringBoot會將該路徑拼接成:
classpath:com/lagou/**/.class這樣的形式索赏,然后一個叫做
xPathMatchingResourcePatternResolver的類會將該路徑下所有的.class文件都加載進來,然后
遍歷判斷是不是有@Component注解贴彼,如果有的話潜腻,就是我們要裝載的BeanDefinition。大致過
程就是這樣的了锻弓。

3砾赔、第三個過程:注冊BeanDefinition
這個過程通過調(diào)用上文提到的BeanDefinitionRegister接口的實現(xiàn)來完成。這個注冊過程把載入
過程中解析得到的BeanDefinition向IoC容器進行注冊。通過上文的分析暴心,我們可以看到妓盲,在IoC容
器中將BeanDefinition注入到一個ConcurrentHashMap中,IoC容器就是通過這個HashMap來持
有這些BeanDefinition數(shù)據(jù)的专普。比如DefaultListableBeanFactory 中的beanDefinitionMap屬性悯衬。
OK,總結(jié)完了檀夹,接下來我們通過代碼看看具體是怎么實現(xiàn)的筋粗。

刷新應用上下文后的擴展接口

/**
 * Called after the context has been refreshed.
 * @param context the application context
 * @param args the application arguments
 */
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

擴展接口,設(shè)計模式中的模板方法炸渡,默認為空實現(xiàn)娜亿。如果有自定義需求,可以重寫該方法蚌堵。比如打印一些啟動結(jié)束log买决,或者一些其它后置處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吼畏,一起剝皮案震驚了整個濱河市督赤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泻蚊,老刑警劉巖躲舌,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異性雄,居然都是意外死亡没卸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門毅贮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來办悟,“玉大人,你說我怎么就攤上這事滩褥。” “怎么了炫加?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵瑰煎,是天一觀的道長。 經(jīng)常有香客問我俗孝,道長酒甸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任赋铝,我火速辦了婚禮插勤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己农尖,他們只是感情好析恋,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盛卡,像睡著了一般助隧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滑沧,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天并村,我揣著相機與錄音,去河邊找鬼滓技。 笑死哩牍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的令漂。 我是一名探鬼主播膝昆,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洗显!你這毒婦竟也來了外潜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤挠唆,失蹤者是張志新(化名)和其女友劉穎处窥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玄组,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡耘戚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了弓颈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邑商。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖患膛,靈堂內(nèi)的尸體忽然破棺而出摊阀,到底是詐尸還是另有隱情,我是刑警寧澤踪蹬,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布胞此,位于F島的核電站,受9級特大地震影響跃捣,放射性物質(zhì)發(fā)生泄漏漱牵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一疚漆、第九天 我趴在偏房一處隱蔽的房頂上張望酣胀。 院中可真熱鬧刁赦,春花似錦、人聲如沸闻镶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽儒溉。三九已至宦焦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顿涣,已是汗流浹背波闹。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涛碑,地道東北人精堕。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像蒲障,于是被迫代替她去往敵國和親歹篓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容