一、啓動類
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})
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”**外部文件。
這個外部文件,有很多自動配置的類。如下:
總結:
通過EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應用將所有符合條件的@Configuration配置都加載到當前SpringBoot創建並使用的IoC容器。就像一隻“八爪魚”一樣。
所以SpringFactoriesLoader是非常重要的一個類,下面對它進行分析
2.3.3 SpringFactoriesLoader詳解
藉助於Spring框架原有的一個工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動配置功效才得以大功告成!
SpringFactoriesLoader屬於Spring框架私有的一種擴展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置。
配合@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;
}
上圖就是從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
,其中定義了大量自動配置類:
非常多,幾乎涵蓋了現在主流的開源框架。來看一個我們熟悉的,例如SpringMVC,查看mvc 的自動配置類:
打開WebMvcAutoConfiguration:
我們看到這個類上的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的類,那麼這個默認配置就會失效!
接着,我們查看該類中定義了什麼:
視圖解析器
處理器適配器
2.4.2 默認配置屬性
另外,這些默認配置的屬性來自哪裏呢?
這裏通過@EnableAutoConfiguration註解引入了兩個屬性:WebMvcProperties和ResourceProperties。這不正是SpringBoot的屬性注入玩法嘛。
查看這兩個屬性類:
在WebMvcProperties中找到了內部資源視圖解析器的prefix和suffix屬性。
ResourceProperties中主要定義了靜態資源(.js,.html,.css等)的路徑:
如果我們要覆蓋這些默認屬性,只需要在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加載配置信息。
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:
- 一個/config當前目錄的子目錄
- 當前目錄當前目錄
- 一個類路徑/config包
- 類路徑根類路徑根
-
**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)時序圖
(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。