SpringBoot 加载 Tomcat 容器流程

in Tech Java

简介

假设 SpringBoot 使用默认 Web 容器,也就是 Tomcat。以 war 包形式部署,是 Tomcat 启动时带动 IoC 容器的启动;以 jar 包形式部署,是 IoC 容器启动时带动嵌入式 Tomcat 的启动。但不管是传统 Spring XML 还是 SpringBoot 自动化配置,Web 容器与 SpringBoot 的交互,始终是基于 DispatcherServlet 来实现的。

接下来主要关心两个问题:

  1. SpringBoot 是如何加载 Tomcat 的?
  2. DispatcherServlet 是如何加载到 Tomcat 中?

createWebServer() 方法

Spring - IoC 源码阅读笔记:AbstractApplicationContext - onRefresh() 创建 Web 容器流程。

    private void createWebServer() {

        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        // 使用内置 web 容器时,尚未初始化。所以 webServer、servletContext  为 null
        if (webServer == null && servletContext == null) {
            // 该方法会将 DispatcherServlet 实例化,添加到 BeanFactory
            ServletWebServerFactory factory = getWebServerFactory();
            // 配置容器、装载 DispatcherServlet 
            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();
    }

getWebServerFactory() 方法

    protected ServletWebServerFactory getWebServerFactory() {
        // Use bean names so that we don't consider the hierarchy
        // 获取 ServletWebServerFactory 类型的 BeanName
        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));
        }
        // 从 Bean 工厂中获取 ServletWebServerFactory 类型对象
        // 该对象实例化过中会触发 ErrorPageRegistrarBeanPostProcessor 
        // 而 ErrorPageRegistrarBeanPostProcessor 获取 ErrorPageRegistrar 类型的对象时
        // 会实例化 ErrorPageCustomizer 类,继而实例化该类中的成员变量 DispatcherServletPath 接口的子类 
        // 也就是 DispatcherServletRegistrationBean 。该类的构造方法参数含 DispatcherServlet
        // 因此 BeanFactory 会实例化 DispatcherServlet 。
        // DispatcherServlet 在自动配置时已将 BeanDefinition 加入 BeanFactory
        return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }

getSelfInitializer() 方法

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        // 这里暂不延伸,按执行流程往下走
        return this::selfInitialize;
    }

getWebServer() 方法

    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        // 构建 Tomcat 服务
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        // 设置临时文件目录
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        // 构建 Connector
        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);
        // 构建 TomcatWebServer ,这里会将 DispatcherServlet 配置到 Tomcat
        return getTomcatWebServer(tomcat);
    }

prepareContext() 方法

    protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        File documentRoot = getValidDocumentRoot();
        // 构建 Context 对象
        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());
        // 设置 Local
        resetDefaultLocaleMapping(context);
        addLocaleMappings(context);
        context.setUseRelativeRedirects(false);
        try {
            context.setCreateUploadTargets(true);
        }
        catch (NoSuchMethodError ex) {
            // Tomcat is < 8.5.39. Continue.
        }
        configureTldSkipPatterns(context);
        // 构建 Webapp 对象 
        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);
    }

configureContext() 方法

    protected void configureContext(Context context, ServletContextInitializer[] initializers) {
        // 初始化器添加到 TomcatStarter
        TomcatStarter starter = new TomcatStarter(initializers);
        if (context instanceof TomcatEmbeddedContext) {
            TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
            embeddedContext.setStarter(starter);
            embeddedContext.setFailCtxIfServletStartFails(true);
        }
        // 将 TomcatStarter 设置到上下文中,这样在启动的时候就会被调用到啦
        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());
        }
        // 配置 Session
        configureSession(context);
        new DisableReferenceClearingContextCustomizer().customize(context);
        for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
            customizer.customize(context);
        }
    }

getTomcatWebServer() 方法

    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, getPort() >= 0);
    }

TomcatWebServer() 方法

org.springframework.boot.web.embedded.tomcat.TomcatWebServer#TomcatWebServer

    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        initialize();
    }

initialize() 方法

    private void initialize() throws WebServerException {
        // 控制台能看到输出
        logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                // 给引擎名称加个自增长id
                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();
                    }
                });
                // 启动服务器以触发初始化侦听器。
                // 也就是触发前面设置的初始化器会在这里被触发,进而回调到 selfInitialize 方法里
                // 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);
            }
        }
    }

selfInitialize() 方法

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        registerApplicationScope(servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        // getServletContextInitializerBeans() 获取继承了 ServletContextInitializer 接口的实现类
        // 前面的 DispatcherServletRegistrationBean 类间接实现了该接口,因此这里会被获取到
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }

onStartup() 方法

RegistrationBean#onStartup

    // Tomcat 调用的时候,会将其 ApplicationContextFacade 对象传递过来
    @Override
    public final void onStartup(ServletContext servletContext) throws ServletException {
        String description = getDescription();
        if (!isEnabled()) {
            logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
            return;
        }
        // 由子类实现
        register(description, servletContext);
    }

register() 方法

DynamicRegistrationBean#register

    @Override
    protected final void register(String description, ServletContext servletContext) {
        // 由子类实现
        D registration = addRegistration(description, servletContext);
        if (registration == null) {
            logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
            return;
        }
        configure(registration);
    }

addRegistration() 方法

ServletRegistrationBean#addRegistration
@Override
    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = getServletName();
        // 将 DispatcherServlet 对象添加到 Tomcat
        return servletContext.addServlet(name, this.servlet);
    }

总结

Tomcat 加载流程:

  1. 查找是否存在 WebServer 和 servletContext。不存在即使用内置容器
  2. 获取 ServletWebServerFactory 对象,用于区分 Jetty、Tomcat、Undertow。实例化该类过程中会将 DispatcherServlet 顺带实例化
  3. 构建配置 Tomcat 服务实例、Connector、Engine、Host、Context、Initializer 等
  4. 构建配置 TomcatWebServer 实例,启动 Tomcat 。启动过程中触发 Initializer,进而将 DispatcherServlet 添加到 Tomcat

DispatcherServlet 加载流程:

  1. 由 SpringBoot 自动配置将 DispatcherServlet 的 BeanDefinition 加入 BeanFactory
  2. 获取 WebServerFactory 对象时,附带实例化 DispatcherServlet
  3. 利用 Tomcat 初始化器,在 Tomcat 启动时,将 DispatcherServlet 加入 Tomcat