SpringBoot原理分析(初級版本)

一、啓動類

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author: 98050
 * @Time: 2019-05-02 20:06
 * @Feature:
 */
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

發現特別的地方有兩個:

  • 註解:@SpringBootApplication
  • run方法:SpringApplication.run()

那麼分別來研究這兩個部分。

二、@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.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

先不去關注接口中定義的屬性,先研究以下三個註解:

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

這三個註解就是Spring Boot的核心,所以啓動器類也可以這樣寫:

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;

/**
 * @Author: 98050
 * @Time: 2019-05-02 20:06
 * @Feature:
 */
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

每次寫這3個比較累,所以寫一個@SpringBootApplication方便點。接下來分別介紹這3個Annotation。

2.1 @ComponentScan

@ComponentScan這個註解在Spring中很重要,它對應XML配置中的元素,@ComponentScan的功能其實就是自動掃描並加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。

我們可以通過basePackages等屬性來細粒度的定製@ComponentScan自動掃描的範圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描。

2.2 @SpringBootConfiguration

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

package org.springframework.boot;

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

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

其實 @SpringBootConfiguration的核心還是@Configuration

這裏的@Configuration對我們來說不陌生,它就是JavaConfig形式的Spring Ioc容器的配置類使用的那個@Configuration,SpringBoot社區推薦使用基於JavaConfig的配置形式,所以,這裏的啓動類標註了@Configuration之後,本身其實也是一個IoC容器的配置類。

2.3 @EnableAutoConfiguration

@EnableAutoConfiguration這個Annotation最爲重要,所以放在最後來解讀,大家是否還記得Spring框架提供的各種名字爲@Enable開頭的Annotation定義?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其實一脈相承,簡單概括一下就是:藉助@Import的支持,收集和註冊特定場景相關的bean定義

  • @EnableScheduling是通過@Import將Spring調度框架相關的bean定義都加載到IoC容器。
  • @EnableMBeanExport是通過@Import將JMX相關的bean定義加載到IoC容器。

而@EnableAutoConfiguration也是藉助@Import的幫助,將所有符合自動配置條件的bean定義加載到IoC容器,僅此而已!

//
// 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
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

    String[] excludeName() default {};
}

兩個比較重要的註解:

  • @AutoConfigurationPackage:自動配置包
  • @Import({AutoConfigurationImportSelector.class}): 導入自動配置的組件

2.3.1 @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})
public @interface AutoConfigurationPackage {
}

通過@Import將Registrar加載到Spring IOC容器中

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

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    }

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

(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName(),返回了當前主程序類的同級以及子級的包組件。

Application與controller、dao、service同級

demo與example同級

所以當啓動Application後,是不會加載demo的。所以在搭建springboot項目時,一定要把啓動器放在項目的最高級目錄中

2.3.2 @Import({AutoConfigurationImportSelector.class})

1556804669001

AutoConfigurationImportSelector 實現了 DeferredImportSelector ,DeferredImportSelector繼承了 ImportSelector

ImportSelector有一個方法爲:selectImports。在AutoConfigurationImportSelector中給出了實現:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        try {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            configurations = this.sort(configurations, autoConfigurationMetadata);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        } catch (IOException var6) {
            throw new IllegalStateException(var6);
        }
    }
}

關注一下這句代碼:

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
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;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            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();
                    List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                    result.addAll((String)entry.getKey(), factoryClassNames);
                }
            }

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

這個方法的功能其實就是去加載 **“META-INF/spring.factories”**外部文件。

這個外部文件,有很多自動配置的類。如下:

1556806242329

總結:

通過EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器。就像一隻“八爪魚”一樣。

所以SpringFactoriesLoader是非常重要的一個類,下面對它進行分析

2.3.3 SpringFactoriesLoader詳解

藉助於Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!

SpringFactoriesLoader屬於Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置。

1556806882017

配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作爲查找的Key,獲取對應的一組@Configuration類

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;
}

this.getSpringFactoriesLoaderFactoryClass()

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

1556807205726

上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內容,可以很好地說明問題。

所以,@EnableAutoConfiguration自動配置的魔法騎士就變成了:從classpath中搜尋所有的META-INF/spring.factories配置文件,並將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對應的配置項通過反射(Java Refletion)實例化爲對應的標註了@Configuration的JavaConfig形式的IoC容器配置類,然後彙總爲一個並加載到IoC容器。

