SpringBoot 啟動過程源碼分析

主要了解和學(xué)習(xí)下SpringBoot啟動的大致原理是如何,以及知道幾個注解的真正含義和用途是什么,SpringBoot就可以以SpringApplication.run(Bootstrap.class);這樣的一句代碼作為啟動的入口

1、SpringApplication 對象實(shí)例化

SpringApplication 文件

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
      // 傳遞的source其實(shí)就是類Bootstrap
    return new SpringApplication(sources).run(args);
    // 實(shí)例化一個SpringApplication對象執(zhí)行run方法
}

實(shí)例化的時候又會執(zhí)行initialize 方法

private void initialize(Object[] sources) {
      // 這個source依舊是上文說的Bootstrap.class 類
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
        // 添加到source資源列表里面去
    }
    this.webEnvironment = deduceWebEnvironment();
    // 設(shè)置其是否為web環(huán)境
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 拆分為兩步,一步是getSpringFactoriesInstances,再者就是set操作
    // set操作很簡單,就是設(shè)置當(dāng)前對象的初始化對象以及監(jiān)聽器
    this.mainApplicationClass = deduceMainApplicationClass();
    // 通過堆棧信息,推斷 main方法的類對象為當(dāng)前的主程序類
}

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

private boolean deduceWebEnvironment() {
    for (String className : WEB_ENVIRONMENT_CLASSES) {
           // 遍歷包含上述兩個類名稱的數(shù)組
        if (!ClassUtils.isPresent(className, null)) {
               // 一旦發(fā)現(xiàn)不存在該類,就立即返回 deduce 推斷不是web環(huán)境
            return false;
        }
    }
    // 必須同時包含兩個類,才推斷出為web環(huán)境
    return true;
}

getSpringFactoriesInstances 方法操作

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
        // 傳遞的type就是上面說的ApplicationContextInitializer.class以及ApplicationListener.class類
        // 類型以及參數(shù)目前都沒有具體指
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            // 通過SpringFactoriesLoader 獲取對應(yīng)的名稱,具體詳情可以看下面的代碼塊
            // 這點(diǎn)需要重點(diǎn)關(guān)注下?。?!
            // 結(jié)果就是返回一個set集合
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
            // 看樣子就是創(chuàng)建一個實(shí)例的集合
    AnnotationAwareOrderComparator.sort(instances);
    // 然后通過AnnotationAwareOrderComparator 的排序規(guī)則跪?qū)嵗线M(jìn)行排序
    // 排序就是看是否存在Order或者Priority注解,然后取得注解的值,排在集合前面
    return instances;
}

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
           // 遍歷上面取到的name 集合
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            // 取到這個類名稱的類
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass
                    .getDeclaredConstructor(parameterTypes);
            // 獲取當(dāng)前類的符合當(dāng)前參數(shù)的構(gòu)造器
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            // 利用反射的方式生成具體的對象
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException(
                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }
    // 最后生成name映射的實(shí)例集合
    return instances;
}

SpringFactoriesLoader.loadFactoryNames 方法

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    // 傳遞的factoryClass 就是上面的ApplicationContextInitializer、ApplicationListener.等
    String factoryClassName = factoryClass.getName();
    // 獲取類的全名稱
    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        // 如果類加載器為null,則使用系統(tǒng)默認(rèn)的方法,否則使用當(dāng)前傳遞的類加載器讀取
        // 當(dāng)前類加載器可以獲取到的所有文件路徑為“META-INF/spring.factories” 的地址
        
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
              // 迭代遍歷url
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            // 讀取映射的spring.factories 文件的KV鍵值對,存放到properties對象中
            String factoryClassNames = properties.getProperty(factoryClassName);
            // 類似于map一般,獲取對應(yīng)的值
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            // 對值使用逗號分隔,生成list,然后去重添加到result
        }
        
        // 總結(jié)下來就是遍歷當(dāng)前類環(huán)境中的所有路徑為“META-INF/spring.factories”的文件
        // 讀取文件,然后獲取k為當(dāng)前類名稱的所有值,然后存儲到set中返回
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

到這里整個的initialize操作就已經(jīng)清楚了,通過類加載器可獲取的所有為“META-INF/spring.factories” 的地址的文件內(nèi)容,然后獲取key為ApplicationContextInitializer.class以及ApplicationListener.class的類名稱的值集合
然后依次就行實(shí)例化,最后排序返回,最后保存到當(dāng)前對象的初始化集合以及監(jiān)聽器集合中,便于后續(xù)操作

