溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SpringBoot啟動(dòng)流程SpringApplication源碼分析

發(fā)布時(shí)間:2023-04-03 16:07:03 來源:億速云 閱讀:87 作者:iii 欄目:開發(fā)技術(shù)

這篇“SpringBoot啟動(dòng)流程SpringApplication源碼分析”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“SpringBoot啟動(dòng)流程SpringApplication源碼分析”文章吧。

    SpringBoot啟動(dòng)流程源碼分析一、入口參數(shù)研究和創(chuàng)建對(duì)象

    準(zhǔn)備階段分析

    以下先看下SpringApplication的run()方法

    package org.springframework.boot;
    public ConfigurableApplicationContext run(String... args) {
       //1.計(jì)時(shí)器
       StopWatch stopWatch = new StopWatch();
       stopWatch.start();
       ConfigurableApplicationContext context = null;
       Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
       //2.headless配置
       configureHeadlessProperty();
       //3、獲取監(jiān)聽
       SpringApplicationRunListeners listeners = getRunListeners(args);
       listeners.starting();
       try {
       //應(yīng)用程序啟動(dòng)的參數(shù)  
          ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
          //4、準(zhǔn)備環(huán)境
          ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
          //環(huán)境創(chuàng)建成功后,配置bean信息,決定是否跳過 BeanInfo 類的掃描,如果設(shè)置為 true,則跳過
          configureIgnoreBeanInfo(environment);
          //打印banner信息
          Banner printedBanner = printBanner(environment);
          context = createApplicationContext();
          exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
          prepareContext(context, environment, listeners, applicationArguments, printedBanner);
          refreshContext(context);
          afterRefresh(context, applicationArguments);
          //停止計(jì)時(shí)
          stopWatch.stop();
          //控制是否打印日志的,這里為true,即打印日志
          if (this.logStartupInfo) {
             new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
          }
          listeners.started(context);
          callRunners(context, applicationArguments);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, listeners);
          throw new IllegalStateException(ex);
       }
       try {
          listeners.running(context);
       }
       catch (Throwable ex) {
          handleRunFailure(context, ex, exceptionReporters, null);
          throw new IllegalStateException(ex);
       }
       return context;
    }

    我將會(huì)根據(jù)執(zhí)行過程逐行進(jìn)行分析

    1、StopWatch計(jì)時(shí)器

    此類實(shí)則為計(jì)時(shí)器,如下對(duì)具體使用進(jìn)行分析

    StopWatch stopWatch = new StopWatch();
    //開始計(jì)時(shí)
    stopWatch.start();
    //停止計(jì)時(shí)
    stopWatch.stop();

    對(duì)于具體打印的上面寫的為

    //將當(dāng)前類傳入StartupInfoLogger創(chuàng)建了一個(gè)對(duì)象
    //然后調(diào)用logStarted打印日志
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    //創(chuàng)建一個(gè)Log類
    protected Log getApplicationLog() {
       if (this.mainApplicationClass == null) {
          return logger;
       }
       return LogFactory.getLog(this.mainApplicationClass);
    }
    //調(diào)用log類的log.info()方法來打印日志
    public void logStarted(Log log, StopWatch stopWatch) {
        if (log.isInfoEnabled()) {
                log.info(getStartedMessage(stopWatch));
        }
    }
    //打印詳細(xì)的日志
    private StringBuilder getStartedMessage(StopWatch stopWatch) {
       StringBuilder message = new StringBuilder();
       message.append("Started ");
       message.append(getApplicationName());
       message.append(" in ");
       message.append(stopWatch.getTotalTimeSeconds());
       try {
          double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
          message.append(" seconds (JVM running for " + uptime + ")");
       }
       catch (Throwable ex) {
          // No JVM time available
       }
       return message;
    }

    這里可以看到stopWatch.getTotalTimeSeconds()方法就是來獲取實(shí)際的計(jì)時(shí)時(shí)間的。再者,通過這幾行代碼,我們也可以考慮下平常在寫代碼的時(shí)候,有幾種日志打印方式?SpringBoot是怎么集成日志框架的?

    2、configureHeadlessProperty()

    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
    private void configureHeadlessProperty() {
       System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
             System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
    }

    這一部分代碼這樣理解吧,首先java.awt包提供了用于創(chuàng)建用戶界面和繪制圖形圖像的所有分類,那么 屬性SYSTEM_PROPERTY_JAVA_AWT_HEADLESS就一定會(huì)和用戶界面相關(guān)了。 這里將SYSTEM_PROPERTY_JAVA_AWT_HEADLESS設(shè)置為true,其實(shí)就是表示在缺少顯示屏、鍵盤或者鼠標(biāo)中的系統(tǒng)配置,如果將其設(shè)置為true,那么headless工具包就會(huì)被使用。

    3、getRunListeners(args) 獲取監(jiān)聽

    總體上可以分這三步

    • 獲取一個(gè)默認(rèn)的加載器

    • 根據(jù)類型獲取spring.factories中符合的類名

    • 創(chuàng)建類實(shí)例,返回

    如下將跟下代碼

    //獲取所有監(jiān)聽
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //啟動(dòng)監(jiān)聽
    listeners.starting();

    跳轉(zhuǎn)進(jìn)入getRunListeners方法

    private SpringApplicationRunListeners getRunListeners(String[] args) {
       Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
       return new SpringApplicationRunListeners(logger,
             getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
      //3.1獲取類加載器
       ClassLoader classLoader = getClassLoader();
       // Use names and ensure unique to protect against duplicates
       //3.2 根據(jù)類型獲取spring.factories中符合的類名
       Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
       //3.3 創(chuàng)建類實(shí)例
       List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
       //對(duì)實(shí)例進(jìn)行排序
       AnnotationAwareOrderComparator.sort(instances);
       return instances;
    }
    SpringApplicationRunListeners類解讀

    先看下SpringApplicationRunListeners類

    /**
     * A collection of {@link SpringApplicationRunListener}.
     *
     * @author Phillip Webb
     */
    class SpringApplicationRunListeners {
       private final Log log;
       private final List<SpringApplicationRunListener> listeners;
       SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
          this.log = log;
          this.listeners = new ArrayList<>(listeners);
       }

    SpringApplicationRunListeners類內(nèi)部關(guān)聯(lián)了SpringApplicationRunListener的集合,說白了就是用List集合存儲(chǔ)了SpringApplicationRunListeners類,那么,我們就需要了解一下這個(gè)類是干嘛的

    老規(guī)矩,先把源碼抬上來

    /**
     *//可以理解為Spring Boot應(yīng)用的運(yùn)行時(shí)監(jiān)聽器
     * Listener for the {@link SpringApplication} {@code run} method.
     *//SpringApplicationRunListener的構(gòu)造器參數(shù)必須依次為SpringApplication和String[]類型
     * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}
     * and should declare a public constructor that accepts a {@link SpringApplication}
     * instance and a {@code String[]} of arguments.
     *//每次運(yùn)行的時(shí)候?qū)?huì)創(chuàng)建一個(gè) SpringApplicationRunListener
      A new
     * {@link SpringApplicationRunListener} instance will be created for each run.
     *
     */
    public interface SpringApplicationRunListener {
       /**
        * Called immediately when the run method has first started. Can be used for very
        * early initialization.
        */
        //Spring應(yīng)用剛啟動(dòng)
       void starting();
       /**
        * Called once the environment has been prepared, but before the
        * {@link ApplicationContext} has been created.
        * @param environment the environment
        */
        //ConfigurableEnvironment準(zhǔn)備妥當(dāng),允許將其調(diào)整
       void environmentPrepared(ConfigurableEnvironment environment);
       /**
        * Called once the {@link ApplicationContext} has been created and prepared, but
        * before sources have been loaded.
        * @param context the application context
        */
        //ConfigurableApplicationContext準(zhǔn)備妥當(dāng),允許將其調(diào)整
       void contextPrepared(ConfigurableApplicationContext context);
       /**
        * Called once the application context has been loaded but before it has been
        * refreshed.
        * @param context the application context
        */
        //ConfigurableApplicationContext已裝載,但是任未啟動(dòng)
       void contextLoaded(ConfigurableApplicationContext context);
       /**
        * The context has been refreshed and the application has started but
        * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
        * ApplicationRunners} have not been called.
        * @param context the application context.
        * @since 2.0.0
        */
        //ConfigurableApplicationContext已啟動(dòng),此時(shí)Spring Bean已初始化完成
       void started(ConfigurableApplicationContext context);
       /**
        * Called immediately before the run method finishes, when the application context has
        * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
        * {@link ApplicationRunner ApplicationRunners} have been called.
        * @param context the application context.
        * @since 2.0.0
        */
        //Spring應(yīng)用正在運(yùn)行
       void running(ConfigurableApplicationContext context);
       /**
        * Called when a failure occurs when running the application.
        * @param context the application context or {@code null} if a failure occurred before
        * the context was created
        * @param exception the failure
        * @since 2.0.0
        */
        //Spring應(yīng)用運(yùn)行失敗
       void failed(ConfigurableApplicationContext context, Throwable exception);
    }

    單純的看源碼,是一個(gè)簡(jiǎn)單的接口,這時(shí)候我們可以看下作者給的注釋。理解部分就直接加到上面源碼中了。

    再看下他的實(shí)現(xiàn)類EventPublishingRunListener

    public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
       private final SpringApplication application;
       private final String[] args;
       private final SimpleApplicationEventMulticaster initialMulticaster;
       public EventPublishingRunListener(SpringApplication application, String[] args) {
          this.application = application;
          this.args = args;
          this.initialMulticaster = new SimpleApplicationEventMulticaster();
          for (ApplicationListener<?> listener : application.getListeners()) {
             this.initialMulticaster.addApplicationListener(listener);
          }
       }

    這里我們看到兩點(diǎn):

    • 構(gòu)造器參數(shù)和他實(shí)現(xiàn)的接口(上面剛分析了)注釋中規(guī)定的一致

    • 將SpringApplication中的ApplicationListener實(shí)例列表全部添加到了SimpleApplicationEventMulticaster對(duì)象中

    SimpleApplicationEventMulticaster是Spring框架的一個(gè)監(jiān)聽類,用于發(fā)布Spring應(yīng)用事件。因此EventPublishingRunListener實(shí)際充當(dāng)了Spring Boot事件發(fā)布者的角色。

    這里我再跟進(jìn)源碼的時(shí)候發(fā)現(xiàn),針對(duì)SpringBoot的事件/監(jiān)聽機(jī)制內(nèi)容還是挺多的,我們?cè)诔浞掷斫獾臅r(shí)候需要先了解Spring的事件/監(jiān)聽機(jī)制,后面將兩個(gè)結(jié)合后單獨(dú)進(jìn)行對(duì)比分析。

    3.1獲取類加載器getClassLoader()
    ClassLoader classLoader = getClassLoader();
    public ClassLoader getClassLoader() {
      if (this.resourceLoader != null) {
         return this.resourceLoader.getClassLoader();
      }
      return ClassUtils.getDefaultClassLoader();
    }

    這里的類加載器獲取首先是獲取resourceLoader的類加載器,獲取不到則獲取默認(rèn)的類加載器。 resourceLoader是資源加載器類,有具體的實(shí)現(xiàn)類。

    3.2 根據(jù)類型獲取spring.factories中符合的類名
    SpringFactoriesLoader.loadFactoryNames(type, classLoader)
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    //獲取類型名稱:org.springframework.context.ApplicationContextInitializer
       String factoryClassName = factoryClass.getName();
       return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    我們繼續(xù)對(duì)loadSpringFactories追下去

    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 {
            //執(zhí)行classLoader.getResources("META-INF/spring.factories"),表示通過加載器獲取META-INF/spring.factories下的資源
                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
                    UrlResource resource = new UrlResource(url);
                    //加載里面的屬性,屬性見下圖
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        //獲取key值
                        String factoryClassName = ((String)entry.getKey()).trim();
                        //獲取value值
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
    //這里是將查詢出來的key作為result的key,value轉(zhuǎn)換成字符數(shù)組存放到result的value中
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                //將結(jié)果集存入緩存中
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

    SpringBoot啟動(dòng)流程SpringApplication源碼分析

    default V getOrDefault(Object key, V defaultValue) {
        V v;
        return (((v = get(key)) != null) || containsKey(key))
            ? v
            : defaultValue;
    }

    這個(gè)的意思是如果沒有,則獲取一個(gè)空的list

    3.3創(chuàng)建實(shí)例createSpringFactoriesInstances()

    這一步其實(shí)就是將上一步從META-INF/spring.factories加載進(jìn)來的資源進(jìn)行實(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());
       for (String name : names) {
          try {
          //根據(jù)類加載器獲取指定類
             Class<?> instanceClass = ClassUtils.forName(name, classLoader);
             Assert.isAssignable(type, instanceClass);
             //根據(jù)參數(shù)獲取構(gòu)造器
             Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
             //根據(jù)傳入的構(gòu)造器對(duì)象以及構(gòu)造器所需的參數(shù)創(chuàng)建一個(gè)實(shí)例
             T instance = (T) BeanUtils.instantiateClass(constructor, args);
             //添加實(shí)例到集合中
             instances.add(instance);
          }
          catch (Throwable ex) {
             throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
          }
       }
       return instances;
    }

    4、環(huán)境準(zhǔn)備prepareEnvironment

    prepareEnvironment(listeners, applicationArguments)
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
          ApplicationArguments applicationArguments) {
       // Create and configure the environment
       //4.1 創(chuàng)建一個(gè)環(huán)境
       ConfigurableEnvironment environment = getOrCreateEnvironment();
       //4.2 配置環(huán)境
       configureEnvironment(environment, applicationArguments.getSourceArgs());
       //4.3 ConfigurationPropertySourcesPropertySource對(duì)象存入到第一位
       ConfigurationPropertySources.attach(environment);
       //listeners環(huán)境準(zhǔn)備(就是廣播ApplicationEnvironmentPreparedEvent事件)
       listeners.environmentPrepared(environment);
       // 將環(huán)境綁定到SpringApplication
       bindToSpringApplication(environment);
         // 如果是非web環(huán)境,將環(huán)境轉(zhuǎn)換成StandardEnvironment
       if (!this.isCustomEnvironment) {
          environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
       }
        // 配置PropertySources對(duì)它自己的遞歸依賴
       ConfigurationPropertySources.attach(environment);
       return environment;
    }
    4.1創(chuàng)建一個(gè)環(huán)境getOrCreateEnvironment
    private ConfigurableEnvironment getOrCreateEnvironment() {
    //有的話,直接返回
      if (this.environment != null) {
         return this.environment;
      }
      //這里我們?cè)谏厦嬉姷竭^,通過WebApplicationType.deduceFromClasspath()方法獲取的
      switch (this.webApplicationType) {
      case SERVLET:
         return new StandardServletEnvironment();
      case REACTIVE:
         return new StandardReactiveWebEnvironment();
      default:
         return new StandardEnvironment();
      }
    }

    這里創(chuàng)建了一個(gè)StandardServletEnvironment實(shí)例的環(huán)境 systemProperties用來封裝了JDK相關(guān)的信息 如下圖

    SpringBoot啟動(dòng)流程SpringApplication源碼分析

    systemEnvironment用來封轉(zhuǎn)環(huán)境相關(guān)的信息

    SpringBoot啟動(dòng)流程SpringApplication源碼分析

    封裝的還是挺詳細(xì)的哈。

    4.2 配置環(huán)境
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
       if (this.addConversionService) {
          ConversionService conversionService = ApplicationConversionService.getSharedInstance();
          environment.setConversionService((ConfigurableConversionService) conversionService);
       }
       configurePropertySources(environment, args);
       configureProfiles(environment, args);
    }

    setConversionService(ConfigurableConversionService conversionService)方法繼承于ConfigurablePropertyResolver接口, 該接口是PropertyResolver類型都將實(shí)現(xiàn)的配置接口。提供用于訪問和自定義將屬性值從一種類型轉(zhuǎn)換為另一種類型時(shí)使用的ConversionService的工具。PropertyResolver是用于針對(duì)任何底層源解析屬性的接口。

    configurePropertySources(environment, args);當(dāng)前方法主要是將啟動(dòng)命令中的參數(shù)和run 方法中的參數(shù)封裝為PropertySource。

    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    //獲取所有的屬性源,就是獲取4.1的ConfigurableEnvironment上獲取到的屬性
      MutablePropertySources sources = environment.getPropertySources();
      if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
         sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
      }
      //是否添加命令啟動(dòng)參數(shù),addCommandLineProperties為true,表示需要添加,但是前提是你得配置了參數(shù)
      if (this.addCommandLineProperties && args.length > 0) {
         String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
         if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(
                  new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
         }
         else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
         }
      }
    }

    configureProfiles(environment, args);環(huán)境配置

    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    //獲取激活的環(huán)境
      environment.getActiveProfiles(); // ensure they are initialized
      // But these ones should go first (last wins in a property key clash)
      Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
      profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
      //設(shè)置當(dāng)前的環(huán)境
      environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }
    4.3 ConfigurationPropertySourcesPropertySource對(duì)象存入
    public static void attach(Environment environment) {
       Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
       //獲取所有的屬性源,就是獲取4.1的ConfigurableEnvironment上獲取到的屬性
       MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
       //判斷是否有 屬性 configurationProperties
       PropertySource&lt;?&gt; attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
       if (attached != null &amp;&amp; attached.getSource() != sources) {
          sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
          attached = null;
       }
       if (attached == null) {
       // 將sources封裝成ConfigurationPropertySourcesPropertySource對(duì)象,并把這個(gè)對(duì)象放到sources的第一位置
          sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
                new SpringConfigurationPropertySources(sources)));
       }
    }

    以上就是關(guān)于“SpringBoot啟動(dòng)流程SpringApplication源碼分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI