SpringBoot內置嵌入式Tomcat啟動原理


1.引言

        現在JavaEE開發基本離不開spring全家桶,spring面世以來極大地簡化了開發過程和代碼量,但是隨着spring版本迭代,功能越來越豐富和強大,帶來的問題就是有大量的配置文件需要去開發人員去編寫 ,所以springboot 應運而生,springboot 的理念是約定大於配置,極大地縮減了配置文件的量,借助springboot的自動配置甚至可以實現0配置,快速搭建項目,同時另外一個亮點就是內置servlet容器,不用再將代碼打成war包,然后再去部署到tomcat,再啟動tomcat,直接將項目打成jar包啟動,也是特別方便。

2.內置servlet容器的使用方法

(1)使用約定的也就是默認的容器。默認使用的是tomcat,只需要引入web的依賴就可以自動使用Tomcat作為默認的servlet容器啟動項目

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

(2)使用其他的內置容器

  除了tomcat ,springboot 還支持Undertow 和 Jetty,並且可以快速切換成任意一個。做法是排除tomcat,引入想要用的容器jar包

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

(3)不使用內置容器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- 移除嵌入式tomcat插件 -->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--添加servlet-api依賴--->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

3.Tomcat 基本結構及其用

 

 

   Tomcat中最頂層的容器是Server,代表着整個服務器,從上圖中可以看出,一個Server可以包含至少一個Service,用於具體提供服務。Service主要包含兩個部分:Connector和Container,是tomcat的核心。Connector用於處理連接相關的事情,並提供Socket與Request和Response相關的轉化,Container用於封裝和管理Servlet,以及具體處理Request請求。

 

 

        一個Tomcat中只有一個Server,一個Server可以包含多個Service,一個Service只有一個Container,但是可以有多個Connectors,這是因為一個服務可以有多個連接,如同時提供Http和Https鏈接,也可以提供向相同協議不同端口的連接

 

 

 

 多個 Connector 和一個 Container 就形成了一個 Service,有了 Service 就可以對外提供服務了,但是 Service必須依賴server 生存,所以整個 Tomcat 的生命周期由 Server 控制。

 

 

 

4.內置Tomcat啟動流程

tomcat 的啟動需要從main 函數入手,main 函數的run方法實際調用的是 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);
        // 發布了一個spring應用啟動事件
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准備應用啟動環境(StandardServletEnviroment)
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            //打印啟動banner
            Banner printedBanner = printBanner(environment);
            // 根據條件創建applicationContext,這里創建的是AnnotationConfigServletWebServerApplicationContext
            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;
    }
createApplicationContext(); 創建了一個 AnnotationConfigServletWebServerApplicationContext 這個context 是注解版的servletContext ,它的本質還是applicationContext,繼承和實現關系如下

 

 

 接下來執行refreshContext,刷新上下文,調用的是AbstractApplicationContext的refresh,這個方法已經不是在springboot 的包內了,而是在spring 中了,這個啟動流程是一個模板方法

  public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            // 獲取beanFactory,這個就是springIoc容器的祖先
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                //為容器子類添加特殊的postprocess
                this.postProcessBeanFactory(beanFactory);
                //執行postprocess
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 為Bean 添加后置處理器
                this.registerBeanPostProcessors(beanFactory);
                // 初始化國際化信息源
                this.initMessageSource();
                //初始化時間傳播器
                this.initApplicationEventMulticaster();
                // 刷新,這個方法是留給子類擴展使用的,tomcat的啟動就在這里執行
                this.onRefresh();
                //注冊時間監聽器
                this.registerListeners();
                //初始化單實例Bean,循環依賴,后置處理器,initMethod,awear 接口的實現,自動裝配,都在這里完成,boot 在這里加載了一些默認的bean,mvc相關的,條件注解相關的,共26個
                this.finishBeanFactoryInitialization(beanFactory);
                //初始化和發布Bean的生命周期事件
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

接下來重點看onRefresh如何啟動tomcat,子類ServletWebServerApplicationContext 復寫了這個方法

protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}
createWebServer
private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      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();
}

先獲取ServletWebServerFactory,然后通過工廠獲取具體的webServer,此時獲取的是TomcatServletWebServerFactory,同時這個接口的實現還有undertow和jetty的工廠。getWebServer 實現如下

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);
   }
   prepareContext(tomcat.getHost(), initializers);
   return getTomcatWebServer(tomcat);
}

主要就是創建tomcat 的server,service,connector,engine 這些核心組件,然后調用 getTomcatWebServer,初始化和啟動tomcat,

 

private void initialize() throws WebServerException {
   logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
   synchronized (this.monitor) {
      try {
         addInstanceIdToEngineName();

         Context context = findContext();
         context.addLifecycleListener((event) -> {
            if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
               // Remove service connectors so that protocol binding doesn't
               // happen when the service is started.
               removeServiceConnectors();
            }
         });

         // Start the server to trigger initialization listeners
         this.tomcat.start();

         // We can re-throw failure exception directly in the main thread
         rethrowDeferredStartupExceptions();

         try {
            ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
         }
         catch (NamingException ex) {
            // Naming is not enabled. Continue
         }

         // Unlike Jetty, all Tomcat threads are daemon threads. We create a
         // blocking non-daemon to stop immediate shutdown
         startDaemonAwaitThread();
      }
      catch (Exception ex) {
         stopSilently();
         destroySilently();
         throw new WebServerException("Unable to start embedded Tomcat", ex);
      }
   }
}

 

最后執行模板方法的最后一步finishRefresh,這個方法也被子類ServletWebServerApplicationContext復寫了,

protected void finishRefresh() {
   super.finishRefresh();
   WebServer webServer = startWebServer();
   if (webServer != null) {
      publishEvent(new ServletWebServerInitializedEvent(webServer, this));
   }
}
private WebServer startWebServer() {
   WebServer webServer = this.webServer;
   if (webServer != null) {
      webServer.start();
   }
   return webServer;
}

此時tomcat 還沒有真正啟動,當執行webServer.start()時會找到context ,並且load,此時才算項目啟動了

 

public void start() throws WebServerException {
   synchronized (this.monitor) {
      if (this.started) {
         return;
      }
      try {
         addPreviouslyRemovedConnectors();
         Connector connector = this.tomcat.getConnector();
         if (connector != null && this.autoStart) {
            performDeferredLoadOnStartup();
         }
         checkThatConnectorsHaveStarted();
         this.started = true;
         logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
               + getContextPath() + "'");
      }
      catch (ConnectorStartFailedException ex) {
         stopSilently();
         throw ex;
      }
      catch (Exception ex) {
         if (findBindException(ex) != null) {
            throw new PortInUseException(this.tomcat.getConnector().getPort());
         }
         throw new WebServerException("Unable to start embedded Tomcat server", ex);
      }
      finally {
         Context context = findContext();
         ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
      }
   }
}

 

最后再到main函數的

listeners.started(context);
callRunners(context, applicationArguments);

這兩個方法,分別執行容器啟動的監聽器的回調,和執行 ApplicationRunner 和 ApplicationRunner 這些類型Bean的調用。至此基本描述了springboot 中tomcat 的啟動過程,順帶些了一下spring Ioc容器的啟動流程。


免責聲明!

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



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