Spring Boot 啟動流程源碼分析


學習過springboot的都知道,在Springboot的main入口函數中調用SpringApplication.run(DemoApplication.class,args)函數便可以啟用SpringBoot應用程序,跟蹤一下SpringApplication源碼可以發現,最終還是調用了SpringApplication的動態run函數。

下面以SpringBoot2.0.3.RELEASE為例簡單分析一下運行過程。

SpringApplicatiton部分源碼:

1 public static ConfigurableApplicationContext run(Class<?>[] primarySources,
2       String[] args) {
3   //創建springapplication對象,調用函數run(args)
4    return new SpringApplication(primarySources).run(args);
5 }

上面的源碼可以發現還是先創建SpringApplication實例,再調用run方法

第一步 分析 SpringApplication構造函數

SpringApplication構造函數代碼如下:

 1  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
 2    this.resourceLoader = resourceLoader;
 3    Assert.notNull(primarySources, "PrimarySources must not be null");
 4    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
 5 
 6   //1:判斷web環境
 7    this.webApplicationType = deduceWebApplicationType();
 8 
 9   //2:加載classpath下META-INF/spring.factories中配置的ApplicationContextInitializer
10    setInitializers((Collection) getSpringFactoriesInstances(
11          ApplicationContextInitializer.class));
12   //3:加載classpath下META-INF/spring.factories中配置的ApplicationListener
13   
14    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
15   //4:推斷main方法所在的類
16    this.mainApplicationClass = deduceMainApplicationClass();
17 }

具體邏輯分析:

  1. deduceWebApplicationType(),  SpringApplication構造函數中首先初始化應用類型,根據加載相關類路徑判斷應用類型,具體邏輯如下:
 1   private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
 2       + "web.reactive.DispatcherHandler";
 3 
 4   private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
 5       + "web.servlet.DispatcherServlet";
 6 
 7   private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
 8       "org.springframework.web.context.ConfigurableWebApplicationContext" };
 9 
10 
11 
12    private WebApplicationType deduceWebApplicationType() {
13   //當類路徑中存在REACTIVE_WEB_ENVIRONMENT_CLASS並且不存在MVC_WEB_ENVIRONMENT_CLASS時
14    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
15          && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
16       return WebApplicationType.REACTIVE;
17    }
18   //當加載的類路徑中不包含WEB_ENVIRONMENT_CLASSES中定義的任何一個類時,返回標准應用()
19    for (String className : WEB_ENVIRONMENT_CLASSES) {
20       if (!ClassUtils.isPresent(className, null)) {
22 return WebApplicationType.NONE; 23 } 24 } 25   //加載的類路徑中包含了WEB_ENVIRONMENT_CLASSES中定義的所有類型則判斷為servlet的web應用 26 return WebApplicationType.SERVLET; 27 }

  2. setInitializers初始化屬性initializers,加載classpath下META-INF/spring.factories中配置的ApplicationContextInitializer,此處getSpringFactoriesInstances方法入參type=ApplicationContextInitializer.class

 1   private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
 2         Class<?>[] parameterTypes, Object... args) {
 3      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 4      // Use names and ensure unique to protect against duplicates
 5     // SpringFactoriesLoader.loadFactoryNames()方法將會從calssptah下的META-INF/spring.factories中讀取key為//org.springframework.context.ApplicationContextInitializer的值,並以集合形式返回
 6      Set<String> names = new LinkedHashSet<>(
 7            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
 8      //根據返回names集合逐個實例化,也就是初始化各種ApplicationContextInitializer,這些Initializer實際是在Spring上下文ApplicationContext執行refresh前調用
 9      List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
10          classLoader, args, names);
11      AnnotationAwareOrderComparator.sort(instances); //對instance排序 12      return instances;
13   }

        以我的demo為例,實際debug時得到的initializers如下,其中數據來源於spring-boot,spring-boot-autoconfiguration和spring-boot-devtolls三個jar包下的classpath中,ApplicationContextInitializer接口是Spring框架提供地的,其主要作用是在Spring容器初始化過程中prepareContext()這一步進行回調,具體可參考:Spring Boot(七)擴展分析

 

  3. setListeners 初始化屬性listeners,加載classpath下META-INF/spring.factories中配置的ApplicationListener,此處入參為getSpringFactoriesInstances方法入參type= ApplicationListener.class

 1    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
 2         Class<?>[] parameterTypes, Object... args) {
 3      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
 4      // Use names and ensure unique to protect against duplicates
 5     // SpringFactoriesLoader.loadFactoryNames()方法將會從calssptah下的META-INF/spring.factories中讀取key為//org.springframework.context.ApplicationListener的值,並以集合形式返回
 6      Set<String> names = new LinkedHashSet<>(
 7          SpringFactoriesLoader.loadFactoryNames(type, classLoader));
 8     //根據配置,初始化各種ApplicationListener,作用是用來監聽ApplicationEvent
 9      List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
10          classLoader, args, names);
11      AnnotationAwareOrderComparator.sort(instances);
12      return instances;
13   }

第二步 分析 SpringApplication中 run方法

SpringApplication的run方法代碼如下:

 1 public ConfigurableApplicationContext run(String... args) {
 2    StopWatch stopWatch = new StopWatch();
 3    stopWatch.start();
 4    ConfigurableApplicationContext context = null;
 5    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 6     //設置系統變量java.awt.headless
 7    configureHeadlessProperty();
 8     //1:獲取監聽器:加載classpath下面的META-INF/spring.factories配置的監聽器SpringApplicationRunListener
 9    SpringApplicationRunListeners listeners = getRunListeners(args);
10     //2:啟動監聽器:執行所有runlistener的starting方法,實際上發布一個【ApplicationStartingEvent】事件
11    listeners.starting();
12    try {
13       //3:實例化ApplicationArguments對象
14       ApplicationArguments applicationArguments = new DefaultApplicationArguments(
15             args);
16       //4: 准備應用上下文環境Environment (web環境 or 標准環境)+配置Environment,主要是把run方法的參數配置到Environment  發布【ApplicationEnvironmentPreparedEvent】事件
17       ConfigurableEnvironment environment = prepareEnvironment(listeners,
18             applicationArguments);
19           configureIgnoreBeanInfo(environment);
20     //打印banner,SpringBoot啟動時,控制台輸出的一個歪歪扭扭的很不清楚的Spring幾個大字母,也可以自定義
21       Banner printedBanner = printBanner(environment);
22     //5: 根據不同environment實例化上下文 context
23       context = createApplicationContext();
24     // 異常處理,實例化一個SpringBootExceptionReporter.class 用於處理啟動過程中的錯誤
25       exceptionReporters = getSpringFactoriesInstances(
26             SpringBootExceptionReporter.class,
27             new Class[] { ConfigurableApplicationContext.class }, context);
28     //6: 上下文相關預處理                  發布【ApplicationPreparedEvent】事件
29       prepareContext(context, environment, listeners, applicationArguments,
30             printedBanner);
31     //7: 【刷新應用上線文】執行spring容器(context)的refresh方法,並且調用context的registerShutdownHook方法
32       refreshContext(context);
33     //8:空方法,用於擴展
34       afterRefresh(context, applicationArguments);
35       stopWatch.stop();
36       if (this.logStartupInfo) {
37          new StartupInfoLogger(this.mainApplicationClass)
38                .logStarted(getApplicationLog(), stopWatch);
39       }
40      //9:執行所有runlisteners的started方法,發布【ApplicationStartedEvent】事件
41       listeners.started(context);
42      //10: 遍歷執行CommandLineRunner和ApplicationRunner
43      //如果需要在SpringBoot應用啟動后運行一些特殊的邏輯,可以通過實現ApplicationRunner或CommandLineRunner接口中的run方法,該自定義類的run方法會在此處統一調用
44       callRunners(context, applicationArguments);
45    }
46    catch (Throwable ex) {
47       handleRunFailure(context, ex, exceptionReporters, listeners);
48       throw new IllegalStateException(ex);
49    }
50 
51    try {
52       listeners.running(context);
53    }
54    catch (Throwable ex) {
55       handleRunFailure(context, ex, exceptionReporters, null);
56       throw new IllegalStateException(ex);
57    }
58    return context;
59 }

