springboot 容器化部署获取ip_Server

上一节我们主要分析了refreshContext中,主要有3个逻辑,如下图:

springboot 容器化部署获取ip_Server_02

上一节重点解析了invokeBeanFactoryPostProcessors执行容器扩展点,实现了自动装备配置、第三方执行扩展的执行。

今天我们继续分析refreshContext另一个重要的逻辑onRefresh()逻辑,让我们开始吧!

快速概览: onRefresh启动内嵌tomcat前的操作

refreshContext中onRefresh之前还有一些逻辑,我们先来快速看下它们主要做了什么。首先来看下代码:

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
              //省略

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

            //省略
    }

上面主要涉及了3个方法,从名字行就能猜出来它们做了什么:

1)registerBeanPostProcessors 通过扫描到BeanDefination中找出BeanPostProcessor,增加几个Bean的扩展点BeanPostProcessor 按4类顺序逐个增加。

回顾术语BeanPostProcessor是什么?

之前BeanFactoryPostProcessor是对容器的扩展,主要有一个方法,可以给容器设置属性,补充一些单例对象,补充一些BeanDefinition。

那BeanPostProcessor是对bean的扩展,有before和after两类方法,对Bean如何做扩展,在bean的创建前后,给bean补充一些属性等。

*2)initMessageSource 注册消息M essageSource对象到容器 *DelegatingMessageSource 国际化相关支持,默认的没有。

*3)initApplicationEventMulticaster 注册广播对象到容器 * 这个对象就是之前触发listener扩展点的广播对象。

熟悉了onRefresh方法之前的大体逻辑后,目前为止,整个rereshConext()执行的逻辑主要如下:

springboot 容器化部署获取ip_Server_03

onRefresh的核心脉络

熟悉了onRefresh方法之前的大体逻辑后,接下来我们就先研究下onRefresh的核心脉络在做什么了。

//ServletWebServerApplicationContext.java
    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }
    //父类GenericWebApplicationContext.java
    @Override
    protected void onRefresh() {
        this.themeSource = UiApplicationContextUtils.initThemeSource(this);
    }

这个onRefresh方法的脉络其实很简单,父类没有什么逻辑,核心应该就是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();
    }

这个逻辑其实很有意思,主要的核心脉络是if-else-if

1)如果webServer和servletContext为空,就创建一个WebServer,之后执行initPropertySources。

2)否则就使用getSelfInitializer,执行onStartup方法,之后执行initPropertySources。

可以默认情况webServer和servletContext为空的,这个我们在之前分析的整个流程中没看到过任何关于这两个组件的逻辑,或者你自己断点也能明显的找到代码执行的路径。

这里其实你会发现,判断走哪个分支的方法可能不止一种,你可以连蒙带猜,也可以断点走一下,也可以根据经验分析等等。方法有很多,大家千万分析原理或者源码的时候不要陷入追寻有哪些方法上。方法不是最重要的,适合你自己的就好。这个思想很关键,你可以模仿,但不能完全照搬,一定要结合自己情况考虑,最终才能成为你自己的。自己悟到的才是自己的,这些也是思想,也是最关键的。所以不要总问我有哪些方法,有时候是你自己悟出来的,我只能提醒或者建议。

好了,言归正传,这里实际走的路径就是第一条了。如下图所示:

springboot 容器化部署获取ip_Java_04

最终,你会发现onRefresh涉及的核心组件ServletWebServerFactoryWebServerServletContext

SpringBoot对web容器的抽象封装和设计

既然之前涉及到了几个组件ServletWebServerFactoryWebServerServletContext。 那它们是分别是什么东西呢?

其实从名字就能猜出很多东西,不难想到:

ServletContext,这个是指处理整个web请求是的上下文对象,在Tocmat中通常是整个请求的上下文参数都封装在这个对象中了,非常关键的对象。

ServletWebServerFactory和WebServer是什么?很明显ServletWebServerFactory是个工厂,用来创建WebServer。

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

}

从上面两个接口的设计和注释看