2.4 默認配置的原理

2.4.1 默認配置類

@EnableAutoConfiguration會開啓SpringBoot的自動配置,並且根據你引入的依賴來生效對應的默認配置。那麼問題來了:

  • 這些默認配置是在哪裏定義的呢?
  • 爲何依賴引入就會觸發配置呢?

需要注意項目中引入的一個依賴:spring-boot-autoconfigure,其中定義了大量自動配置類:

1556809179729

1556809213048

非常多,幾乎涵蓋了現在主流的開源框架。來看一個我們熟悉的,例如SpringMVC,查看mvc 的自動配置類:

1556809282699

打開WebMvcAutoConfiguration:

1556809408300

我們看到這個類上的4個註解:

  • @Configuration:聲明這個類是一個配置類

  • @ConditionalOnWebApplication(type = Type.SERVLET)

    滿足項目的類是是Type.SERVLET類型,也就是一個普通web工程,

  • @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })

    這裏的條件是OnClass,也就是滿足以下類存在:Servlet、DispatcherServlet、WebMvcConfigurer,其中Servlet只要引入了tomcat依賴自然會有,後兩個需要引入SpringMVC纔會有。這裏就是判斷你是否引入了相關依賴,引入依賴後該條件成立,當前類的配置纔會生效!

  • @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

    這個條件與上面不同,OnMissingBean,是說環境中沒有指定的Bean這個才生效。其實這就是自定義配置的入口,也就是說,如果我們自己配置了一個WebMVCConfigurationSupport的類,那麼這個默認配置就會失效!

接着,我們查看該類中定義了什麼:

視圖解析器

1556809827948

處理器適配器

1556809947047

2.4.2 默認配置屬性

另外,這些默認配置的屬性來自哪裏呢?

1556810239953

這裏通過@EnableAutoConfiguration註解引入了兩個屬性:WebMvcProperties和ResourceProperties。這不正是SpringBoot的屬性注入玩法嘛。

查看這兩個屬性類:

在WebMvcProperties中找到了內部資源視圖解析器的prefix和suffix屬性。

1556810363718

ResourceProperties中主要定義了靜態資源(.js,.html,.css等)的路徑:

1556810437699

如果我們要覆蓋這些默認屬性,只需要在application.properties中定義與其前綴prefix和字段名一致的屬性即可。

2.5 總結

SpringBoot爲我們提供了默認配置,而默認配置生效的條件一般有兩個:

  • 你引入了相關依賴
  • 你自己沒有配置

1)啓動器

所以,我們如果不想配置,只需要引入依賴即可,而依賴版本我們也不用操心,因爲只要引入了SpringBoot提供的stater(啓動器),就會自動管理依賴及版本了。

因此,玩SpringBoot的第一件事情,就是找啓動器,SpringBoot提供了大量的默認啓動器。

2)全局配置

另外,SpringBoot的默認配置,都會讀取默認屬性,而這些屬性可以通過自定義application.properties文件來進行覆蓋。這樣雖然使用的還是默認配置,但是配置中的值改成了我們自定義的。

因此,玩SpringBoot的第二件事情,就是通過application.properties來覆蓋默認屬性值,形成自定義配置。我們需要知道SpringBoot的默認屬性key,非常多。

最後通過一個簡單的流程圖總結一下@SpringBootApplication的功能。

三、SpringApplication.run執行流程

從SpringApplication的run方法的開始探究執行流程:

3.1 構造SpringApplication實例

如果我們使用的是SpringApplication的靜態run方法,那麼,這個方法裏面首先要創建一個SpringApplication對象實例,然後調用這個創建好的SpringApplication的實例方法。

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

在SpringApplication實例初始化的時候,它會提前做幾件事情:

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

從源碼上來看,主要是deduceFromClasspath();getSpringFactoriesInstances(xxx.class);deduceMainApplicationClass();這三個方法,我們一個一個來看。

1、deduceFromClasspath

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;
    }
}
// 判斷給定的類是否能夠加載,就是說類路徑下是否存在給定的類
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        forName(className, classLoader);
        return true;
    } catch (IllegalAccessError var3) {
        throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
    } catch (Throwable var4) {
        return false;
    }
}

