springboot源碼1--啟動流程

之前在使用spring boot框架搭建一個web服務(wù)時,一直想詳細研究下spring boot的源碼,主要是bean加載到IOC容器和Spring Aop這兩個功能的具體實現(xiàn),最近有時間就在家看了下spring關(guān)于這兩個功能的源碼光绕,也在網(wǎng)上找了些資料去看,發(fā)現(xiàn)大部分資料寫的都是偏重于某一塊源碼的講解,我是希望能夠按照spring boot的啟動流程來分析這兩個功能骤宣,這樣的話能夠前后連貫,理解起來也會更容易序愚,否則單獨講這兩部分的話憔披,很多東西不知道在哪完成初始化的,因此關(guān)于spring 源碼學習的第一篇文章就從spring boot框架的啟動開始講解。

@SpringBootApplication
public class Chapter1Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter1Application.class, args);
    }
}

通過調(diào)用SpringApplication的run方法就可以快速啟動一個web應(yīng)用芬膝。這個run方法里最后會調(diào)用下面這個run方法望门。

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }

    public SpringApplication(Object... sources) {
        initialize(sources);
    }

    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

這個run方法中new了一個SpringApplication實例,在構(gòu)造函數(shù)中調(diào)用了initialize方法锰霜,在這個方法里主要做了三件事:
(1)檢查當前啟動的應(yīng)用是否是一個web應(yīng)用筹误。如果是webEnvironment 變量的值為true,檢測的方法是能否通過反射實例化下面這兩個類來判斷癣缅。

"javax.servlet.Servlet",        "org.springframework.web.context.ConfigurableWebApplicationContext"

(2) 設(shè)置ApplicationContextInitializer實現(xiàn)類厨剪,這些實現(xiàn)類是配置在META-INF/spring.factories文件中,這些實現(xiàn)類后面會使用友存。

org.springframework.context.ApplicationContextInitializer=
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,
org.springframework.boot.context.ContextIdApplicationContextInitializer,
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

(3) 設(shè)置listener監(jiān)聽器祷膳,這些監(jiān)聽器也是配置在META-INF/spring.factories下如下:

org.springframework.context.ApplicationListener=
org.springframework.boot.ClearCachesApplicationListener,
org.springframework.boot.builder.ParentContextCloserApplicationListener,
org.springframework.boot.context.FileEncodingApplicationListener,
org.springframework.boot.context.config.AnsiOutputApplicationListener,
org.springframework.boot.context.config.ConfigFileApplicationListener,
org.springframework.boot.context.config.DelegatingApplicationListener,
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,
org.springframework.boot.logging.ClasspathLoggingApplicationListener,
org.springframework.boot.logging.LoggingApplicationListener

這些監(jiān)聽器是spring啟動時,自動加載的屡立。用戶也可以自己實現(xiàn)ApplicationListener接口直晨,按照業(yè)務(wù)的要求實現(xiàn)具體的監(jiān)聽器,對spring的事件監(jiān)聽機制不熟悉的可以參考這篇文章sping監(jiān)聽器膨俐。

初始化SpringApplication實例后勇皇,調(diào)用SpringApplication的run方法,看下這個run方法實現(xiàn)吟策。

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
    }

這個方法比較長儒士,spring的啟動就是在這個方法中完成的,我們還是按照步驟來分析這個方法檩坚。
(1)獲取SpringApplicationRunListeners對象着撩,獲取方式與之前獲取ApplicationListener實現(xiàn)類相同,也是從spring.factories中獲取具體的SpringApplicationRunListeners實現(xiàn)類匾委。

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener這個類的主要作用是根據(jù)spring啟動的不同時期拖叙,觸發(fā)不同的事件類型。之前設(shè)置的監(jiān)聽器赂乐,每個監(jiān)聽器對應(yīng)不同的事件薯鳍,事件類型匹配時會觸發(fā)對應(yīng)監(jiān)聽器的onApplicationEvent方法,這是一個典型的觀察者模式挨措。這個類的具體代碼可以參考EventPublishingRunListener挖滤。

(2)觸發(fā)EventPublishingRunListener的starting方法。

    public void starting() {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }

這個方法就會觸發(fā)一個ApplicationStartedEvent事件浅役,通知這個事件對應(yīng)的監(jiān)聽器完成具體的業(yè)務(wù)斩松。

(3)創(chuàng)建ApplicationContext對象。
這個對象非常重要觉既,Spring IOC的實現(xiàn)主要就是基于這個對象惧盹∪樾遥看下怎么創(chuàng)建的。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            }
            catch (ClassNotFoundException ex) {
            
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

根據(jù)是否是一個web應(yīng)用钧椰,確定ApplicationContext的具體類型粹断。本文以web應(yīng)用為例,初始化一個AnnotationConfigEmbeddedWebApplicationContext類嫡霞,具體初始化過程參考ApplicationContext對象初始化過程瓶埋。

(4)執(zhí)行prepareContext操作。

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        诊沪。悬赏。。
        // 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);
    }
    }