首先ServletWebServerFactory的getWebServer注释翻译: 获取一个新的完全配置但暂停的 {@link WebServer} 实例。 客户应无法连接到返回的服务器,直到 {@link WebServer#start()} 是调用(当 {@code ApplicationContext} 已完全刷新)。

也就是说,这个方法意思就是获取到一个配置好的webServer容器,在调用start方法时启动容器,启动时候ApplicationContext,也就是Spring容器已经完成了刷新。

WebServer接口封装了web容器的常见操作,如启动、停止,获取端口号之类的。

也就是说ServletWebServerFactory可以获得一个web容器,WebServer可以操作一个容器。

并且从下面的图中可以看出,它们有很多不同web容器的实现。整体如下图所示:

springboot 容器化部署获取ip_SpringBoot_05

综上,最终你可以理解为WebServer和ServletWebServerFactory,这一套,其实就是SpringBoot对web容器的抽象封装,WebServer可以代表了整个容器。

了解了onRefesh的整体脉络和关键的组件之后,我们来看下如何创建webServer的。

默认情况我们获取到的是TomcatServletWebServerFactory,通过它来创建

//TomcatServletWebServerFactory.java
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)入参是一个java8定义的函数表达式,也就是参数传递进来了一个方法,使用的是函数式接口ServletContextInitializer。这个方法在后面应该会被执行的。

2)创建了核心组件Tomcat,一会可以看下它的核心脉络,里面封装了Server

3)创建和配置组件Connector, new Connector()、customizeConnector这个是Tomcat的Connector组件相关

4)创建和配置组件Engine getEngine、 configureEngine tomcat的Engine组件相关设置

5)prepareContext 准备tomcat的context相关

6)getTomcatWebServer 真正启动tomcat

画成图如下所示:

springboot 容器化部署获取ip_Java_06

看完这个方法后,你可能对这里涉及的很多组件比较陌生,因为涉及到了很多tomcat的组件。不过没有关系,你可以通过之前学习的方法来梳理这些组件的关系。就算不知道每个组件是干嘛的,也可以连蒙带猜下。

new Tomcat核心组件和脉络分析

这里我就来带教大家一起用之前的方法和思路分析一下它们的核心脉络吧。

首先第一个组件就是Tomcat这个类的创建。老方法,可以看下这个类脉络、构造方法之后,画一个图。

首先看下构造方法

public Tomcat() {
        ExceptionUtils.preload();
    }

你会发现什么都么有,只有一个异常工具预加载的处理。一看就不是重点。

那就再看下这个类的整体脉络吧:

springboot 容器化部署获取ip_SpringBoot_07

springboot 容器化部署获取ip_SpringBoot_08

看完这个类的脉络,可以看出来Tomcat这个类主要有 1)对Wrapper相关的操作方法,比如addServelt方法就是返回一个Wrapper。

2)有Context相关一些方法,createContext、addWebapp之类的

3)有一堆组件的get方法,比如Connector、Engine、Service、Server、Host

4)最后就是一些属性了,比如Server对象、端口号、hostname、用户权限相关Userpass/UserRole之类的。

虽然我们不知道这个类里面的那些组件是干嘛的。但是起码我们有了一个印象。可以感受到这个Tomcat类,封装了几乎Tomcat所有的核心组件,是一个对tomcat容器的一个抽象对象,代表了整个tomcat。

最后我们可以画图先列举下看完Tomcat这个类的构造函数和类脉络中,主要涉及概念或者说是组件,如下图所示:

springboot 容器化部署获取ip_封装_09

不知道你们目前是什么感受,感觉有好多新的概念。如果你不了解tomcat的原理的话,第一次看到这一大堆组件,肯定有点懵的。

不过没关系,你其实可以连蒙带猜,或者抓大放小,因为我们主要还是看SpringBoot如何启动内嵌tomcat,如何和tomcat整合Spring容器的。

所以你没必要非要弄清楚这些组件,等之后我们Tomcat成长记,研究tomcat的原理和源码时候再来仔细弄清楚。

这里我们还是找到关注的重点就可以了。

好,我们接着向下分析。

Connector基本创建和扩展设计

最高层的抽象封装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创建默认传入了一个Http11NioProtocol类的全名,当然你可以通过set方法修改这个protocol的默认值,只要获取到TomcatServletWebServerFactory就可以修改对吧?

至于为啥传递了类的全名,你猜想下都知道,它内部可能是通过反射创建了这个类,并把这个组件设置给了Connector。我们来看下是不是:

public Connector(String protocol) {
    boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
            AprLifecycleListener.getUseAprConnector();

    if ("HTTP/1.1".equals(protocol) || protocol == null) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
        }
    } else if ("AJP/1.3".equals(protocol)) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
        }
    } else {
        protocolHandlerClassName = protocol;
    }

    // Instantiate protocol handler
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {
        this.protocolHandler = p;
    }

    // Default for Connector depends on this system property
    setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}