如果org.springframework.web.reactive.DispatcherHandler能夠被加載且org.springframework.web.servlet.DispatcherServlet不能夠被加載,那麼斷定web應用類型是REACTIVE;如果javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext任意一個不能被加載,那麼斷定web應用類型是NONE;如果不能斷定是REACTIVE和NONE,那麼就是SERVLET類型;三種類型的含義:

NONE:不需要web容器的環境下運行,也就是普通工程

SERVLET:基於servlet的web項目

REACTIVE:響應式web應用,reactive web是Spring5版本的新特性

3.2 調用SpringApplication.run方法

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        this.refreshContext(context);
        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, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }

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

接下來進行具體方法分析

  • StopWatch類:計時類,計算SpringBoot應用的啓動時間。

  • SpringBootExceptionReporter類:是一個回調接口,用於支持SpringApplication啓動錯誤的自定義報告。

  • configureHeadlessProperty()方法:配置Headless模式配置,該模式下系統缺少顯示設備、鼠標或鍵盤,而服務器端往往需要在該模式下工作。

3.2.1 getRunListeners(args)方法

獲取SpringApplicationRunListeners類,是SpringApplicationRunListener類的集合。

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
    return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

通過ClassLoader.getResources加載META-INF/spring.factories路徑下的文件信息,從中找key爲SpringApplicationRunListener對應類,並實例化。

3.2.2 starting()方法

public void starting() {
    Iterator var1 = this.listeners.iterator();

    while(var1.hasNext()) {
        SpringApplicationRunListener listener = (SpringApplicationRunListener)var1.next();
        listener.starting();
    }

}

發佈ApplicationStartedEvent事件。

  • DefaultApplicationArguments(args)類:提供訪問運行一個SpringApplication的arguments的訪問入口。

3.2.3 prepareEnvironment(listeners,applicationArguments)方法

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    //1.得到環境對象ConfigurableEnvironment,沒有則創建一個
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    //2.配置環境信息(激活環境,通過從系統環境變量裏取)
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    //3.發佈ApplicationEnvironmentPreparedEvent事件,加載配置文件
    listeners.environmentPrepared((ConfigurableEnvironment)environment);
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }

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

創建和配置Environment,同時在調用AbstractEnvironment構造函數時生成PropertySourcesPropertyResolver類。

1、根據webApplicationType來創建相應的環境,默認創建StandardEnvironment()

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    } else {
        switch(this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
        }
    }
}

2、配置環境信息

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService)conversionService);
    }

    this.configurePropertySources(environment, args);
    // 配置ConfigurableEnvironment中的激活屬性
    this.configureProfiles(environment, args);
}

先設置類型轉換的服務接口,然後再配置環境信息

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    }

    if (this.addCommandLineProperties && args.length > 0) {
        String name = "commandLineArgs";
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        } else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }

}

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    environment.getActiveProfiles();
    // additionalProfiles是項目啓動時在main中SpringApplication.setAdditionalProfiles("")配置的
    Set<String> profiles = new LinkedHashSet(this.additionalProfiles);
    // 獲取環境變量中設置的spring.profiles.active屬性
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    // 賦值 activeProfiles
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

3、發佈ApplicationEnvironmentPreparedEvent事件,加載Spring配置文件信息,例如application.properties

listeners.environmentPrepared((ConfigurableEnvironment)environment);

該事件會在ConfigFileApplicationListener監聽器中處理,具體請看ConfigFileApplicationListener類,最終會調用ConfigFileApplicationListener中的內部類Loader加載配置信息。

1557136223672

public void load() {
	this.profiles = new LinkedList<>();
	this.processedProfiles = new LinkedList<>();
	this.activatedProfiles = false;
	this.loaded = new LinkedHashMap<>();
	// 初始化默認激活環境
	initializeProfiles();
	while (!this.profiles.isEmpty()) {
		Profile profile = this.profiles.poll();
		if (profile != null && !profile.isDefaultProfile()) {
			addProfileToEnvironment(profile.getName());
		}
		// 載入配置文件
		load(profile, this::getPositiveProfileFilter,
				addToLoaded(MutablePropertySources::addLast, false));
		this.processedProfiles.add(profile);
	}
	resetEnvironmentProfiles(this.processedProfiles);
	load(null, this::getNegativeProfileFilter,
	addToLoaded(MutablePropertySources::addFirst, true));
	addLoadedPropertySources();
}


