天天看點

Springboot如何啟動内置的tomcat(源碼解析)?

傳統項目為了啟動,會放在tomcat下面,那麼springboot為何不需要放在tomcat啟動呢??因為springboot有内置tomcat啟動項目,這篇文章從源碼分析springboot如何啟動内置tomcat。

// Allows post-processing of the bean factory in context subclasses.
        postProcessBeanFactory(beanFactory);

        // Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);

        // Register bean processors that intercept bean creation.
        registerBeanPostProcessors(beanFactory);

        // Initialize message source for this context.
        initMessageSource();

        // Initialize event multicaster for this context.
        initApplicationEventMulticaster();

        // Initialize other special beans in specific context subclasses.
        onRefresh();

        // Check for listener beans and register them.
        registerListeners();

        // Instantiate all remaining (non-lazy-init) singletons.
        finishBeanFactoryInitialization(beanFactory);

        // Last step: publish corresponding event.
        finishRefresh();      

registerBeanPostProcessors我們onRefresh就是實作了這個類,前面的postBeanFactory是對容器擴充,這裡的是對bean的對象進行擴充,有着beanPostProcessorBeaforeInstantiation和beanPostProcessorAfterInstantiation後置處理器,保證單執行個體 和 執行個體化完畢。

initApplicationEventMuliticaster是注冊廣播對象到容器,在實際代碼開發中,會和applicationEvent和applicationListener使用。

熟悉了這裡面的大緻邏輯之後,我們這篇文章主要介紹onRefresh()方法,核心方法在這裡:

//ServletWebServerApplicationContext.java
  @Override
  protected void onRefresh() {
    super.onRefresh();
    try {
      createWebServer();
    }
    catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
    }
  }


//父類的onRefresh
  @Override
  protected void onRefresh() {
    this.themeSource = UiApplicationContextUtils.initThemeSource(this);
  }      

他super的父類沒什麼邏輯,主要看這個createWenServer():

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
      getBeanFactory().registerSingleton("webServerGracefulShutdown",
          new WebServerGracefulShutdownLifecycle(this.webServer));
      getBeanFactory().registerSingleton("webServerStartStop",
          new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
      try {
        getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
        throw new ApplicationContextException("Cannot initialize servlet context", ex);
      }
    }
    initPropertySources();
  }      

這裡面邏輯主要是當servletContext為空或者webServer為空,則就通過servletWebServerFactory建立一個webServer,或者走else if 裡面的onStartup方法,最後都會執行initPropeertySources()。

那這幾個元件是什麼呢,首先上面說了servletWebServerFactory就是管理webServer的工廠,用來建立webServer。

servletContext指整個web請求是上下文對象,在tomcat中通常整個請求的上下文都封裝在這個對象中。

webServer則是核心的元件,裡面可以看到封裝了web容器的啟動和停止,擷取端口的核心操作,也就是webServer是web容器的抽象封裝。

@FunctionalInterface
public interface ServletWebServerFactory {

  /**
   * Gets a new fully configured but paused {@link WebServer} instance. Clients should
   * not be able to connect to the returned server until {@link WebServer#start()} is
   * called (which happens when the {@code ApplicationContext} has been fully
   * refreshed).
   * @param initializers {@link ServletContextInitializer}s that should be applied as
   * the server starts
   * @return a fully configured and started {@link WebServer}
   * @see WebServer#stop()
   */
  WebServer getWebServer(ServletContextInitializer... initializers);

}


public interface WebServer {

  /**
   * Starts the web server. Calling this method on an already started server has no
   * effect.
   * @throws WebServerException if the server cannot be started
   */
  void start() throws WebServerException;

  /**
   * Stops the web server. Calling this method on an already stopped server has no
   * effect.
   * @throws WebServerException if the server cannot be stopped
   */
  void stop() throws WebServerException;

  /**
   * Return the port this server is listening on.
   * @return the port (or -1 if none)
   */
  int getPort();

  /**
   * Initiates a graceful shutdown of the web server. Handling of new requests is
   * prevented and the given {@code callback} is invoked at the end of the attempt. The
   * attempt can be explicitly ended by invoking {@link #stop}. The default
   * implementation invokes the callback immediately with
   * {@link GracefulShutdownResult#IMMEDIATE}, i.e. no attempt is made at a graceful
   * shutdown.
   * @param callback the callback to invoke when the graceful shutdown completes
   * @since 2.3.0
   */
  default void shutDownGracefully(GracefulShutdownCallback callback) {
    callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
  }

}      