需要注意到SpringFactoriesLoader.loadFactoryNames 后面很多地方都需要使用該方法去獲取相關(guān)內(nèi)容

當(dāng)然現(xiàn)在只是完成了SpringApplication構(gòu)造器里面的方法,還剩下后面的run(args)方法執(zhí)行

如下代碼塊就是SpringBoot的執(zhí)行過程(最后的套路依舊是Spring Framework的執(zhí)行策略)

2、SpringApplication的run方法啟動

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 記錄當(dāng)前服務(wù)開始啟動
    ConfigurableApplicationContext context = null;
    // 上下文context,非常關(guān)鍵
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    // 給系統(tǒng)設(shè)置headless屬性值
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 就是通過SpringFactoriesLoader 獲取到所有SpringApplicationRunListener.class的對象
    // 其中args是用來進(jìn)行實(shí)例化SpringApplicationRunListener對應(yīng)的對象的構(gòu)造器參數(shù)
    // 最后返回listener是整個系統(tǒng)的監(jiān)聽器
    listeners.starting();
    // 監(jiān)聽器開始執(zhí)行
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 默認(rèn)程序參數(shù)
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        // 準(zhǔn)備運(yùn)行的環(huán)境上下文
        Banner printedBanner = printBanner(environment);
        // 打印banner,默認(rèn)輸出當(dāng)前springboot版本等內(nèi)容,可以自定義設(shè)置文本或者圖片
        // 具體看下面的方法詳解
        context = createApplicationContext();
        // 創(chuàng)建SpringBoot最重要的上下文容器
        analyzers = new FailureAnalyzers(context);
        // 分析上下文出現(xiàn)問題的點(diǎn),便于使用者可以直觀的發(fā)現(xiàn)問題出現(xiàn)在哪里
        // 其實(shí)套路類似,就是使用SpringFactoriesLoader獲取所有的FailureAnalyzer實(shí)例對象,然后設(shè)置其bean工廠為context的bean工廠上下文
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // 看名稱就是對context的前置準(zhǔn)備工作,細(xì)節(jié)在后面說
        refreshContext(context);
        // 切入到spring framework的方式去完成context內(nèi)容的裝載
        // 如果需要注冊終止鉤子,則注冊一個
        afterRefresh(context, applicationArguments);
        // 基本上認(rèn)為springboot所需的服務(wù)都加載完成,進(jìn)行最后的處理操作
        // 里面常用的就是CommandLineRunner
        listeners.finished(context, null);
        // 監(jiān)聽器的啟動結(jié)束事件,
        stopWatch.stop();
        // 表示SpringBoot服務(wù)啟動步驟完成,統(tǒng)計(jì)下啟動時間等操作
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
              // 打印SpringBoot啟動成功的消息,例如 Started xxx in 12.4 seconds 等信息
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        // 啟動失敗了就會輸出Application startup failed 日志
        // 并且會輸出具體的錯誤內(nèi)容信息
        throw new IllegalStateException(ex);
    }
}

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 如果當(dāng)前環(huán)境值不為null,直接返回
    // 否則根據(jù)上文推斷出的webEnvironment boolean 值 生成對象的環(huán)境對象
    // 當(dāng)為true的時候,生成StandardServletEnvironment
    // 否則生成的是StandardEnvironment
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    if (!this.webEnvironment) {
          // 如果不是web的環(huán)境,再對當(dāng)前的環(huán)境進(jìn)行包裝,生成一個新的運(yùn)行環(huán)境對象
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}