private void initializeProfiles() {
	// The default profile for these purposes is represented as null. We add it
	// first so that it is processed first and has lowest priority.
	// 增加一個null的環境變量
	this.profiles.add(null);
	// 從當前系統環境變量裏獲取激活的環境
	Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
   // 獲取排除系統環境變量裏的激活環境信息設置SpringApplication.setAdditionalProfiles("");
	this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
	// 合併以上得到的激活環境信息,可以看出來從系統環境變量得到的會在之後加載,所以會覆蓋以前的
	addActiveProfiles(activatedViaProperty);
	// 如果profiles長度爲1,則設置一個default激活環境
	if (this.profiles.size() == 1) { // only has null profile
		for (String defaultProfileName : this.environment.getDefaultProfiles()) {
			Profile defaultProfile = new Profile(defaultProfileName, true);
			this.profiles.add(defaultProfile);
		}
	}
}


private void load(Profile profile, DocumentFilterFactory filterFactory,DocumentConsumer consumer) {
	getSearchLocations().forEach((location) -> {
		boolean isFolder = location.endsWith("/");
		// 加載名字
		Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
		names.forEach(
				(name) -> load(location, name, profile, filterFactory, consumer));
	});
}
private Set<String> getSearchLocations() {
    // 如果是環境變量中配置的有spring.config.location,則直接返回該地址
    if (this.environment.containsProperty("spring.config.location")) {
                return this.getSearchLocations("spring.config.location");
            } else {
        // 如果是環境變量中沒有配置spring.config.location,則先從spring.config.additional-location中加載,再從默認中加載
                Set<String> locations = this.getSearchLocations("spring.config.additional-location");
                locations.addAll(this.asResolvedSet(ConfigFileApplicationListener.this.searchLocations, "classpath:/,classpath:/config/,file:./,file:./config/"));
                return locations;
            }
}
private Set<String> getSearchNames() {
  // 如果配置了spring.config.name直接返回,不在啓用默認的
	if (this.environment.containsProperty("spring.config.name")) {
                String property = this.environment.getProperty("spring.config.name");
                return this.asResolvedSet(property, (String)null);
            } else {
                return this.asResolvedSet(ConfigFileApplicationListener.this.names, "application");
            }
}

SpringApplication從application.properties以下位置的文件加載屬性並將它們添加到Spring Environment:

  1. 一個/config當前目錄的子目錄
  2. 當前目錄當前目錄
  3. 一個類路徑/config包
  4. 類路徑根類路徑根
  • **configureIgnoreBeanInfo(environment)**方法:配置系統IgnoreBeanInfo屬性。

  • printBanner(environment)方法:打印啓動的圖形,返回Banner接口實現類。

3.2.4 createApplicationContext()方法

根據SpringApplication構造方法生成的webApplicationType變量創建一個ApplicationContext,默認生成AnnotationConfigApplicationContext。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch(this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                break;
            case REACTIVE:
                contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                break;
            default:
                contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException var3) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
        }
    }

    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

(1)時序圖

1557145511355

(2)分析

a. 方法根據applicationContextClass變量是否爲空,決定是否執行下面的switch語句,而applicationContextClass變量是由SpringApplicationBuilder類中的contextClass()方法調用ApplicationContext的setApplicationContextClass()賦值的,默認爲空;

b. webApplicationType變量是SpringApplication.run()方法調用構造方法時賦值的;

c. switch語句通過反射根據webApplicationType生成對應的容器,分別是AnnotationConfigServletWebServerApplicationContext、AnnotationConfigReactiveWebServerApplicationContext以及AnnotationConfigApplicationContext的,默認生成的是AnnotationConfigApplicationContext,同時,其構造函數生成AnnotedBeanDefinitonReader和ClassPathBeanDefinitionScanner類;

d. 最後,通過BeanUtils工具類將獲取到的容器類轉換成ConfigurableApplicationContext類,返回給應用使用。

  • **getSpringFactoriesInstances(SpringBootExceptionRepoter.class, new Class[] {ConfigurableApplicationContext.class }, context)**方法:獲取SpringBootExceptionReporter類的集合。

