個人博客:haichenyi.com。感謝關注
需要搞清楚幾個重要的事件回調機制
配置在META-INF/spring.factories
- ApplicationContextInitializer
- SpringApplicationRunListener
只需要放在ioc容器中
-
ApplicationRunner
-
CommandLineRunner
新建一個空項目,就勾選web,找到啓動類,每個方法上面寫的註釋,可以看一下:
@SpringBootApplication
public class SellApplication {
public static void main(String[] args) {
SpringApplication.run(SellApplication.class, args);
}
}
//上面run方法點進來
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
看到這裏,就應該看到了,啓動流程分爲兩步
- 創建SpringApplication對象
- 運行run方法
創建SpringApplication對象
//上面的構造方法點進去
//這裏與1.5版本不一樣的地方就是,
//2.X這裏調用了重載的構造方法,而1.5這裏調用的是一個initialize()方法,這個方法裏面的內容,與下面兩個參數的重載方法差不多
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
//下面這個就是this調用的重載的構造方法
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
//保存主配置類,1.5裏面這裏有個非空判斷,用if做的,這裏換成的斷言做判斷
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判斷當前是否一個web應用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//從類路徑下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然後保存起 來
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//從類路徑下找到ETA‐INF/spring.factories配置的所有ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//從多個配置類中找到有main方法的主配置類
this.mainApplicationClass = deduceMainApplicationClass();
}
setInitializers()方法
看方法名就知道,這個是初始化方法,初始化什麼東西呢?再看傳的參數ApplicationContextInitializer,就是一開始我們提到的類。我們看這個是怎麼獲取的
//第一步:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
//第二步:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//看這裏的導入方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
//第三步:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
//第四步,就是這裏了。看這裏面的實現
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//一眼看過去。很明顯,這裏就是classLoader.getResources(),導入的本地的資源。看這個傳的參數,我放到這個方法下面去了
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
//這裏通過一個while循環,加載本地配置的ApplicationContextInitializer
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
所以,從上面的源碼,我們一步一步點擊進去看,我們就能發現,他最終都是加載到 META-INF/spring.factories 目錄下的 ApplicationContextInitializer 當然,到目前爲止這裏只是初始化
setListeners()方法
一眼就能看出來,這裏是設置監聽方法
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
一眼看過去,這個setListener方法傳的參數熟不熟悉?就是我們上面初始化的時候傳的參數是同一個方法。所以,這裏設置監聽設置哪些監聽方法也是META-INF/spring.factories 目錄下的listener方法,我們看一下這個文件內容:
這些都是是自動配置類的內容
運行Run方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//獲取SpringApplicationRunListeners;從類路徑下META‐INF/spring.factories
SpringApplicationRunListeners listeners = getRunListeners(args);
//回調所有的獲取SpringApplicationRunListener.starting()方法
listeners.starting();
try {
//封裝命令行參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//準備環境,創建環境完成後回調SpringApplicationRunListener.environmentPrepared();表示環境準 備完成
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//這裏是新增的,點擊去看,就是再properties文件中配置你需要忽略的bean
configureIgnoreBeanInfo(environment);
//這個是打印spring的logo banner圖
Banner printedBanner = printBanner(environment);
/創建ApplicationContext;這個下面有下介紹
context = createApplicationContext();
//看一下參數,這個就是做異常報告處理的
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//準備上下文環境;將environment保存到ioc中;而且applyInitializers();
//applyInitializers():回調之前保存的所有的ApplicationContextInitializer的initialize方法
//回調所有的SpringApplicationRunListener的contextPrepared();
//prepareContext運行完成以後回調所有的SpringApplicationRunListener的contextLoaded();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//s刷新容器;ioc容器初始化(如果是web應用還會創建嵌入式的Tomcat);Spring註解版
//掃描,創建,加載所有組件的地方;(配置類,組件,自動配置)
refreshContext(context);
//2.x裏面是空方法
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
//從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner進行回調 //ApplicationRunner先回調,CommandLineRunner再回調
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回這個IOC容器
return context;
}
getRunListeners()方法
我們看到上面第一個有註釋的位置:getRunListeners方法
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
方法熟悉嗎?就是我們上面初始化的時候調用的那個方法,只是這裏的參數傳的是:SpringApplicationRunListener,我們看最開始說的,這就是我們要了解的第二個內容,回調都是怎麼調用的
方法裏面的註釋也寫了,先獲取監聽事件,然後回調starting方法,我們看一下這個接口有那些回調方法:
public interface SpringApplicationRunListener {
default void starting() {
}
default void environmentPrepared(ConfigurableEnvironment environment) {
}
default void contextPrepared(ConfigurableApplicationContext context) {
}
default void contextLoaded(ConfigurableApplicationContext context) {
}
default void started(ConfigurableApplicationContext context) {
}
default void running(ConfigurableApplicationContext context) {
}
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
就這些回調,這裏還用了1.8的新特性,default關鍵字,接口裏面的方法可以有方法體
prepareEnvironment()
看到第二個寫註釋的位置,眼熟嗎?可不就是跟上面回調方法名字相同麼?我們點進去看一下
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//這裏劃重點,這裏就調用的environmentPrepared的回調方法
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
準備完環境之後,調用environmentPrepared的回調
createApplicationContext()
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
創建applicationContext,這裏跟1.5不一樣,1.5就只有兩種:一種是web的ioc容器,一種是默認的ioc容器。2.X這裏有三種:DEFAULT_CONTEXT_CLASS,DEFAULT_SERVLET_WEB_CONTEXT_CLASS,DEFAULT_REACTIVE_WEB_CONTEXT_CLASS,實際字符串比較長,可以去看一下源碼。然後用BeanUtils通過反射創建。
prepareContext()方法
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//將environment放到context中
context.setEnvironment(environment);
postProcessApplicationContext(context);
//初始化
applyInitializers(context);
//這裏回調contextPrepared方法
listeners.contextPrepared(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]));
//這裏回調contextLoaded()方法
listeners.contextLoaded(context);
}
準備上下文環境;將environment保存到ioc中;而且applyInitializers()
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
這裏就將我們一開始從配置文件裏面讀取,然後創建ApplicationContextInitializer初始化。
當環境準備好之後,就回調了SpringApplicationRunListener的contextPrepared();
當所有的都準備好了之後,回調SpringApplicationRunListener的contextLoaded();
到這裏,所有的環境都準備好了,需要打印的logo也加進去了。
refreshContext()
刷新容器這個方法,我們可以點到具體的功能實現裏面,可以看到,這裏就是掃描,創建,加載所有的組件,配置類,組件,自動配置等。
到這裏,這個方法創建完之後,所有的控制器就創建完了,所有的組件,bean等,都在控制檯打印出來了。如果是web應用,還會創建嵌入式的tomcat。我們spring boot項目內嵌tomcat,就是在這裏創建的。
afterRefresh()
1.5版本這個方法裏面回調的是callRunners方法,而2.X版本,現在這是個空方法裏面並沒有實現。callRunners被提出來了,放到了最後面。
started(),running()
在1.5版本這裏,也就是afterRefresh()之後,應該是調用的SpringApplicationRunListeners的finished()方法。
在2.X版本之後,去掉了finished方法,改成了調用started方法,然後調用running方法。我們上面有一個starting方法,從這裏名字就可以看出來,相當於,首先是正在啓動當中,然後就是啓動完成了,正在運行了。
callRunners()
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
從IOC容器中(不是配置文件)獲取所有的的ApplicationRunner和CommandLineRunner進行回調。這也是最開始說的兩個注意的地方
並且,這裏有個先後順序,先回調的ApplicationRunner,後回調的CommandLineRunner
這裏也是run方法,最後執行的地方。從這裏就是真正的開啓了run。
最後一步,返回那個context就是返回IOC容器對象。到這裏,我們的spring boot就啓動完成了。
這就是我們的spring boot的啓動原理。初始化,listener的回調,Runner的回調都說的很清楚。