這個方法中主要對上一步創(chuàng)建的ApplicationContext做一些初始化操作娄徊,調(diào)用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);
        }
    }

這個方法中會遍歷之前添加的實現(xiàn)ApplicationContextInitializer的初始化子類,然后調(diào)用這些子類的initialize方法寄锐,完成對ApplicationContext的一些初始化操作兵多。
例如:
ContextIdApplicationContextInitializer類會為ApplicationContext生成一個唯一的id。
DelegatingApplicationContextInitializer類會從application.properties配置文件中讀取key為context.initializer.classes的實現(xiàn)ApplicationContextInitializer的初始化子類橄仆,這樣就允許開發(fā)者自己添加ApplicationContextInitializer子類剩膘。

遍歷完初始化子類后,執(zhí)行l(wèi)isteners.contextPrepared(context)盆顾,這個是觸發(fā)監(jiān)聽器操作的怠褐,這個方法目前是空,不會觸發(fā)監(jiān)聽器的執(zhí)行。

然后執(zhí)行l(wèi)oad方法,加載Spring boot框架的主函數(shù)所在的類到ApplicationContext中屯阀,看下加載過程。

    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        BeanDefinitionLoader loader = createBeanDefinitionLoader(
                getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }

初始化一個BeanDefinitionLoader類磷杏,然后調(diào)用load方法,省略中間調(diào)用過程捏卓,調(diào)用下面這個load方法极祸。

    private int load(Class<?> source) {
        。怠晴。遥金。
        if (isComponent(source)) {
            this.annotatedReader.register(source);
            return 1;
        }
        return 0;
    }

首先加載的類是否含有@Component注解,然后調(diào)用annotatedReader.register(source)方法蒜田,annotatedReader實例對應(yīng)的實現(xiàn)類是AnnotatedBeanDefinitionReader類稿械,忽略中間調(diào)用過程,最終會調(diào)用下面這個register方法物邑。

    public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
        if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
            return;
        }
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        if (qualifiers != null) {
            for (Class<? extends Annotation> qualifier : qualifiers) {
                if (Primary.class == qualifier) {
                    abd.setPrimary(true);
                }
                else if (Lazy.class == qualifier) {
                    abd.setLazyInit(true);
                }
                else {
                    abd.addQualifier(new AutowireCandidateQualifier(qualifier));
                }
            }
        }

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
    }

首先創(chuàng)建一個基于注解的AnnotatedGenericBeanDefinition實現(xiàn)類溜哮,這個類中保存了bean的所有信息。
然后調(diào)用this.scopeMetadataResolver.resolveScopeMetadata(abd)這個方法色解。

    @Override
    public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
        ScopeMetadata metadata = new ScopeMetadata();
        if (definition instanceof AnnotatedBeanDefinition) {
            AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
            AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
                    annDef.getMetadata(), this.scopeAnnotationType);
            if (attributes != null) {
                metadata.setScopeName(attributes.getString("value"));
                ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
                if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
                    proxyMode = this.defaultProxyMode;
                }
                metadata.setScopedProxyMode(proxyMode);
            }
        }
        return metadata;
    }

這個方法主要是判斷類中是否有@Scope注解茂嗓,如果有這個注解會獲取這個注解對應(yīng)的value和proxyMode屬性,關(guān)于這個注解可以參考@Scope科阎。獲取完@Scope注解屬性后返回ScopeMetadata對象述吸。根據(jù)返回的ScopeMetadata對象,設(shè)置AnnotatedGenericBeanDefinition中這個bean的Scope屬性锣笨。

調(diào)用AnnotationConfigUtils.processCommonDefinitionAnnotations(abd)這個方法完成對這個類中注解的解析蝌矛,代碼如下。

    static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
        if (metadata.isAnnotated(Lazy.class.getName())) {
            abd.setLazyInit(attributesFor(metadata, Lazy.class).getBoolean("value"));
        }
        else if (abd.getMetadata() != metadata && abd.getMetadata().isAnnotated(Lazy.class.getName())) {
            abd.setLazyInit(attributesFor(abd.getMetadata(), Lazy.class).getBoolean("value"));
        }

        if (metadata.isAnnotated(Primary.class.getName())) {
            abd.setPrimary(true);
        }
        if (metadata.isAnnotated(DependsOn.class.getName())) {
            abd.setDependsOn(attributesFor(metadata, DependsOn.class).getStringArray("value"));
        }

        if (abd instanceof AbstractBeanDefinition) {
            AbstractBeanDefinition absBd = (AbstractBeanDefinition) abd;
            if (metadata.isAnnotated(Role.class.getName())) {
                absBd.setRole(attributesFor(metadata, Role.class).getNumber("value").intValue());
            }
            if (metadata.isAnnotated(Description.class.getName())) {
                absBd.setDescription(attributesFor(metadata, Description.class).getString("value"));
            }
        }
    }

解析完常用注解后错英,將這個AnnotatedGenericBeanDefinition放入BeanDefinitionHolder對象中入撒,然后調(diào)用AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry)。

    static BeanDefinitionHolder applyScopedProxyMode(
            ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

        ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
        if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
            return definition;
        }
        boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
        return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
    }

如果bean中含有@Scope注解椭岩,則根據(jù)proxyMode屬性生成一個代理對象茅逮。
生成代理對象后,將這個代理對象放入ApplicationContext中DefaultListableBeanFactory中的beanDefinitionMap這個map中判哥。

到這里prepareContext方法的大部分功能就完成了献雅。

(5)refresh Context
上一步只是初始化了context的一些參數(shù),創(chuàng)建了context里創(chuàng)建bean的DefaultListableBeanFactory塌计,將sping boot啟動的主函數(shù)類放入beanDefinitionMap這個map中挺身,項目中的其他定義的bean還未被掃描放入beanDefinitionMap,以及beanDefinitionMap中放入的bean還沒有被實例化锌仅。這些操作其實是在refresh Context這一步完成章钾。
這一步的內(nèi)容比較多,也比較復(fù)雜热芹,我們在bean加載詳細講解伍玖。

我們接下來還是繼續(xù)分析啟動的主流程。
(6)最后調(diào)用listeners.finished(context, null)
看下fininshed這個方法

    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        SpringApplicationEvent event = getFinishedEvent(context, exception);
        if (context != null && context.isActive()) {
            // Listeners have been registered to the application context so we should
            // use it at this point if we can
            context.publishEvent(event);
        }
        else {
            // An inactive context may not have a multicaster so we use our multicaster to
            // call all of the context's listeners instead
            if (context instanceof AbstractApplicationContext) {
                for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
                        .getApplicationListeners()) {
                    this.initialMulticaster.addApplicationListener(listener);
                }
            }
            if (event instanceof ApplicationFailedEvent) {
                this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
            }
            this.initialMulticaster.multicastEvent(event);
        }
    }

其實就是發(fā)送一個ApplicationReadyEvent事件觸發(fā)監(jiān)聽此事件的監(jiān)聽器剿吻,我看了下之前加載的監(jiān)聽器好像沒有監(jiān)聽此事件的監(jiān)聽器窍箍,當然用戶也可以自己定義關(guān)于此事件的監(jiān)聽器。

到這里springboot 啟動的大概流程就分析完了丽旅,大部分的文章對這部分都是一概而過椰棘,直接去講bean的加載,但是這里還是有很多內(nèi)容需要清楚的榄笙,這樣才能更明白類的調(diào)用關(guān)系邪狞、監(jiān)聽器模式、bean是如何加載的茅撞。

下一節(jié)就具體講bean加載帆卓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巨朦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剑令,更是在濱河造成了極大的恐慌糊啡,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吁津,死亡現(xiàn)場離奇詭異棚蓄,居然都是意外死亡,警方通過查閱死者的電腦和手機碍脏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門梭依,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人典尾,你說我怎么就攤上這事役拴。” “怎么了钾埂?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵扎狱,是天一觀的道長。 經(jīng)常有香客問我勃教,道長淤击,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任故源,我火速辦了婚禮污抬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绳军。我一直安慰自己印机,他們只是感情好,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布门驾。 她就那樣靜靜地躺著射赛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奶是。 梳的紋絲不亂的頭發(fā)上楣责,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音聂沙,去河邊找鬼秆麸。 笑死,一個胖子當著我的面吹牛及汉,可吹牛的內(nèi)容都是我干的沮趣。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼坷随,長吁一口氣:“原來是場噩夢啊……” “哼房铭!你這毒婦竟也來了驻龟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤缸匪,失蹤者是張志新(化名)和其女友劉穎翁狐,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豪嗽,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年豌骏,在試婚紗的時候發(fā)現(xiàn)自己被綠了龟梦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡窃躲,死狀恐怖计贰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒂窒,我是刑警寧澤躁倒,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站洒琢,受9級特大地震影響秧秉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜衰抑,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一象迎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呛踊,春花似錦砾淌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至愉择,卻和暖如春劫乱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锥涕。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工要拂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人站楚。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓脱惰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窿春。 傳聞我的和親對象是個殘疾皇子拉一,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361