3.2.5 prepareContext()方法

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
	// ⑴.對ApplicationContext設置環境變量;
	context.setEnvironment(environment);
	// ⑵.配置屬性ResourceLoader和ClassLoader屬性;
	postProcessApplicationContext(context);
	// ⑶.循環初始化繼承ApplicationContextInitializer接口的類
	applyInitializers(context);
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}

	// Add boot specific singleton beans
	context.getBeanFactory().registerSingleton("springApplicationArguments",
			applicationArguments);
	if (printedBanner != null) {
		context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
	}

	// Load the sources
	Set<Object> sources = getSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[sources.size()]));
	listeners.contextLoaded(context);
}

@Override
public void setEnvironment(ConfigurableEnvironment environment) {
	super.setEnvironment(environment);
	this.reader.setEnvironment(environment);
	this.scanner.setEnvironment(environment);
}

protected void applyInitializers(ConfigurableApplicationContext context) {
    Iterator var2 = this.getInitializers().iterator();

    while(var2.hasNext()) {
        ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }

}

設置Environment,在ApplicationContext中應用所有相關的後處理,在刷新之前將所有的ApplicationContextInitializers應用於上下文,設置SpringApplicationRunLIstener接口實現類實現多路廣播Spring事件,添加引導特定的單例(SpringApplicationArguments, Banner),創建DefaultListableBeanFactory工廠類,從主類中定位資源並將資源中的bean加載進入ApplicationContext中,向ApplicationContext中添加ApplicationListener接口實現類。

3.2.6 refreshContext(context)方法

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        //1.準刷新上下文環境
        this.prepareRefresh();
        //2.初始化BeanFactory
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        //3.對BeanFactory進行各種填充
        this.prepareBeanFactory(beanFactory);

        try {
            //4.子類覆蓋方法做額外的處理,這裏會調用子類AnnotationConfigServletWebServerApplicationContext注入
            this.postProcessBeanFactory(beanFactory);
            //5.激活各種BeanFactory處理器
            this.invokeBeanFactoryPostProcessors(beanFactory);
            //6.註冊攔截Bean創建的Bean處理,這裏只是註冊、真正調用的時候是在拿Bean的時候
            this.registerBeanPostProcessors(beanFactory);
            //7.爲上下文初始化Message源,即不同語言的消息體,國際化處理
            this.initMessageSource();
            //8.初始化應用消息廣播器,並放到applicationEventMulticaster bean中
            this.initApplicationEventMulticaster();
            //9.留給子類來初始化其他bean
            this.onRefresh();
            //10.在所有註冊的bean中查找Listener bean,註冊到消息廣播中
            this.registerListeners();
            //11.初始化剩下的單實例(非惰性)
            this.finishBeanFactoryInitialization(beanFactory);
            //12.完成刷新過程,通知生命週期處理器lifecycleProcessor刷新過程,同時發出ContextRefreshEvent通知別人
            this.finishRefresh();
        } catch (BeansException var9) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
            }

            this.destroyBeans();
            this.cancelRefresh(var9);
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

調用AbstractApplicationContext的refresh()方法初始化DefaultListableBeanFactory工廠類。

重要方法分析:

1、this.obtainFreshBeanFactory()

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    this.refreshBeanFactory();
    return this.getBeanFactory();
}
//實現
protected final void refreshBeanFactory() throws BeansException {
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }

        try {
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            this.loadBeanDefinitions(beanFactory);
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }
//創建BeanFactory
protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
    }
//獲取頂級BeanFactory
@Nullable
    protected BeanFactory getInternalParentBeanFactory() {
        //將創建好的上下文對象ConfigurableApplicationContext轉換成BeanFactory類型返回
        return (BeanFactory)(this.getParent() instanceof ConfigurableApplicationContext ? ((ConfigurableApplicationContext)this.getParent()).getBeanFactory() : this.getParent());
    }
//獲取BeanFactory
public final ConfigurableListableBeanFactory getBeanFactory() {
    synchronized(this.beanFactoryMonitor) {
        if (this.beanFactory == null) {
            throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
        } else {
            return this.beanFactory;
        }
    }
}
  • 持有一個工廠類單例(默認爲DefaultListableBeanFactory),並依靠調用者通過工廠類單例或構造方法註冊bean;
  • 將在AbstarctApplicationContext類生成的id和在DefaultListableBeanFatory生成指向自身的弱引用,存儲進入一個由DefaultListableBeanFactory持有的Map中。