这个构造函数最关键的就是3行代码:

public Connector(String protocol) {
  Class<?> clazz = Class.forName(protocolHandlerClassName);
  p = (ProtocolHandler) clazz.getConstructor().newInstance();
  this.protocolHandler = p;
 }

也就是说其实new Connector核心就做了一件事情:创建了一个Http11NioProtocol组件。

这个从名字上看就是一个NIO相关的通信组件,内部应该会有Selector、Channel、Bytebuffer等NIO核心组件的。

至于Http11NioProtocol如何创建的这里我就不带大家深究了,你可以分析它的构造函数、类脉络、画一个组件图分析下它的创建过程,或者之后我们Tomcat成长记会详细分析的,可之后带大家一起分析下。

到这里先画个图小结下:

springboot 容器化部署获取ip_tomcat_10

new Connector之后就是非常关键的扩展点执行了customizeConnector()方法。

这个方法实际是SpringBoot对Connector扩展设计的接入,可以修改Connector中很多配置和属性,让我们来一起看下。

private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();

private Set<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new LinkedHashSet<>();

protected void customizeConnector(Connector connector) {
        int port = Math.max(getPort(), 0);
        connector.setPort(port);
        if (StringUtils.hasText(this.getServerHeader())) {
            connector.setAttribute("server", this.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);
        }
}

这个扩展方法核心的逻辑就是在给connector进行一些属性设置,核心通过了两个扩展进行调用。

1)invokeProtocolHandlerCustomizers 执行对ProtocolHandler扩展

2)customizer.customize(connector); 执行对Connector的扩展

其实可以看到触发的都是tomcatConnectorCustomizers、tomcatProtocolHandlerCustomizers这两个集合中的扩展类。、

整体如下图所示:

springboot 容器化部署获取ip_Server_11

我们先不着急看这些扩展类做了什么,首先的得思考下,tomcatConnectorCustomizers、tomcatProtocolHandlerCustomizers这两个集合中的扩展类什么时候设置的值呢?

其实你想下,这两个属性属于谁呢?没错,属于TomcatServletWebServerFactory。而这个类是不是之前通过ServletWebServerApplicationContext执行onRefresh脉络时候获取到的呢?如下图:

springboot 容器化部署获取ip_SpringBoot_12

对应获取的代码如下:

//ServletWebServerApplicationContext.java
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);
}

可以看到的是,这个方法核心就是通过getBean从容器获取一个对象。但是其实容器中并没有ServletWebServerFactory这个对象,只有它的BeanDefinition。

为什么呢?因为之前我们执行ConfigurationClassPostProcessor时候,只是加载了Bean对应的BeanDefinition而已。

不过没关系,getBean中的逻辑是,如果容器没有但是有对应的BeanDefinition,它会进行Bean实例化,Bean的实例化我们下一节会详细讲,这里只是简单提下。

那这个bean,ServletWebServerFactory实例化的时候会做什么呢?除了基本构造函数外,其实Bean实例化的过程有很多扩展点,可以为bean设置属性。

好,那关键的就来了,tomcatConnectorCustomizers、tomcatProtocolHandlerCustomizers既然是ServletWebServerFactory的两个属性,肯定就可以通过Bean实例化时候的扩展点,给这两个属性设置进去值。

最终,我给大家概况如下图所示:

springboot 容器化部署获取ip_封装_13

至于Connector中每个customizer做了哪些事情,这里我们不去详细分析了

大体就是初始化protocol相关的配置,比如setMaxThreads默认200、minSpareThreads默认10、maxHttpHeaderSize默认8192byte 、maxSwallowSize 2097152等等。

熟悉了这个扩展点的逻辑后,其实最关键的是如何使用它,你可以通过ServerProperties扩展配置值,也可以自定义tomcatConnectorCustomizers或者tomcatProtocolHandlerCustomizers,只要实现对应的接口就可以了。这个才是领悟了SpringBoot的设计思路后最关键的。

术语普及:Tomcat的Engine、Context、Host、Wrapper关系

分析完了Connector的创建之后,其他的组件其实就是普通的创建,建立关联关系而已。它们的关系其实不复杂,属于tomcat的基本知识,这里我通过一个tomcat流程执行图给大家介绍下它们的关系即可。它们之间的关系如下图所示:

springboot 容器化部署获取ip_tomcat_14

这些组件每个都有自己的职责,你大体了解上述组件的关系就可以了,我们就不展开分析了。