具體分析:

  1. 獲取監聽器:getRunListeners(args) 加載各種SpringApplicationRunListener實例,內部實現也還是通過SpringFactoriesLoader.loadFactoryNames(type, classLoader))實現,加載META-INF/spring.factories中key為org.springframework.boot.SpringApplicationRunListener的值,生成對應實例,SpringBoot實際加載了一個EventPublishingRunListener監聽器,該監聽器繼承SpringApplicationRunListener接口,SpringApplicationRunListener規定了SpringBoot的生命周期,在各個生命周期廣播相應的事件,調用實際的ApplicationListener類。

  2. 啟動監聽器: listeners.starting()  執行所有SpringApplicationRunListener的stating方法,發布ApplicationStartedEvent事件,該事件被ApplicationListener類型的listener監聽

  3.  實例化ApplicationArguments對象

  4 . 准備應用上下文環境 並發布ApplicationEnvironmentPreparedEvent事件

 1    private ConfigurableEnvironment prepareEnvironment(
 2       SpringApplicationRunListeners listeners,
 3       ApplicationArguments applicationArguments) {
 4      // Create and configure the environment
 5    ConfigurableEnvironment environment = getOrCreateEnvironment();
 6   //根據properties和profiles配置環境
 7    configureEnvironment(environment, applicationArguments.getSourceArgs());
 8   // 執行EventPublishingRunListener發布ApplicationEnvironmentPreparedEvent事件,將會被ApplicationListener監聽到
 9    listeners.environmentPrepared(environment);
10   //
11    bindToSpringApplication(environment);
12    if (this.webApplicationType == WebApplicationType.NONE) {
13       environment = new EnvironmentConverter(getClassLoader())
14             .convertToStandardEnvironmentIfNecessary(environment);
15    }
16    ConfigurationPropertySources.attach(environment);
17    return environment;
18 }

