Spring MVC之Servlet2.x与Servlet3.x的区别

ServletContextListener/ServletContainerInitializer

Posted by Jay on August 16, 2019

Spring MVC之Servlet2.x与Servlet3.x的区别

一、基于Servlet 2.x的Web应用

1.定义Servlet
public class EchoServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter writer = response.getWriter();
        writer.println("Hello world!");
        writer.flush();
    }
}
2.定义Filter
public class EchoFilter implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("before...");
        chain.doFilter(request, response);
        System.out.println("after...");
    }

    public void destroy() {

    }
}
3.部署描述文件web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>EchoServlet</servlet-name>
        <servlet-class>servlet2.EchoServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>EchoServlet</servlet-name>
        <url-pattern>/echo</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>EchoFilter</filter-name>
        <filter-class>servlet2.EchoFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>EchoFilter</filter-name>
        <url-pattern>/echo/*</url-pattern>
    </filter-mapping>

</web-app>

在Servlet2.x中,Servlet、Filter的定义必须通过部署描述文件web.xml进行描述。

二、Servlet3.x特性

1.使用注解定义Servlet、Filter、Listener(无需定义web.xml)

Servlet:

// Servlet
@WebServlet(name = "echoServlet", urlPatterns = "/echo")
public class EchoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        PrintWriter writer = response.getWriter();
        writer.println("Hello world!+1");
        writer.flush();
    }
}

@WebServlet(urlPatterns = "/hello", loadOnStartup = 1,
        initParams = {
                @WebInitParam(name = "db", value = "jdbc://..."),
                @WebInitParam(name = "dbuser", value = "fred"),
                @WebInitParam(name = "dbpasswd", value = "fred")
        })
public class HelloServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        String jdbc = config.getInitParameter("db"); // 获取Servlet初始化参数
        System.out.println("db: " + jdbc);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("hello+2");
        resp.getWriter().flush();
    }

}

Filter:

@WebFilter(urlPatterns = "/hello", dispatcherTypes = DispatcherType.REQUEST)
public class HelloServletFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // ...
        chain.doFilter(request, response);
        System.out.println("filtered...");
    }

    @Override
    public void destroy() {

    }
}

Listener:

@WebListener("Auto configure Struts")
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        // register the servlet
        ServletRegistration.Dynamic registration =
                context.addServlet("echo", "servlet30.EchoServlet");
        // set init param
        registration.setInitParameter("com.jay.anno.config", "/WEB-INF/struts-com.jay.anno.config.xml");
        registration.setLoadOnStartup(2);
        // add mapping
        registration.addMapping("/echo");
        // Annotate with @MultipartConfig 处理multipart/form-data
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}
2.Multipart支持
@WebServlet("/multi")
@MultipartConfig(location = "/tmp")
public class MultiPartServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Part part = req.getPart("myFile"); // 读取文件
        InputStream in = part.getInputStream();
        // read file...
        byte[] data = new byte[1024];
        int len;
        while((len=in.read(data)) != -1) {
            System.out.println(new String(data, 0, len));
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
3.动态配置Servlet、Filter、Listener
(1) 使用ServletContextListener

ServletContextListener由应用程序开发人员提供实现,其contextInitialized方法在应用程序启动时调用。

// 编程式配置
@WebListener("Auto configure Struts")
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        // register the servlet 注册Servlet
        ServletRegistration.Dynamic registration =
                context.addServlet("echo", "servlet30.EchoServlet");
        // set init param 设置初始化参数
        registration.setInitParameter("com.jay.anno.config", "/WEB-INF/struts-com.jay.anno.config.xml");
        registration.setLoadOnStartup(2);
        // add mapping 添加映射
        registration.addMapping("/echo");
        // Annotate with @MultipartConfig 处理multipart/form-data
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

以上代码通过定义ServletContextListener实现,在其contextInitialized回调的时候动态配置EchoServlet。

(2) 使用ServletContainerInitializer

ServletContainerInitializer主要由框架实现,在应用程序启动时调用其onStartUp()方法,负责框架的初始化动作,如初始化控制器Servlet(对应Spring MVC,DispatcherServlet)、连接数据库等。下面给出简单示例:

@HandlesTypes(StrategyHandler.class) // 感兴趣的类型
public class MyServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
      	// 在这里动态配置Servlet、Filter、Listener
        System.out.println("scanned clazz:");
        c.forEach(clazz -> {
            System.out.println("\t" + clazz.getCanonicalName());
        });
    }
}

然后在META-INF/services目录下定义该实现,文件名为javax.servlet.ServletContainerInitializer,文件内容为initializer.MyServletContainerInitializer,Web容器通过SPI机制发现该实现。

输出:

scanned clazz:
	servlet30.handler.impl.BizStrategyHandler
4.异步Servlet

非常适用于以下情况的 web 应用程序:

  • 长处理时间或伪长处理时间(长轮询)
  • 等待资源释放 — 如数据库连接
  • 等待事件发生 — 如聊天消息
  • 等待缓慢服务的响应 — 如 web 服务
@WebServlet(urlPatterns = "/async", asyncSupported = true) // Servlet声明支持异步
public class AsyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Put the request in async mode 1.将请求置于异步模式,释放Tomcat请求线程
        AsyncContext asyncContext = req.startAsync();

        // Create an instance of handler
        SlowResourceHandler handler = new SlowResourceHandler(asyncContext);

        // Starts the process 2.启动工作线程处理该请求
        asyncContext.start(handler);

        // Request will not commit on return 对于请求的客户端,请求不会返回
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

public class SlowResourceHandler implements Runnable {

    private AsyncContext asyncContext;

    public SlowResourceHandler(AsyncContext asyncContext) {
        this.asyncContext = asyncContext;
    }

    public void run() {
        try {
            int i = ThreadLocalRandom.current().nextInt(3) + 1;
            TimeUnit.SECONDS.sleep(i); // 模拟长时间的处理
            asyncContext.getResponse().getWriter().println("finished..." + i);
        } catch (Exception e) {
            //
        }
        // 3.只有工作线程处理完毕,调用AsyncContext.complete()方法,客户端请求才返回
        asyncContext.complete();
    }
}

参考文章