美文网首页
Spring Boot与嵌入式servlet容器

Spring Boot与嵌入式servlet容器

作者: buzzerrookie | 来源:发表于2019-03-31 11:00 被阅读0次

    传统的Spring MVC工程部署时需要将WAR文件放置在servlet容器的文档目录内,而Spring Boot工程使用嵌入式servlet容器省去了这一步骤,本文分析Spring Boot中嵌入式servlet容器的创建和启动过程。

    刷新应用上下文

    Spring Boot的启动过程一文指出在Spring Boot工程中,Web环境下默认创建AnnotationConfigEmbeddedWebApplicationContext类型的应用上下文,它的刷新方法定义在它的父类EmbeddedWebApplicationContext中,相关代码如下:

    @Override
    public final void refresh() throws BeansException, IllegalStateException {
        try {
            super.refresh();
        }
        catch (RuntimeException ex) {
            stopAndReleaseEmbeddedServletContainer();
            throw ex;
        }
    }
    

    EmbeddedWebApplicationContext类重写的refresh方法在内部调用了基类AbstractApplicationContext的refresh方法,其代码如下所示:

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // 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();
            }
            // 省略一些代码
        }
    }
    
    • refresh方法可以看成是模板方法,子类可以重写prepareRefresh、onRefresh和finishRefresh等方法。

    EmbeddedWebApplicationContext类重写的onRefresh和finishRefresh方法如下:

    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createEmbeddedServletContainer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start embedded container",
                    ex);
        }
    }
    
    @Override
    protected void finishRefresh() {
        super.finishRefresh();
        EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
        if (localContainer != null) {
            publishEvent(
                    new EmbeddedServletContainerInitializedEvent(this, localContainer));
        }
    }
    
    • onRefresh方法首先调用基类的onRefresh方法,然后创建嵌入式servlet容器;
    • finishRefresh方法首先调用基类的finishRefresh方法,然后启动嵌入式servlet容器。

    创建嵌入式servlet容器

    EmbeddedWebApplicationContext类的createEmbeddedServletContainer方法创建嵌入式servlet容器,代码如下:

    private void createEmbeddedServletContainer() {
        EmbeddedServletContainer localContainer = this.embeddedServletContainer;
        ServletContext localServletContext = getServletContext();
        if (localContainer == null && localServletContext == null) {
            EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
            this.embeddedServletContainer = containerFactory
                    .getEmbeddedServletContainer(getSelfInitializer());
        }
        else if (localServletContext != null) {
            try {
                getSelfInitializer().onStartup(localServletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context",
                        ex);
            }
        }
        initPropertySources();
    }
    
    • 嵌入式servlet容器由EmbeddedServletContainer接口抽象,该接口的实现类有TomcatEmbeddedServletContainer、JettyEmbeddedServletContainer和UndertowEmbeddedServletContainer,分别包装了嵌入式Tomcat、Jetty和Undertow;
    • EmbeddedServletContainerFactory接口用于实际创建嵌入式servlet容器,该接口的实现类有TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory和UndertowEmbeddedServletContainerFactory,分别用于创建上述三种嵌入式servlet容器;
    • getEmbeddedServletContainerFactory方法从当前应用上下文中取得唯一的EmbeddedServletContainerFactory类型的bean,若有多个则报错。使用默认的自动配置时,该bean一定是TomcatEmbeddedServletContainerFactory、JettyEmbeddedServletContainerFactory或者UndertowEmbeddedServletContainerFactory中的一个。

    ServletContextInitializer接口

    在上述创建嵌入式servlet容器的过程中,EmbeddedServletContainerFactory接口方法的实参是getSelfInitializer方法的返回值,类型是ServletContextInitializer。ServletContextInitializer接口用于动态配置ServletContext,只有一个回调方法onStartup在容器启动时被调用。

    public interface ServletContextInitializer {
    
        void onStartup(ServletContext servletContext) throws ServletException;
    }
    

    ServletContextInitializer的类层次结构如下图所示,可见ServletRegistrationBean和FilterRegistrationBean都实现了该接口,它们分别向容器添加新的servlet和过滤器。


    ServletContextInitializer接口.png

    配置ServletContext

    getSelfInitializer方法的代码如下,只是调用了selfInitialize方法。

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        return new ServletContextInitializer() {
            @Override
            public void onStartup(ServletContext servletContext) throws ServletException {
                selfInitialize(servletContext);
            }
        };
    }
    

    容器启动时具体的配置动作由selfInitialize方法完成,其代码如下:

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareEmbeddedWebApplicationContext(servletContext);
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
                beanFactory);
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
                getServletContext());
        existingScopes.restore();
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
                getServletContext());
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    该方法主要做了以下工作:

    • prepareEmbeddedWebApplicationContext方法将应用上下文设置到ServletContext的属性中,过程与Spring MVC的启动过程一文中分析的ContextLoader初始化根应用上下文非常相似;
      protected void prepareEmbeddedWebApplicationContext(ServletContext servletContext) {
          Object rootContext = servletContext.getAttribute(
                  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
          if (rootContext != null) {
              if (rootContext == this) {
                  throw new IllegalStateException(
                          "Cannot initialize context because there is already a root application context present - "
                                  + "check whether you have multiple ServletContextInitializers!");
              }
              return;
          }
          Log logger = LogFactory.getLog(ContextLoader.class);
          servletContext.log("Initializing Spring embedded WebApplicationContext");
          try {
              servletContext.setAttribute(
                      WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
              if (logger.isDebugEnabled()) {
                  logger.debug(
                          "Published root WebApplicationContext as ServletContext attribute with name ["
                                  + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
                                  + "]");
              }
              setServletContext(servletContext);
              if (logger.isInfoEnabled()) {
                  long elapsedTime = System.currentTimeMillis() - getStartupDate();
                  logger.info("Root WebApplicationContext: initialization completed in "
                          + elapsedTime + " ms");
              }
          }
          catch (RuntimeException ex) {
              logger.error("Context initialization failed", ex);
              servletContext.setAttribute(
                      WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
              throw ex;
          }
          catch (Error ex) {
              logger.error("Context initialization failed", ex);
              servletContext.setAttribute(
                      WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
              throw ex;
          }
      }
      
    • 调用其他ServletContextInitializer的回调方法,如ServletRegistrationBean和FilterRegistrationBean分别向容器添加新的servlet和过滤器。

    启动嵌入式servlet容器

    EmbeddedWebApplicationContext类的startEmbeddedServletContainer方法启动先前创建的嵌入式servlet容器,在内部调用EmbeddedServletContainer的start接口方法:

    private EmbeddedServletContainer startEmbeddedServletContainer() {
        EmbeddedServletContainer localContainer = this.embeddedServletContainer;
        if (localContainer != null) {
            localContainer.start();
        }
        return localContainer;
    }
    

    嵌入式Tomcat

    在Spring Boot中,嵌入式Tomcat由TomcatEmbeddedServletContainer类包装,该类的实例创建于TomcatEmbeddedServletContainerFactory。
    TomcatEmbeddedServletContainerFactory类实现了EmbeddedServletContainerFactory接口,实现的接口方法如下:

    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        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 getTomcatEmbeddedServletContainer(tomcat);
    }
    
    protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
            Tomcat tomcat) {
        return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
    }
    
    • 首先新建Tomcat实例,然后设置工作目录,最后绑定并自定义Connector、Engine和Context等Tomcat组件,关于这些组件的功能可以参考笔者以前的Tomcat分析系列
    • getTomcatEmbeddedServletContainer方法返回包装有嵌入式Tomcat的TomcatEmbeddedServletContainer实例。

    相关文章

      网友评论

          本文标题:Spring Boot与嵌入式servlet容器

          本文链接:https://www.haomeiwen.com/subject/gvqjbqtx.html