2、this.postProcessBeanFactory(beanFactory);

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}

這裏會調用子類AnnotationConfigServletWebServerApplicationContext注入

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    super.postProcessBeanFactory(beanFactory);
    if (this.basePackages != null && this.basePackages.length > 0) {
        this.scanner.scan(this.basePackages);
    }

    if (!this.annotatedClasses.isEmpty()) {
        this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
    }
}
//父類中的方法
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 添加後置處理器,在創建Tomcat時會利用這個後置處理器來初始化Tomcat Server類
    beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
    beanFactory.ignoreDependencyInterface(ServletContextAware.class);
    this.registerWebApplicationScopes();
}

添加後置處理器,在創建Tomcat時會利用這個後置處理器來初始化Tomcat Server類

3、onRefresh()

根據類的關係ServletWebServerApplicationContext->AbstractApplicationContext,在ServletWebServerApplicationContext實現了onRefresh方法

protected void onRefresh() {
    super.onRefresh();

    try {
        //創建WebServer容器
        this.createWebServer();
    } catch (Throwable var2) {
        throw new ApplicationContextException("Unable to start web server", var2);
    }
}

主要講解內置tomcat是什麼時候被初始化

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = this.getWebServerFactory();
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
    } else if (servletContext != null) {
        try {
            this.getSelfInitializer().onStartup(servletContext);
        } catch (ServletException var4) {
            throw new ApplicationContextException("Cannot initialize servlet context", var4);
        }
    }

    this.initPropertySources();
}

如果webServer和servletContext都爲空的時候,那麼就對webServer進行初始化,先拿到容器:

protected ServletWebServerFactory getWebServerFactory() {
    String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    if (beanNames.length == 0) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
    } else if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    } else {
        return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }
}

然後再調用getWebServer方法,它有三個不同的實現:

進入TomcatServletWebServerFactory中:

public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    this.customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    this.configureEngine(tomcat.getEngine());
    Iterator var5 = this.additionalTomcatConnectors.iterator();

    while(var5.hasNext()) {
        Connector additionalConnector = (Connector)var5.next();
        tomcat.getService().addConnector(additionalConnector);
    }

    this.prepareContext(tomcat.getHost(), initializers);
    return this.getTomcatWebServer(tomcat);
}

重要方法分析:

1、this.customizeConnector(connector)

protected void customizeConnector(Connector connector) {
    int port = this.getPort() >= 0 ? this.getPort() : 0;
    connector.setPort(port);
    if (StringUtils.hasText(this.getServerHeader())) {
        connector.setAttribute("server", this.getServerHeader());
    }

    if (connector.getProtocolHandler() instanceof AbstractProtocol) {
        this.customizeProtocol((AbstractProtocol)connector.getProtocolHandler());
    }

    if (this.getUriEncoding() != null) {
        connector.setURIEncoding(this.getUriEncoding().name());
    }

    connector.setProperty("bindOnInit", "false");
    if (this.getSsl() != null && this.getSsl().isEnabled()) {
        this.customizeSsl(connector);
    }

    TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(this.getCompression());
    compression.customize(connector);
    Iterator var4 = this.tomcatConnectorCustomizers.iterator();

    while(var4.hasNext()) {
        TomcatConnectorCustomizer customizer = (TomcatConnectorCustomizer)var4.next();
        customizer.customize(connector);
    }

}
  • **afterRefresh(CongigurableApplicationContext context, ApplicationArguments args)**方法:在刷新ApplicationContext之後調用,在SpringAppliation中是方法體爲空的函數,故不做任何操作。

  • **listeners.started(context)**方法:在ApplicationContext已經刷新及啓動後,但CommandLineRunners和ApplicationRunner還沒有啓動時,調用該方法向容器中發佈SpringApplicationEvent類或者子類。

  • callRunners(context, applicationArguments)方法:調用應用中ApplicationRunner和CommanLineRunner的實現類,執行其run方法,調用時機是容器啓動完成之後,可以用@Order註解來配置Runner的執行順序,可以用來讀取配置文件或連接數據庫等操作。

  • **listeners.running(context)**方法:在容器刷新以及所有的Runner被調用之後,run方法完成執行之前調用該方法。調用之前得到的SpringApplicationRunListeners類running(context)方法。

  • 最後,嚮應用中返回一個之前獲得到的ApplicationContext。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章