備注:實際上載spring-boot-2.0.3.RELEASE.jar包中,可以發現spring.factories中只配置了一個RunListener: org.springframework.boot.context.event.EventPublishingRunListener

     截取EventPublishingRunListener.java部分代碼:

 1 public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
 2     
 3 
 4      public EventPublishingRunListener(SpringApplication application, String[] args) {  5      this.application = application; 6      this.args = args; 7     this.initialMulticaster = new SimpleApplicationEventMulticaster(); 8       //將SpringApplication實例中的ApplicationListener類型的listeners添加到initialMulticaster,后續執行監聽  9   for (ApplicationListener<?> listener : application.getListeners()) { 10    this.initialMulticaster.addApplicationListener(listener); 11     } 12   } 13 14   // 發布一個ApplicationEnvironmentPreparedEvent事件 15  @Override 16 public void environmentPrepared(ConfigurableEnvironment environment) { 17 //所有被添加到initialMulticaster中的listener都將監聽ApplicationEnvironmentPreparedEvent事件 18 this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( 19 this.application, this.args, environment)); 20  } 21 22 }

   

      4.1 根據properties和profiles配置環境:configureEnvironment(environment, applicationArguments.getSourceArgs());

           以下假設指定配文件application-dev.properties,跟蹤一下源碼,可以發現:

    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

   configureEnvironment方法內部比較簡潔,直接調用兩個方法完事,

        configurePropertySources(environment, args)方法的作用是將args封裝成了SimpleCommandLinePropertySource並加入到了environment中,其中arg中含有啟動參數:--spring.profiles.active=dev

        configureProfiles(environment, args)作用是將啟動參數中指定的配置文件激活。

 

configureProfiles中執行enviroment.getActiveProfiles():強制讀取啟動命令中指定的配置文件

    protected Set<String> doGetActiveProfiles() {
        synchronized (this.activeProfiles) {
            if (this.activeProfiles.isEmpty()) {
                String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
                if (StringUtils.hasText(profiles)) {
                    setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
                            StringUtils.trimAllWhitespace(profiles)));
                }
            }
            return this.activeProfiles;
        }
    }

       5. 根據environment類型創建ApplicationContext,通常情況下,我們啟動的是一個Servlet應用,debug進createApplicationContext()源碼,可以看到內部初始化了AnnotationConfigServletWebServerApplicationContext類,也就是我們的上下文context

  6. 上下文相關預處理,prepareContext()方法

 1 private void prepareContext(ConfigurableApplicationContext context,
 2       ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
 3       ApplicationArguments applicationArguments, Banner printedBanner) {
 4    context.setEnvironment(environment); //設置容器環境,environment前面已經准備好  5   //配置beanNameGenerator和資源加載器
 6    postProcessApplicationContext(context);
 7   //回調所有的ApplicationContextInitializer初始化器
 8    applyInitializers(context);
 9   //發布容器以准備好的事件:執行所有SpringApplicationRunListener的contextPrepared方法,觸發事件,實際上EventPublishingRunListener中contextPrepared是一個空方法,什么都沒執行
10    listeners.contextPrepared(context);
11    if (this.logStartupInfo) {
12       logStartupInfo(context.getParent() == null);
13       logStartupProfileInfo(context);
14    }
15 
16   //向Spring容器注入springApplicationArguments和springBootBanner,實際執行是將main函數的參數args和printedBanner分別封裝成單例bean注冊到容器中。
17    // Add boot specific singleton beans
18    context.getBeanFactory().registerSingleton("springApplicationArguments",
19          applicationArguments);
20    if (printedBanner != null) {
21       context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
22    }
23 
24    // Load the sources
25    Set<Object> sources = getAllSources();
26    Assert.notEmpty(sources, "Sources must not be empty");
//加載啟動類,將啟動類也注入容器
27 load(context, sources.toArray(new Object[0])); 28   //發布容器已加載事件:執行所有SpringApplicationRunListener的contextLoaded方法,下面是EventPublishingRunListener中的contextLoaded 29 listeners.contextLoaded(context); 30 }

     具體load方法實現, load方法的作用其注釋寫的也很清楚,"Load beans into the application context",實際執行時可以發現主要是將我們的SpringBoot應用的啟動類(此處SpringbootdemoApplication2)注冊到容器中!前面if條件都為false,不執行,具體分析loader.loader()。

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

loader.load()源碼:

public int load() {
        int count = 0;
        for (Object source : this.sources) {
            count += load(source);
        }
        return count;
    }

    private int load(Object source) {
        Assert.notNull(source, "Source must not be null");
        if (source instanceof Class<?>) {
            return load((Class<?>) source);  //執行
        }
        if (source instanceof Resource) {
            return load((Resource) source);
        }
        if (source instanceof Package) {
            return load((Package) source);
        }
        if (source instanceof CharSequence) {
            return load((CharSequence) source);
        }
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

    private int load(Class<?> source) {
        if (isGroovyPresent()
                && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
            // Any GroovyLoaders added in beans{} DSL can contribute beans here
            GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
                    GroovyBeanDefinitionSource.class);
            load(loader);
        }
        if (isComponent(source)) {
            this.annotatedReader.register(source);
            return 1;
        }
        return 0;
    }

  isComponent(source)方法判斷當前類是不是有@Component注解,顯然啟動類的@SpringBootApplication這個組合注解是包含Component注解的。

      this.annotatedReader.register(source)方法內部調用Spring框架底層提供的AnnotatedBeanDefinitionReader.doRegisterBean(xxx)方法,最終將SpringBoot應用的啟動類注冊到容器中。

 

    7.  執行context的refresh,並且調用context的registerShutdownHook方法,refresh方法的具體邏輯分析可以參考:

      Spring源碼解析 – AnnotationConfigApplicationContext容器創建過程

  8. afterRefresh空方法

  9. 執行所有runlisteners的started方法,發布ApplicationStartedEvent事件

  10. 遍歷執行CommandLineRunner和ApplicationRunner

以上。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM