本文將從Spring Boot 2.0的啟動(dòng)流程來解析其中的一些關(guān)鍵內(nèi)容墨叛,本文源碼的版本為spring-boot-starter-parent 2.4.6,不熟悉spring源碼的建議先熟悉下spring源碼崩泡,話不多說上代碼(女朋友之前吐槽很討厭上來直接就寫源碼的博客,后面盡量總結(jié)下流程和知識(shí)點(diǎn))贪嫂。
public static void main(String[] args) {
SpringApplication.run(MergePayApplication.class, args);
}
以上的代碼大家可能最熟悉不過了全景,這也是我們了解Spring Boot原理的入口,小伙伴最好可以跟著代碼進(jìn)行調(diào)試來加深印象(其實(shí)是檢查寫的內(nèi)容是否有錯(cuò)誤)骨望。
一硬爆、SpringApplication
1.構(gòu)造函數(shù)
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();
}
- resourceLoader 調(diào)用傳入的為null,暫時(shí)可以忽略擎鸠;
- primarySources 傳入的為run的第一個(gè)參數(shù)缀磕,本例子為MergePayApplication;
- webApplicationType 該方法會(huì)返回web應(yīng)用的三種類型分別為REACTIVE、SERVLET袜蚕、NONE糟把,本例子返回的為SERVLET;
- bootstrapRegistryInitializers牲剃、initializers遣疯、listeners這部分也是該方法的重點(diǎn)內(nèi)容,帶*的三行分別設(shè)置了三個(gè)List類型的變量颠黎;
- mainApplicationClass啟動(dòng)類本例子返回MergePayApplication類另锋;
private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
getSpringFactoriesInstances(Bootstrapper.class).stream()
.map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
.forEach(initializers::add);
initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
return initializers;
}
其實(shí)有的時(shí)候看源碼不用看到每一行都想去弄明白滞项,之前我也是這么看代碼狭归,但是到最后根本記不住大體的流程,滿腦子都是一些無關(guān)緊要的細(xì)節(jié)文判,就像上面代碼的BootstrapRegistryInitializer初看不知道是什么鬼过椎,但是接著看它后面的賦值和調(diào)用猜個(gè)八九不離十。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
終于到了重點(diǎn)的方法戏仓,該方法主要分為兩個(gè)不步驟疚宇,一個(gè)是SpringFactoriesLoader.loadFactoryNames,一個(gè)是createSpringFactoriesInstances赏殃;
2.loadFactoryNames
在loadFactoryNames方法中主要是通過SpringFactoriesLoader#loadSpringFactories方法讀取classpath下所有jar中META-INF文件夾下的spring.factories文件敷待,以spring-boot-autoconfigure-2.4.6為例,實(shí)例如下第一\前的為實(shí)現(xiàn)的接口名稱仁热,\后面的內(nèi)容為需要加載的類榜揖,一般我們?cè)赟pring Boot進(jìn)行一些擴(kuò)展到時(shí)候都是使用到EnableAutoConfiguration,大家也可以將這部分的實(shí)現(xiàn)理解為我們自定義一個(gè)starter包(如 mybatis-spring-boot-starter)抗蠢,我們就需要將自己實(shí)現(xiàn)的功能(如MybatisAutoConfiguration)與Spring Boot進(jìn)行融合從而實(shí)現(xiàn)自動(dòng)配置举哟。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration
在該方法中將META-INF/spring.factories文件轉(zhuǎn)化為urls,最后將spring.factories中的對(duì)應(yīng)關(guān)系存儲(chǔ)在result中迅矛,并在cache中進(jìn)行緩存妨猩,整個(gè)流程只需要加載一次這個(gè)過程即可。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
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();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
result中存儲(chǔ)的<key,value>結(jié)構(gòu)<key,value>如下秽褒,上層的調(diào)用方法會(huì)根據(jù)壶硅,并根據(jù)BootstrapRegistryInitializer、ApplicationContextInitializer销斟、ApplicationListener(可以提前看看源碼的注釋呦)三種類型從result中返回不同的三種集合初始化到剛剛說的bootstrapRegistryInitializers森瘪、initializers、listeners三個(gè)變量中票堵。
- BootstrapRegistryInitializer:對(duì)BootstrapRegistry的回調(diào)扼睬,可以注冊(cè)一些創(chuàng)建成本高或者在ApplicationContext之前的共享變量;
- ApplicationContextInitializer:用于在spring容器刷新之前初始化Spring,ConfigurableApplicationContext的回調(diào)接口窗宇,在容器刷新之前調(diào)用該類的 initialize 方法措伐。
- ApplicationListener:熟悉Spring源碼的應(yīng)該知道這是一個(gè)監(jiān)聽者模式,作為一種回調(diào)军俊,在spring上下文創(chuàng)建完成后進(jìn)行統(tǒng)一的調(diào)用侥加。
3.createSpringFactoriesInstances
這部分比較簡(jiǎn)單就不貼源碼了(要不女朋友又要罵我了),主要就是根據(jù)result中篩選出的類利用反射及逆行初始化粪躬,然后應(yīng)用AnnotationAwareOrderComparator根據(jù)注解(@Order担败、@Priority)進(jìn)行排序。
截至到現(xiàn)在SpringApplication的初始化就介紹完啦镰官,是不是很簡(jiǎn)單呀提前,如果還不是特別的了解,可以看下我的小伙伴的提綱博客加深印象泳唠,下面我們介紹下啟動(dòng)流程吧狈网。
二、Run啟動(dòng)流程
Spring Boot的啟動(dòng)流程可以濃縮成這一個(gè)方法笨腥,我們的介紹也是從這個(gè)方法進(jìn)行拓哺,大家不要慌,慢慢看應(yīng)該可以看得懂的脖母。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
- StopWatch是一個(gè)計(jì)時(shí)器的封裝士鸥,通過start和stop方法來計(jì)算運(yùn)行的時(shí)間;
- createBootstrapContext將第一部分中bootstrapRegistryInitializers保存的BootstrapRegistryInitializer實(shí)例取出來逐一的調(diào)用initialize方法進(jìn)行執(zhí)行谆级,參數(shù)為BootstrapRegistry烤礁;
- configureHeadlessProperty設(shè)置Headless(java.awt.headless)的屬性狀態(tài),這個(gè)可以忽略不用較真兒去理解Headless哨苛;
- getRunListeners從第一部分我們說的cache中找到SpringApplicationRunListener的實(shí)現(xiàn)類鸽凶,默認(rèn)只有只有一個(gè)EventPublishingRunListener,并將它作為參數(shù)(listeners)初始化SpringApplicationRunListeners建峭,SpringApplicationRunListener的作用是作為SpringApplication的run方法的監(jiān)聽器玻侥。通過代碼中的listeners.starting方法將starting的Event發(fā)布到Spring的所有ApplicationListener監(jiān)聽器中。
1.EventPublishingRunListener
看到這大家應(yīng)該有點(diǎn)暈了吧亿蒸,一堆Listener繞來繞去凑兰,寫到這我自己差點(diǎn)都暈了,下面針對(duì)在這些Listener進(jìn)行以下簡(jiǎn)單的梳理边锁。
該部分是通過doWithListeners方法將spring.boot.application.starting事件ApplicationStartingEvent通過Spring的SimpleApplicationEventMulticaster發(fā)布到Spring的事件傳播器中姑食,在multicastEvent方法中g(shù)etApplicationListeners會(huì)篩選與ApplicationStartingEvent匹配的ApplicationListeners,然后通過invokeListener執(zhí)行onApplicationEvent方法實(shí)現(xiàn)starting事件的傳播茅坛。
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
//通過initialMulticaster實(shí)現(xiàn)與Spring事件發(fā)布的融合
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Executor executor = this.getTaskExecutor();
//得到與發(fā)布的event的一致的listener
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
//listener回調(diào)參數(shù)為發(fā)布的event
this.invokeListener(listener, event);
}
}
}
2.prepareEnvironment
然后我們接著回到run方法中繼續(xù)執(zhí)行第一try的代碼中音半,DefaultApplicationArguments主要是講args進(jìn)行封裝则拷,args這個(gè)參數(shù)之前一直沒有介紹,其實(shí)就是啟動(dòng)的參數(shù)入在jar -jar啟動(dòng)項(xiàng)目包的時(shí)候制定的一些參數(shù)曹鸠,如--server.port等煌茬。
緊接著是prepareEnvironment方法,這個(gè)方法看著其實(shí)比較頭痛彻桃,其實(shí)要是給每一個(gè)方法都講清楚和明白坛善,其實(shí)和寫源碼的注釋就差不多了,這個(gè)文章給關(guān)鍵方法的著重講解下邻眷, 其他的就簡(jiǎn)單說一下用處眠屎。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment方法會(huì)根據(jù)我們之前在初始化SpringApplicaiton時(shí)候的
webApplicationType類型返回ConfigurableEnvironment的實(shí)現(xiàn)類,本例子中是Servlet所有返回StandardServletEnvironment肆饶,響應(yīng)式返回StandardReactiveWebEnvironment改衩,其他返回StandardEnvironment;-
configureEnvironment方法抖拴,第一步是配置一個(gè)ConversionService燎字,大家就理解為是一個(gè)轉(zhuǎn)換工具就可以腥椒,configurePropertySources方法會(huì)將啟動(dòng)參數(shù)的配置封裝到SimpleCommandLinePropertySource中阿宅,最終加入到Environment中。configureProfiles方法在本例子中該方法中沒有任何代碼笼蛛,大家先忽略洒放;
大家可以看一下environment這個(gè)對(duì)象,大家可以簡(jiǎn)單的理解就是所有的配置按照不同的優(yōu)先級(jí)和實(shí)例組織在propertySources中滨砍。
ConfigurationPropertySources.attach方法是將configurationProperties添加到propertySources中往湿;
listeners.environmentPrepared方法,大家看到listeners應(yīng)該就懂了惋戏,和前面的listeners執(zhí)行starting類似领追,回調(diào)的參數(shù)是DefaultBootstrapContext和Environment;在這個(gè)方法中主要是處理一些SpringBoot的一些配置响逢,其中有一個(gè)比較重要的EnvironmentPostProcessorApplicationListener绒窑,他是處理實(shí)現(xiàn)所有實(shí)現(xiàn)EnvironmentPostProcessor接口的類,有點(diǎn)像Spring中BeanPostProcessor的作用舔亭,其中一個(gè)ConfigDataEnvironmentPostProcessor實(shí)現(xiàn)了讀取我們properties配置文件并加入到environment中的作用些膨。
EnvironmentPostProcessor 這多說一句如果在SpringBoot的應(yīng)用程序中,如果想對(duì)配置進(jìn)行一些修改就可以實(shí)現(xiàn)這個(gè)接口然后進(jìn)行自定義的擴(kuò)展,這部分在我們的開發(fā)中也有使用過钦铺,如不通過配置在項(xiàng)目啟動(dòng)的時(shí)候添加一些后面會(huì)使用的參數(shù) 订雾。
- DefaultPropertiesPropertySource.moveToEnd方法,本例子中沒有defaultProperties配置矛洞,所以沒有執(zhí)行洼哎;
- bindToSpringApplication實(shí)現(xiàn)environment和SpringApplication的綁定,new EnvironmentConverter方法是對(duì)Environment通過進(jìn)行一些轉(zhuǎn)換;
至此prepareEnvironment方法已經(jīng)介紹完了是不是很好理解噩峦,其實(shí)有時(shí)候看源碼也不用說每一行都看懂在干什么窑邦,知道大概的流程就可以了,那回來頭我們接著說我們的run方法壕探。
configureIgnoreBeanInfo方法冈钦,將environment中的spring.beaninfo.ignore屬性設(shè)置到System環(huán)境變量中,這部分也可以不關(guān)注李请;
printBanner方法瞧筛,打印SpringBoot的啟動(dòng)Logo愿意改可以自定義。
createApplicationContext方法导盅,根據(jù)SpirngApplication中的webApplicationType類型來返回ApplicaitonContext(簡(jiǎn)稱上下文)较幌,本例子是一個(gè)Servlet項(xiàng)目,返回AnnotationConfigServletWebServerApplicationContext白翻,響應(yīng)式返回AnnotationConfigReactiveWebServerApplicationContext乍炉,其他返回AnnotationConfigApplicationContext;AnnotationConfigServletWebServerApplicationContext里面就我們很熟悉的兩個(gè)類滤馍,一個(gè)是AnnotatedBeanDefinitionReader岛琼,一個(gè)是ClassPathBeanDefinitionScanner,(不太清楚的孩子去回憶Spring哈)巢株;
context.setApplicationStartup槐瑞,將上下文關(guān)聯(lián)applicationStartup;
3.prepareContext
這個(gè)方法是上下文的準(zhǔn)備工作阁苞,看參數(shù)的個(gè)數(shù)大家應(yīng)該就知道這個(gè)類比較重要困檩,參數(shù)基本上包括了上述的大部分內(nèi)容。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
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);
}
- context.setEnvironment上線文關(guān)聯(lián)environment那槽;
- postProcessApplicationContext在本例子中只將ApplicationConversionService進(jìn)行關(guān)聯(lián)悼沿;
- applyInitializers方法是將在SpringApplicaiton構(gòu)造的時(shí)候初始化的initializer循環(huán)執(zhí)行其中的initialize方法。
- listeners.contextPrepared這個(gè)應(yīng)該也不用多說了和前面starting的流程類似骚灸;
- bootstrapContext.close關(guān)閉啟動(dòng)器糟趾;
- 上面代碼中兩個(gè)**中的內(nèi)容其實(shí)就是將上線中的BeanFactory去出來然后進(jìn)行一些信息的注冊(cè)或設(shè)置,如springApplicationArguments逢唤、springBootBanner等拉讯;
- load方法是將啟動(dòng)類Source作為參數(shù)然后將bean加載到Spring上下文中,過程是通過AnnotatedBeanDefinitionReader類對(duì)啟動(dòng)類進(jìn)行Bean定義的注冊(cè)鳖藕;
- listeners.contextLoaded最后執(zhí)行contextLoaded方法魔慷,此部分與starting部分也類似;
4.refreshContext
refreshContext中有兩部分工作著恩,一部分是在Runtime中注冊(cè)一個(gè)鉤子(可以理解為一個(gè)線程)院尔,當(dāng)程序執(zhí)行完成后會(huì)做一個(gè)操作蜻展,SpringBoot寫的鉤子大體上是完成一寫資源的銷毀。另一部分是核心邀摆,也是SpringBoot與Spring的連接點(diǎn)纵顾,在方法的最后執(zhí)行了Spring的applicationContext的refresh方法,然后完成Spring上下文中所有Bean的初始化栋盹;
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
refresh((ApplicationContext) context);
}
截至現(xiàn)在run方法中我們還有2行比較重要的代碼就結(jié)束了施逾,listeners.started就不多說了,另一個(gè)是callRunners例获,它的主要作用是SpringBoot應(yīng)用啟動(dòng)完成后進(jìn)行一個(gè)回調(diào)汉额,可以實(shí)現(xiàn)ApplicationRunner接口也可以實(shí)現(xiàn)CommandLineRunner接口,代碼如下榨汤。
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);
}
}
}
主要的流程是得到實(shí)現(xiàn)兩個(gè)接口的類然后按照@Order注解的順序進(jìn)行排序蠕搜,他們兩個(gè)接口的區(qū)別就是回調(diào)的參數(shù)不同,一個(gè)是ApplicationArguments收壕,一個(gè)是String... args妓灌,然后循環(huán)執(zhí)行各自的run方法的進(jìn)行回調(diào)。還有一個(gè)小細(xì)節(jié)就是在執(zhí)行的過程中如果有異常依舊會(huì)調(diào)用listeners.failed方法蜜宪。