Spring Boot啟動過程

對于Spring Boot項目來說只需要如下代碼就可以啟動整個項目

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

那么Spring容器,Web容器等等是怎么啟動的?

  1. new SpringApplication
    /**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param resourceLoader the resource loader to use
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #setSources(Set)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    
    • WebApplicationType.deduceFromClasspath();推斷Web容器類型,具體參考容器推斷

    • getSpringFactoriesInstances(ApplicationContextInitializer.class)
      從Spring factories中獲取ApplicationContextInitializer.class對應(yīng)的實現(xiàn)類融柬,具體參考Spring factories

    •   //把對應(yīng)的實現(xiàn)加進Initializers集合
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //把對應(yīng)的實現(xiàn)加進Listeners集合
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
      
    • this.mainApplicationClass = deduceMainApplicationClass();
      一個有意思的寫法仰楚,根據(jù)錯誤堆棧獲取當前調(diào)用的類

      private Class<?> deduceMainApplicationClass() {
          try {
              //獲取當前的堆棧
              StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
              for (StackTraceElement stackTraceElement : stackTrace) {
                  //判斷方法名,獲取對應(yīng)的啟動類
                  if ("main".equals(stackTraceElement.getMethodName())) {
                      return Class.forName(stackTraceElement.getClassName());
                  }
              }
          }
          catch (ClassNotFoundException ex) {
              // Swallow and continue
          }
          return null;
      }
      

      至此举哟,完成了SpringApplication的初始化

  2. 調(diào)用SpringApplication的run方法
    public ConfigurableApplicationContext run(String... args) {
            //一個工具類,用于計算啟動時間
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            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);
            stopWatch.stop();
            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;
    }
    
    •   //獲取SpringApplicationRunListener接口對應(yīng)的實現(xiàn)然后封裝成SpringApplicationRunListeners迅矛,其實就是內(nèi)部維護了一個集合
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //觀察者模式妨猩,循環(huán)調(diào)用內(nèi)部集合進行通知
        listeners.starting();
      
    • ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);創(chuàng)建準備Environment對象

      private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
          // 根據(jù)推斷的web應(yīng)用類型,創(chuàng)建對應(yīng)的Environment
          ConfigurableEnvironment environment = getOrCreateEnvironment();
          // 對environment添加秽褒、刪除壶硅、重排序PropertySource
          // 設(shè)置environment的active profiles
          configureEnvironment(environment, applicationArguments.getSourceArgs());
          //通知environment準備好
          listeners.environmentPrepared(environment);
          bindToSpringApplication(environment);
          if (!this.isCustomEnvironment) {
              environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                      deduceEnvironmentClass());
          }
          ConfigurationPropertySources.attach(environment);
          return environment;
      }
      
    •   //根據(jù)推斷來的web應(yīng)用容器類型創(chuàng)建對應(yīng)的Spring context
        //servlet容器對應(yīng)的是AnnotationConfigServletWebServerApplicationContext
        context = createApplicationContext();
      
    • prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      準備Spring context

      private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
          SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
          context.setEnvironment(environment);
          postProcessApplicationContext(context);
          // 從Spring factories中加載到的ApplicationContextInitializer對應(yīng)的實例威兜,
          // 調(diào)用對應(yīng)實例的initialize方法,把Spring context傳進去
          applyInitializers(context);
          listeners.contextPrepared(context);
          if (this.logStartupInfo) {
              logStartupInfo(context.getParent() == null);
              logStartupProfileInfo(context);
          }
          // Add boot specific singleton beans
          ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
          beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
          if (printedBanner != null) {
              beanFactory.registerSingleton("springBootBanner", printedBanner);
          }
          if (beanFactory instanceof DefaultListableBeanFactory) {
              ((DefaultListableBeanFactory) beanFactory)
                      .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
          }
          // Load the sources
          Set<Object> sources = getAllSources();
          Assert.notEmpty(sources, "Sources must not be empty");
          load(context, sources.toArray(new Object[0]));
          listeners.contextLoaded(context);
      }
      
    • refreshContext(context);
      開始進入Web容器的啟動

      • 根據(jù)之前創(chuàng)建的Spring context實例庐椒,調(diào)用對應(yīng)的onRefresh()方法牡属。這里調(diào)用的是org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh()最終進入到
        private void createWebServer() {
            WebServer webServer = this.webServer;
            ServletContext servletContext = getServletContext();
            if (webServer == null && servletContext == null) {
                //webServer和servletContext都會空,會走到這里
                //從當前的BeanFactory獲取類型為ServletWebServerFactory的bean
                //這里獲取到的是TomcatServletWebServerFactory
                ServletWebServerFactory factory = getWebServerFactory();
                this.webServer = factory.getWebServer(getSelfInitializer());
            }
            else if (servletContext != null) {
                try {
                    getSelfInitializer().onStartup(servletContext);
                }
                catch (ServletException ex) {
                    throw new ApplicationContextException("Cannot initialize servlet context", ex);
                }
            }
            initPropertySources();
        }
        
        getSelfInitializer()獲取自身的初始化器扼睬,類型為ServletContextInitializer逮栅,這里返回的是一個lambda表達式,也就是返回了一個方法引用窗宇。
      • 至此措伐,獲取到了TomcatServletWebServerFactory實例和一個ServletContextInitializer類型的lambda表達式
        public WebServer getWebServer(ServletContextInitializer... initializers) {
            if (this.disableMBeanRegistry) {
                Registry.disableRegistry();
            }
            Tomcat tomcat = new Tomcat();
            File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
            tomcat.setBaseDir(baseDir.getAbsolutePath());
            Connector connector = new Connector(this.protocol);
            connector.setThrowOnFailure(true);
            tomcat.getService().addConnector(connector);
            customizeConnector(connector);
            tomcat.setConnector(connector);
            tomcat.getHost().setAutoDeploy(false);
            configureEngine(tomcat.getEngine());
            for (Connector additionalConnector : this.additionalTomcatConnectors) {
                tomcat.getService().addConnector(additionalConnector);
            }
            //準備tomcat context
            prepareContext(tomcat.getHost(), initializers);
            return getTomcatWebServer(tomcat);
        }
        
        /**
        準備tomcat context
        */
        protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
            File documentRoot = getValidDocumentRoot();
            TomcatEmbeddedContext context = new TomcatEmbeddedContext();
            if (documentRoot != null) {
                context.setResources(new LoaderHidingResourceRoot(context));
            }
            context.setName(getContextPath());
            context.setDisplayName(getDisplayName());
            context.setPath(getContextPath());
            File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
            context.setDocBase(docBase.getAbsolutePath());
            context.addLifecycleListener(new FixContextListener());
            context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
                    : ClassUtils.getDefaultClassLoader());
            resetDefaultLocaleMapping(context);
            addLocaleMappings(context);
            context.setUseRelativeRedirects(false);
            try {
                context.setCreateUploadTargets(true);
            }
            catch (NoSuchMethodError ex) {
                // Tomcat is < 8.5.39. Continue.
            }
            configureTldSkipPatterns(context);
            WebappLoader loader = new WebappLoader(context.getParentClassLoader());
            loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
            loader.setDelegate(true);
            context.setLoader(loader);
            if (isRegisterDefaultServlet()) {
                addDefaultServlet(context);
            }
            if (shouldRegisterJspServlet()) {
                addJspServlet(context);
                addJasperInitializer(context);
            }
            context.addLifecycleListener(new StaticResourceConfigurer(context));
            ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
            host.addChild(context);
            configureContext(context, initializersToUse);
            postProcessContext(context);
        }
        /**
        配置tomcat context
        */
        protected void configureContext(Context context, ServletContextInitializer[] initializers) {
            //初始化TomcatStarter,該類是tomcat容器初始化過程的一個委托或是代理的角色
            TomcatStarter starter = new TomcatStarter(initializers);
            if (context instanceof TomcatEmbeddedContext) {
                TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
                embeddedContext.setStarter(starter);
                embeddedContext.setFailCtxIfServletStartFails(true);
            }
            //在這里會進行ServletContextInitializer的初始化工作
            context.addServletContainerInitializer(starter, NO_CLASSES);
            for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
                context.addLifecycleListener(lifecycleListener);
            }
            for (Valve valve : this.contextValves) {
                context.getPipeline().addValve(valve);
            }
            for (ErrorPage errorPage : getErrorPages()) {
                org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
                tomcatErrorPage.setLocation(errorPage.getPath());
                tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
                tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
                context.addErrorPage(tomcatErrorPage);
            }
            for (MimeMappings.Mapping mapping : getMimeMappings()) {
                context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
            }
            configureSession(context);
            new DisableReferenceClearingContextCustomizer().customize(context);
            for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
                customizer.customize(context);
            }
        }
        
      • 最終ServletContext在啟動過程中會被ServletContextInitializer的onStartup進行配置军俊。會對ServletContext的任意servlet filters listeners context-params attributes進行必需的初始化配置侥加。這里的關(guān)于Spring Boot的Web容器啟動方式和傳統(tǒng)的War包部署啟動方式是有一定的差異
      • 在啟動時TomcatStarter會獲取到三個ServletContextInitializer的實例
        • (servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)
          是一個lambda表達式,為servletContext設(shè)置初始化參數(shù)
        • new SessionConfiguringInitializer(this.session)
          配置session和cookie相關(guān)操作
        • ServletWebServerApplicationContext.getSelfInitializer()這里返回的是一個lambda表達式粪躬,也就是返回了一個方法引用担败。
          private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
              return this::selfInitialize;
          }
          
          private void selfInitialize(ServletContext servletContext) throws ServletException {
              prepareWebApplicationContext(servletContext);
              registerApplicationScope(servletContext);
              WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
              for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
                  beans.onStartup(servletContext);
              }
          }
          
          執(zhí)行到這里的時候,會在這里獲取到四個對應(yīng)ServletContextInitializer實例
          • dispatcherServlet urls=[/]
          • characterEncodingFilter urls=[/*]
          • formContentFilter urls=[/*]
          • requestContextFilter urls=[/*]
      • tomcat都是通過編程的方式進行servlet等相關(guān)組件的注冊镰官,這里是用到Servlet 3.0規(guī)范的內(nèi)容提前。
        例如Servlet的注冊就是通過ServletRegistrationBean完成的。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泳唠,一起剝皮案震驚了整個濱河市狈网,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笨腥,老刑警劉巖拓哺,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脖母,居然都是意外死亡士鸥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門谆级,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烤礁,“玉大人,你說我怎么就攤上這事哨苛「胄祝” “怎么了币砂?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵建峭,是天一觀的道長。 經(jīng)常有香客問我决摧,道長亿蒸,這世上最難降的妖魔是什么凑兰? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮边锁,結(jié)果婚禮上姑食,老公的妹妹穿的比我還像新娘。我一直安慰自己茅坛,他們只是感情好音半,可當我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贡蓖,像睡著了一般曹鸠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斥铺,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天彻桃,我揣著相機與錄音,去河邊找鬼晾蜘。 笑死邻眷,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的剔交。 我是一名探鬼主播肆饶,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岖常!你這毒婦竟也來了抖拴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤腥椒,失蹤者是張志新(化名)和其女友劉穎阿宅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笼蛛,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡洒放,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了滨砍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片往湿。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惋戏,靈堂內(nèi)的尸體忽然破棺而出领追,到底是詐尸還是另有隱情,我是刑警寧澤响逢,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布绒窑,位于F島的核電站,受9級特大地震影響舔亭,放射性物質(zhì)發(fā)生泄漏些膨。R本人自食惡果不足惜蟀俊,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望订雾。 院中可真熱鬧肢预,春花似錦、人聲如沸洼哎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽噩峦。三九已至窑邦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間壕探,已是汗流浹背冈钦。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留李请,地道東北人瞧筛。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像导盅,于是被迫代替她去往敵國和親较幌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,982評論 2 361

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