当然SpringBoot也有一些对它们的扩展,比如对Engine、Context阀门的扩展。也是通过engineValves、contextValves两个list属性进行扩展。

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

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

只不过这两个集合默认是空的,你可以通过TomcatServletWebServerFactory对他们进行设置和扩展。

这里我也不展开了。

记住,只要你理解了SpringBoot围绕TomcatServletWebServerFactory对tomcat做封装和扩展是关键,就可以了。

prepareContext 中的扩展点ServletContextInitializer

前面一堆组件创建完成后,还有一个比较有意思的操作就是prepareContext 。

让我们来看下吧!它的代码如下:

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

整个方法的脉络其实不复杂,主要就是:

1)new TomcatEmbeddedContext

2)为tomcat的这个Context设置了很多值

3)执行了一个扩展点ServletContextInitializer

整体如下图所示:

springboot 容器化部署获取ip_SpringBoot_15

至于扩展点,ServletContextInitializer执行了什么?

其实可以看下它的逻辑。它是使用了java8的特性,通过一个函数式接口传入过来的方法,也就是说,通过方法参数传递过来了一个行为,而不是一个变量。

我们可以找到传入的位置:

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

方法其实不复杂,核心就是触发了ServletContextInitializer的所有实现,执行了扩展方法onStartup。

默认主要有4个实现:

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"

其实从名字就看出来了,它的含义是往ServletContext中注册一堆Servelt、Filter等等。

这个扩展点还是比较关键的。

整体如下图所示:

springboot 容器化部署获取ip_SpringBoot_16

思考:tomcat和SpringBoot怎么整合的?

分析完了整个WebServer的创建后,其实你就会发现:

最终是Spring的容器ServletWebServerApplicationContext创建了WebServer,它持有了这对象,也就有了Tomcat整个抽象封装。

自然它们就整合到了一起了。

内嵌Tomcat最终的启动

之前分析的整个逻辑都是webServer这个对象的创建,之前从注释我们就知道,创建的webServer只是一个配置完成,停止的web容器,web容器并没有启动。只有调用webServer#start()这个方法,容器才会真正启动。

所以,最后我们来分析下tomcat是如何启动的。启动的代码就是getWebServer的最后一行,代码如下:

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

可以看到上面的代码脉络是:通过一系列的方法调用最终将创建的tomcat对象,有封装了一下,封装为了TomcatWebServer对象,之后执行了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方法执行的流程很有意思。它是类似一个链式调用。

其实你从之前tomcat的组件图就可以猜到,它们组件层级关系很多,每个组件都会触发下一层组件的逻辑。

每个组件都有生命周期,比如init方法-->start()-->destory()之类的。

那么也就说tomcat会以链的方式逐级调用各个模块的init()方法进行初始化, 待各个模块都初始化后, 又会逐级调用各个模块的start()方法启动各个模块。

整体大概如下图所示:

springboot 容器化部署获取ip_tomcat_17

小结

最后我们小结下,今天我们主要分析了SpringBoot在onRefresh方法中如何启动的tomcat:

1)快速该来了 onRefresh启动内嵌tomcat前的操作

2)分析了onRefresh的核心脉络

3)思考了SpringBoot对web容器的抽象封装和设计

4)对new Tomcat进行了核心组件和脉络分析

5)分析了Connector基本创建和扩展设计

6)术语普及:Tomcat的Engine、Context、Host、Wrapper关系

7)prepareContext 中的扩展点ServletContextInitializer

8)思考了:tomcat和SpringBoot怎么整合的?

9)内嵌Tomcat最终的启动

最后补充一点思想:

整个过程中有的是知识点,有的是一些设计的思考、扩展点的思考。大家一定要学会抓重点。这个能力非常关键。

这就是涉及到了一个能力模型的层次,可以有这样一种划分

第一个层次:知识、技术基本的使用,也就是说我们从了解知识到使用它做好一件事是一个层次。这个可以体现在你学习技术上,带领团队做项目,或者学习任何新事物上。

第二个层次:通过思考和总结,抽象和提炼出事情的关键点。比如一个提炼设计思想,发现项目的关键节点、路径等。

最后一个层次:站在一定高度和视角,掌控和推进整体。这个就需要很多经验和像比你成功的人学习了,因为掌握力是可以练习的,但是视野和思想的高度,虽然可以经验积累,但是最快的方式就是像比你优秀的人学习,如果有一个导师的话那就更好了。