SpringBoot中通过SpringBootServletInitializer如何实现容器初始化

在之前的《SpringBoot之二:部署Spring Boot应用程序方式》章节有过一个实践,需要启动类继承自SpringBootServletInitializer方可正常部署至常规tomcat下,其主要能够起到web.xml的作用。下面通过源码简单解析为何其能够替代web.xml。
 
本章概要
1、源码分析如何实现SpringBootServletInitializer整个加载过程;
2、实现自定义WebApplicationInitializer配置加载;
3、实现自定义ServletContainerInitializer 配置加载;
 
示例代码如下
1、首先web.xml主要配置各种servlet,filter,listener等,如常见的Log4jConfigListener、OpenSessionInViewFilter、CharacterEncodingFilter、DispatcherServlet等,此部分信息均是容器启动时加载。
2、在springboot中我们从SpringBootServletInitializer源码入手:
package org.springframework.boot.web.support;

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    //...
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // Logger initialization is deferred in case a ordered
        // LogServletContextInitializer is being used
        this.logger = LogFactory.getLog(getClass());
        WebApplicationContext rootAppContext = createRootApplicationContext(
                servletContext);
        if (rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                @Override
                public void contextInitialized(ServletContextEvent event) {
                    // no-op because the application context is already initialized
                }
            });
        }
        else {
            this.logger.debug("No ContextLoaderListener registered, as "
                    + "createRootApplicationContext() did not "
                    + "return an application context");
        }
    }
    //...
}
3、WebApplicationInitializer接口是通过spring的SpringServletContainerInitializer中指定的用于启动组件的。具体见《SpringMVC之五:自定义DispatcherServlet配置及配置额外的 servlets 和 filters》中的WebApplicationInitializer说明。
4、通过3中的说明可以很清楚的理解其服务启动容器加载过程配置的装载过程,在SpringServletContainerInitializer中可以发现所有WebApplicationInitializer实现类在执行onStartup方法前需要根据其注解@order值排序,下面自定义一个WebApplicationInitializer实现类:
package com.dxz.demo.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.web.WebApplicationInitializer;

@Order(1)
public class MyWebApplicationInitializer implements WebApplicationInitializer {
    private Logger logger = LoggerFactory.getLogger(MyWebApplicationInitializer.class);

    @Override
    public void onStartup(ServletContext paramServletContext) throws ServletException {
        logger.info("启动加载自定义的MyWebApplicationInitializer");
        System.out.println("启动加载自定义的MyWebApplicationInitializer");
    }

}
打成WAR包部署至常规tomcat下启动服务验证:
 
注:之前有专门讲解如何装载servlet、filter、listener的注解,且可以通过两种不同的方式。那么第三种方式可以通过WebApplicationInitializer的实现类来进行装载配置。但此方式仅限部署至常规容器下生效,采用jar方式应用内置容器启动服务不加载
 
5、既然可以通过自定义的WebApplicationInitializer来实现常规容器启动加载,那么我们是否可以直接自定义ServletContainerInitializer来实现启动加载配置呢:
5.1、首先编写一个待注册的servlet:
package com.dxz.demo.config;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Servlet4 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet4");
        resp.setContentType("text/html");
        resp.getWriter().write("Servlet4");
    }

    @Override
    public void init() throws ServletException {
        super.init();
        System.out.println("Servlet4 loadOnStart");
    }

}
5.2、编写实现ServletContainerInitializer的自定义实现类:
package com.dxz.demo.config;

import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyServletContainerInitializer implements ServletContainerInitializer {
    private Logger logger = LoggerFactory.getLogger(MyServletContainerInitializer.class);

    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        logger.info("启动加载自定义的MyServletContainerInitializer");
        System.out.println("启动加载自定义的MyServletContainerInitializer");
        ServletRegistration.Dynamic testServlet = servletContext.addServlet("servlet4",
                "com.shf.springboot.servlet.Servlet4");
        testServlet.setLoadOnStartup(1);
        testServlet.addMapping("/servlet4");
    }
}
5.3、对新增的servlet设置其请求路径,同时打成WAR包部署至tomcat启动服务,但请求http://localhost:8080/SpringBoot1/servlet4却失败,此时发现需要了解servlet3对于ServletContainerInitializer 的加载机制是如何的,在官方有类似这样的描述“该接口的实现必须声明一个JAR资源放到程序中的META-INF/services下,并且记有该接口实现类的全路径,才会被运行时(server)的查找机制或是其它特定机制找到”。那么我们先参考spring-web-4.3.2.RELEASE.jar中
我们可以将自定义的类配置到一个jar包下部署至WEB-INFlib目录下,
5.3.1、首先新建如下目录结构的文件并填写内容如下:
5.3.2、然后通过如下命令生成jar包
 
5.3.3、将myTest.jar放置WEB-INFlib目录下重启服务,再次请求:
 
 
注:与4中实现WebApplicationInitializer一样,该方式仅限于部署常规容器生效。故jar通过内置容器启动的服务无法加载servlet4配置。
原文地址:https://www.cnblogs.com/duanxz/p/3557823.html