從Spring Boot2.0啟動(dòng)流程看自動(dòng)裝配EnableAutoConfiguration

本文將從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方法蜜宪。

三虫埂、@EnableAutoConfiguration注解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市端壳,隨后出現(xiàn)的幾起案子告丢,更是在濱河造成了極大的恐慌枪蘑,老刑警劉巖损谦,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異岳颇,居然都是意外死亡照捡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門话侧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栗精,“玉大人,你說我怎么就攤上這事瞻鹏”ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵新博,是天一觀的道長薪夕。 經(jīng)常有香客問我,道長赫悄,這世上最難降的妖魔是什么原献? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任馏慨,我火速辦了婚禮,結(jié)果婚禮上姑隅,老公的妹妹穿的比我還像新娘写隶。我一直安慰自己,他們只是感情好讲仰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布慕趴。 她就那樣靜靜地躺著,像睡著了一般鄙陡。 火紅的嫁衣襯著肌膚如雪秩贰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天柔吼,我揣著相機(jī)與錄音毒费,去河邊找鬼。 笑死愈魏,一個(gè)胖子當(dāng)著我的面吹牛觅玻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播培漏,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼溪厘,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了牌柄?” 一聲冷哼從身側(cè)響起畸悬,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎珊佣,沒想到半個(gè)月后蹋宦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咒锻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年冷冗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惑艇。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒿辙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滨巴,到底是詐尸還是另有隱情思灌,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布恭取,位于F島的核電站泰偿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏秽荤。R本人自食惡果不足惜甜奄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一柠横、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧课兄,春花似錦牍氛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜒茄,卻和暖如春唉擂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檀葛。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工玩祟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屿聋。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓空扎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親润讥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子转锈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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