private Banner printBanner(ConfigurableEnvironment environment) {
     // 參數(shù)environment就是上面生成的環(huán)境對象
    if (this.bannerMode == Banner.Mode.OFF) {
          // 如果設(shè)置了banner關(guān)閉模式,則不進(jìn)行打印輸出操作
        return null;
    }
    ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
            : new DefaultResourceLoader(getClassLoader());
    // 資源加載器生成
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
            resourceLoader, this.banner);
    // 后續(xù)使用SpringApplicationBannerPrinter 類的print進(jìn)行輸出操作
    if (this.bannerMode == Mode.LOG) {
          // 打印模式,如果是log則輸出到log中,否則輸出到終端中
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    // 大致操作就是先看是否存在自定義的圖片類型或者文字類型 banner,如果有就優(yōu)先確定banner對象
    // 否則就默認(rèn)使用SpringBootBanner的banner(這個里面就包含了常規(guī)的springboot輸出內(nèi)容)
    // 然后解析banner的資源,得出將要輸出的字符串內(nèi)容(利用日志直接輸出),存儲到PrintedBanner
}

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            // 如果是web環(huán)境,則使用AnnotationConfigEmbeddedWebApplicationContext
            // 否則就使用AnnotationConfigApplicationContext
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    // 直接通過類,反射生成無構(gòu)造參數(shù)的對象,一般情況就是AnnotationConfigEmbeddedWebApplicationContext對象了
}

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 傳遞上下文、環(huán)境、上下文參數(shù)等數(shù)據(jù)
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 前置處理context上下文,包含了beanNameGenerator和resourceLoader
    // 其中beanNameGenerator 可以自定義規(guī)則約定bean的名稱功能
    applyInitializers(context);
    // 應(yīng)用ApplicationContextInitializer去初始化完成對context的操作
    // 具體的ApplicationContextInitializer對象就是在SpringApplication對象的構(gòu)造方法中實(shí)例化創(chuàng)建的
    // 可以給context添加額外的操作,同時也可以很方便的自定義完成自己需要的功能
    listeners.contextPrepared(context);
    // 執(zhí)行contextPrepared 上下文準(zhǔn)備工作的事件
    if (this.logStartupInfo) {
           // 日志啟動標(biāo)志位,默認(rèn)為true
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
        // 明確當(dāng)前執(zhí)行的主函數(shù)log,輸出SpringBoot的開始啟動信息
    }

    // 注冊springApplicationArguments 這個bean到context中去
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        // 同樣是注冊,打印早就完成了
    }

    // Load the sources
    Set<Object> sources = getSources();
    // 一般情況下這個source就是SpringBoot 啟動的主類Class,注意不是實(shí)例對象
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[sources.size()]));
    // 把source也就是主類當(dāng)做bean,加載到spring的容器中
    listeners.contextLoaded(context);
    // 監(jiān)聽器的上下文導(dǎo)入完成事件 執(zhí)行
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    // 從context獲取ApplicationRunner和CommandLineRunner 對象
    // 然后按照對應(yīng)的規(guī)則進(jìn)行排序
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
        // 分別執(zhí)行各自的run方法
    }
    // 一般情況,我們?nèi)绻枰赟pringBoot加載完成后需要完成一些自定義操作就是注冊
    // ApplicationRunner或者CommandLineRunner 的bean對象,然后自定義實(shí)現(xiàn)run方法即可
}

3、總結(jié)

就SpringBoot的啟動整個過程而已,還是很清晰的,SpringBoot的套用SpringFramework的機(jī)制,為我們自定義實(shí)現(xiàn)功能提供了很好的便利,整個的SpringBoot就是重新包裝了一個SpringFramework。

里面有一個點(diǎn)是SpringFactoriesLoader.loadFactoryNames,從Spring3.2加入的功能,可以讀取META-INF/spring.factories文件需要的內(nèi)容數(shù)據(jù),例如SpringBoot中EnableAutoConfiguration也是充分使用了該功能實(shí)現(xiàn)的,后續(xù)也會針對該功能總結(jié)一篇學(xué)習(xí)筆記

更多關(guān)于Spring的內(nèi)容可以看看Spring 源碼學(xué)習(xí)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 今日體驗(yàn),今天五一節(jié)后上班進(jìn)廠車輛比較多,有的車輛比較急,配件也老配不齊耽誤了很多時間晚上加班把寶馬的大保養(yǎng)做完,...
    王全峰閱讀 143評論 0 0
  • 不知道寫啥,所以啥也不寫了。
    阿風(fēng)聽閱讀 170評論 0 0
  • 文/妙蛙種子君 圖/網(wǎng)絡(luò) (一) 他長得身長七尺五寸,兩耳垂肩,雙手過膝,目能自顧其耳,面如冠玉,唇若涂脂; 他長...
    解讀男人閱讀 787評論 0 1
  • 01 你長過青春痘嗎? 是不是覺得特丑特難看? 每天再也不敢吃香喝辣通宵達(dá)旦,更要命的是,特么連男(女)朋友也沒法...
    文海珀閱讀 642評論 2 6
  • 開源最佳實(shí)踐:Android平臺頁面路由框架ARouter Alibaba-ARouter 源碼分析筆記 ARou...
    Speronie閱讀 290評論 0 0

友情鏈接更多精彩內(nèi)容