Springboot啟動(dòng)流程源碼(一)

寫在前面

Springboot對Spring做了很好的封裝十气,僅通過添加依賴,以及一些有必要的配置外喧伞,就能夠完成項(xiàng)目的啟動(dòng)。下面我將通過對一個(gè)簡單的項(xiàng)目進(jìn)行調(diào)試追蹤,來發(fā)現(xiàn)Springboot是怎樣完成一個(gè)項(xiàng)目的啟動(dòng)的棚赔。

啟動(dòng)項(xiàng)目

SpringApplication.run()作為項(xiàng)目啟動(dòng)的唯一入口,接下來就到SpringApplication類中查看該方法執(zhí)行哪些事眶痰。

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);
    }

可以看到本姥,通過將啟動(dòng)類的class對象作為參數(shù)傳進(jìn)來滓侍,通過SpringApplicatioin構(gòu)造方法新建一個(gè)對象裂逐,再調(diào)用其run方法。本篇先介紹SpringApplication對象的新建過程矮燎,下一篇再對run方法進(jìn)行介紹刊苍。

SpringApplication

public SpringApplication(Class<?>... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

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.lazyInitialization = 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();
    }

主要看關(guān)鍵的幾步穗酥,首先是this.webApplicationType = WebApplicationType.deduceFromClasspath();這一步是推斷應(yīng)用類型百揭。判斷出是servlet、reactive请毛、還是none方仿。這一步比較好理解委粉,就不從源碼上進(jìn)行分析了栗涂。

設(shè)置初始化器

第二步是設(shè)置初始化器墨吓,即setInitializers()方法聪廉,該方法又通過getSpringFactoriesInstances()方法進(jìn)行設(shè)置瞬痘。理解了getSpringFactoriesInstances()方法,就能理解設(shè)置初始化器的過程板熊。以下是getSpringFactoriesInstances()方法的源碼框全。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

在這一步里,通過類加載器去讀取factoryNames干签,再通過factoryNames構(gòu)建factory實(shí)例津辩。

1. 讀取factoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

loadSpringFactories()這個(gè)方法會到org.springframework.boot的jar包中讀取所有的META-INF/spring.factories里面的內(nèi)容。比如下面是spring-boot.jar包其中的Application Context Initializers內(nèi)容:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

讀取這些文件后將內(nèi)容以鍵值對的形式存儲到Map<String, List<String>>中容劳。通過該方法讀取了# Application Context Initializers喘沿、# Application Listeners、# Environment Post Processors等等這樣一些寫在spring.factories文件中的factoryNames鸭蛙。再通過傳給loadFactoryNames()方法的Class<?> factoryType參數(shù)篩選出相對應(yīng)的factoryNames摹恨,即以Map的鍵名讀取值∪⑹樱回到setInitializers()方法晒哄,傳入的參數(shù)為ApplicationContextInitializer.class,因此獲取到的便是ApplicationContextInitializers的factoryNames肪获。

2. 構(gòu)建factory實(shí)例

通過createSpringFactoriesInstances()方法構(gòu)建factory實(shí)例寝凌,該方法的代碼如下:

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList(names.size());
        Iterator var7 = names.iterator();

        while(var7.hasNext()) {
            String name = (String)var7.next();

            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }

        return instances;
    }

可以看到,這一步就是根據(jù)上一步得到的factory類名孝赫,通過反射機(jī)制創(chuàng)建factory實(shí)例较木。

設(shè)置監(jiān)聽器

setListeners()方法與setInitializers()方法類似,通過傳入ApplicationListener.class對象青柄,從META-INF/spring.factories中讀取相應(yīng)的ApplicationListener的factoryNames伐债。再利用反射機(jī)制對這些factory進(jìn)行實(shí)例化。需要指出的是致开,在loadSpringFactories()方法中峰锁,由于setInitializers()方法調(diào)用了一次并將結(jié)果存入cache中,因此setListeners()方法再次調(diào)用時(shí)會將cache中保存的結(jié)果直接返回双戳。

推斷主應(yīng)用類

deduceMainApplicationClass()方法通過跟蹤棧軌跡虹蒋,找出main方法所在的類,進(jìn)而推斷出我們項(xiàng)目的主應(yīng)用類。代碼如下:

private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
        }

        return null;
    }

總結(jié)

回顧一下內(nèi)容魄衅,首先峭竣,啟動(dòng)項(xiàng)目分為兩步:創(chuàng)建SpringApplication對象、調(diào)用SpringApplication對象的run()方法晃虫。創(chuàng)建SpringApplication對象主要有四步:判斷應(yīng)用類型皆撩、設(shè)置初始化器、設(shè)置監(jiān)聽器哲银、判斷主應(yīng)用類毅访。其中設(shè)置初始化器和監(jiān)聽器都通過類加載器去讀取jar包中的META-INF/spring.factories文件,存儲到Map中盘榨,然后再通過反射機(jī)制完成factory的實(shí)例化喻粹。這就是SpringApplication對象創(chuàng)建過程完成的工作。下一篇我將對run()方法進(jìn)行介紹草巡。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末守呜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子山憨,更是在濱河造成了極大的恐慌查乒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郁竟,死亡現(xiàn)場離奇詭異玛迄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棚亩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蓖议,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讥蟆,你說我怎么就攤上這事勒虾。” “怎么了瘸彤?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵修然,是天一觀的道長。 經(jīng)常有香客問我质况,道長愕宋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任结榄,我火速辦了婚禮中贝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘潭陪。我一直安慰自己雄妥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布依溯。 她就那樣靜靜地躺著老厌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黎炉。 梳的紋絲不亂的頭發(fā)上枝秤,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音慷嗜,去河邊找鬼淀弹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛庆械,可吹牛的內(nèi)容都是我干的薇溃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缭乘,長吁一口氣:“原來是場噩夢啊……” “哼沐序!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起堕绩,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤策幼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后奴紧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體特姐,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年黍氮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唐含。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沫浆,死狀恐怖觉壶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情件缸,我是刑警寧澤铜靶,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站他炊,受9級特大地震影響争剿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜痊末,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一蚕苇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凿叠,春花似錦涩笤、人聲如沸嚼吞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舱禽。三九已至,卻和暖如春恩沽,著一層夾襖步出監(jiān)牢的瞬間誊稚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工罗心, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留里伯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓渤闷,卻偏偏與公主長得像疾瓮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子飒箭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

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