getWebServer方法上面的注釋翻譯就是:

擷取一個新的完全配置但暫停的{@link WebServer}執行個體。客戶應不能連接配接到傳回的伺服器,直到{@link WebServer#start()}是調用(當{@code ApplicationContext}被完全調用時發生重新整理)。

也就是說擷取一個配置完成的webServer執行個體,當調用start方法的時候,applicationContext容器也就是spring容器已經重新整理。

也就是說ServletWebServerFactory可以可以擷取一個webServer,webServer可以通過start和stop操作容器。也就是springboot對web容器的抽象封裝成為了webServer。

預設的我們會進入tomcatServletWebServerFactory裡:

@Override
  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);
  }      

1)這裡方法的入參是jdk1.8引入的函數表達式,這個表達式放了這個對象的數組,這個數組傳進來的接口就是ServletContextInitializer。

2)建立了核心的tomcat元件。

3)建立了connector,newConnector,以及springboot特有的coustomizeConnector。

4)通過configureEngine配置了tomcat的引擎。

5)準備tomcat和context相關的屬性。

6)真正的啟動tomcat。

Springboot如何啟動内置的tomcat(源碼解析)?

點開new 的tomcat我們可以看到,裡面有port端口号,hostname:localhost,是不是都非常熟悉。

1)還有wrapper相關操作的方法,比如addServlet方法就是傳回一個wrapper。 

2)有一堆元件的get方法,比如前面的engine、service、host,connection。

3)有一些context相關的方法,比如createContext。

基本可以看到,這個tomcat類封裝了幾乎tomcat的所有核心元件。

下面看connector的基本建立和擴充:

public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";

private String protocol = DEFAULT_PROTOCOL;

public WebServer getWebServer(ServletContextInitializer... initializers) {
   //其他
   Tomcat tomcat = new Tomcat();
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   connector.setThrowOnFailure(true);
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   //其他
}      

這裡可以看到connector傳遞的參數是上面的protocol,這個名稱為什麼用全名呢,我們從構造函數源碼的 反射就可以看到:

public static ProtocolHandler create(String protocol, boolean apr)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        if (protocol == null || "HTTP/1.1".equals(protocol)
                || (!apr && org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol))
                || (apr && org.apache.coyote.http11.Http11AprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.http11.Http11AprProtocol();
            } else {
                return new org.apache.coyote.http11.Http11NioProtocol();
            }
        } else if ("AJP/1.3".equals(protocol)
                || (!apr && org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol))
                || (apr && org.apache.coyote.ajp.AjpAprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.ajp.AjpAprProtocol();
            } else {
                return new org.apache.coyote.ajp.AjpNioProtocol();
            }
        } else {
            // Instantiate protocol handler
            Class<?> clazz = Class.forName(protocol);
            return (ProtocolHandler) clazz.getConstructor().newInstance();
        }
    }      

也就是說new connection核心就是建立了http的元件,這裡還有個擴充的關鍵點,就是customizeConnector()方法,這裡面有給connector配置很多屬性和配置。

// Needs to be protected so it can be used by subclasses
  protected void customizeConnector(Connector connector) {
    int port = Math.max(getPort(), 0);
    connector.setPort(port);
    if (StringUtils.hasText(getServerHeader())) {
      connector.setProperty("server", getServerHeader());
    }
    if (connector.getProtocolHandler() instanceof AbstractProtocol) {
      customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
    }
    invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
    if (getUriEncoding() != null) {
      connector.setURIEncoding(getUriEncoding().name());
    }
    // Don't bind to the socket prematurely if ApplicationContext is slow to start
    connector.setProperty("bindOnInit", "false");
    if (getSsl() != null && getSsl().isEnabled()) {
      customizeSsl(connector);
    }
    TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
    compression.customize(connector);
    for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
      customizer.customize(connector);
    }
  }

  private void customizeProtocol(AbstractProtocol<?> protocol) {
    if (getAddress() != null) {
      protocol.setAddress(getAddress());
    }
  }      

通過上面可以看到對protocolHandler和connector進行了擴充,分别同的對應方法是invokeProtocoHandlerCustomizers和customizer.customize方法。這裡面又跟這兩個屬性有關,

tomcatProtocolHandlerCustomizers和tomcatConnectorCustomizers

仔細可以看到這兩個參數屬于誰,TomcatServletWebServerFactory,前面說了webServer是通過ServletWebServerFactory擷取的,那麼這個TomcatServletWebServerFactory是哪裡擷取的呢,答案就是前面的onRefresh()。

protected ServletWebServerFactory getWebServerFactory() {
    // Use bean names so that we don't consider the hierarchy
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    if (beanNames.length == 0) {
      throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
          + "ServletWebServerFactory bean.");
    }
    if (beanNames.length > 1) {
      throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
          + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
  }      
Springboot如何啟動内置的tomcat(源碼解析)?

其實從源碼可以看到,雖然是從spring工廠按servletWebServerFactory類型查找bean的name,但是擷取到的是beanDefinition。不過沒關系,getBean容器中如果沒有beanDefinition,它會進行bean執行個體化。那麼bean的執行個體化會做什麼呢,也就是servletWebServerFactory執行個體化會做什麼呢,其實除了構造器之外,會給其擴充設定很多屬性,而tomcatProtocolHandlerCustomizers和tomcatConnectorCustomizers就是他的屬性,進而進行指派。

大體就是初始化protocol相關的配置,比如setMaxThreads預設200、minSpareThreads預設10、maxHttpHeaderSize預設8192byte、maxSwallowSize 2097152等等。

熟悉了這個擴充點的邏輯後,其實最關鍵的是如何使用它,你可以通過ServerProperties擴充配置值,也可以自定義tomcatConnectorCustomizers或者tomcatProtocolHandlerCustomizers,隻要實作對應的接口就可以了。這個才是領悟了SpringBoot的設計思路後最關鍵的。

TOMCAT的engine、context、host、wrapper關系

分析完connector元件後,再看看tomcat裡面的元件建立關系,他們的關系也并不複雜,就是tomcat的基礎知識,每個元件負責特定的事件處理。

Springboot如何啟動内置的tomcat(源碼解析)?

 springboot也對他們進行了擴充,比如對engine和context的閥門擴充,也是通過engineValues和contextValues進行擴充的。

//TomcatServletWebServerFactory.java
private List<Valve> engineValves = new ArrayList<>();

private List<Valve> contextValves = new ArrayList<>();      

springboot主要用TomcatServletWebServerFactory對tomcat進行封裝和擴充的。

下面就開始看prepareContext預處理servletContext:

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);
    try {
      context.setCreateUploadTargets(true);
    }
    catch (NoSuchMethodError ex) {
      // Tomcat is < 8.5.39. Continue.
    }
    configureTldPatterns(context);
    WebappLoader loader = new WebappLoader();
    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);
  }      

1)new TomcatEmbeddedContext

2)為tomcat的這個Context設定了很多值

3)執行了一個擴充點ServletContextInitializer

下面我們來看看ServletContextInitializer怎麼執行的,我們可以看到這行代碼:

//ServletWebServerApplicationContext.java
private void selfInitialize(ServletContext servletContext) throws ServletException {
   prepareWebApplicationContext(servletContext);
   registerApplicationScope(servletContext);
   WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
   for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
     beans.onStartup(servletContext);
  }
}      

這裡面主要有四個實作:

result = {ServletContextInitializerBeans@6345}  size = 4
0 = {DispatcherServletRegistrationBean@6339} "dispatcherServlet urls=[/]"
1 = {FilterRegistrationBean@6350} "characterEncodingFilter urls=[/*] order=-2147483648"
2 = {FilterRegistrationBean@6351} "formContentFilter urls=[/*] order=-9900"
3 = {FilterRegistrationBean@6352} "requestContextFilter urls=[/*] order=-105"      

完整的分析webServlet建立之後,基本就知道了springboot如何整合的tomcat,裡面有個tomcatServletWebServerFactory可以建立WebServer,最終怎麼啟動的呢?

webServcer裡面的方法有個strat(),調用之後才會真正啟動。

/TomcatServletWebServerFactory.java
   @Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
     //省略 Tomcat的建立、connector的建立和擴充、其他元件的建立、prepareContext的執行和擴充
    return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
     return new TomcatWebServer(tomcat, getPort() >= 0);
}
   public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
           Assert.notNull(tomcat, "Tomcat Server must not be null");
           this.tomcat = tomcat;
           this.autoStart = autoStart;
           initialize();
  }      
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);
      }
    }
  }      

上面方法邏輯看似多,其實最關鍵的就一句話。這裡核心是你抓大放小,主要關注一句話就可以了:

tomcat.start();

這個start方法執行的流程很有意思。它是類似一個鍊式調用。