Servlet3.x 新特性小结 - TFdream/blog GitHub Wiki

Servlet3.0 新特性

Servlet3.0支持以下几个主要特性:

  • 异步请求
  • 注解配置
  • 可插拔性(pluggability)支持
  • ServletContext运行时动态部署
  • 支持上传文件
  • 非阻塞IO

Servlet3.0 新特性

一、异步请求

从Servlet3.0开始,具备异步请求响应编程模型。即servlet实例所在线程可以不用等待response写返回,而直接释放serlvet线程,因为有些response有比较大的开销,可能会消耗过大的时间,造成线程阻塞,比如等待JDBC连接,等待网络请求等。

  1. 使用线程池
/**
 * @author Ricky Fung
 */
public class DemoServlet extends HttpServlet {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final AtomicInteger counter = new AtomicInteger(1);
    private ExecutorService pool = Executors.newFixedThreadPool(5);

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final int seq = counter.getAndIncrement();
        logger.info("收到请求:{}", seq);
        final AsyncContext context = req.startAsync();
        context.setTimeout(10*1000);
        //1.开启异步线程
        pool.execute(new Runnable() {
            @Override
            public void run() {

                int time = RandomUtils.genRandom(5000);
                logger.info("请求seq:{} 休眠{} ms", seq, time);
                try {
                    sleep(time);
                    logger.info("请求seq:{}休眠结束,返回结果", seq);
                    String result = String.format("request seq:%s success", seq);
                    context.getResponse().getWriter().write(result);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    context.complete();
                }
            }
        });
    }

    private void sleep(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }
}

  1. 不采用线程池
/**
 * @author Ricky Fung
 */
@WebServlet(urlPatterns = "/foo", asyncSupported = true)
public class FooServlet extends HttpServlet {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final AtomicInteger counter = new AtomicInteger(1);

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final int seq = counter.getAndIncrement();
        logger.info("收到请求:{}", seq);
        final AsyncContext context = req.startAsync();
        context.setTimeout(10*1000);
        //1.开启异步线程
        context.start(new Runnable() {
            @Override
            public void run() {

                int time = (int) (Math.random() * 5000);
                logger.info("请求seq:{} 休眠{} ms", seq, time);
                try {
                    sleep(time);
                    logger.info("请求seq:{} 休眠结束,返回结果", seq);
                    context.getResponse().getWriter().write("success "+seq);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    context.complete();
                }

            }
        });
    }

    private void sleep(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }
}

web.xml

<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_3_1.xsd"
         version="3.1">

    <display-name>servlet3x feature demo</display-name>

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>com.mindflow.servlet3.demo.filter.CharacterEncodingFilter</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>DemoServlet</servlet-name>
        <servlet-class>com.mindflow.servlet3.demo.servlet.DemoServlet</servlet-class>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>DemoServlet</servlet-name>
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

使用Servlet 3.x 新特性时可能会出现

java.lang.IllegalStateException: A filter or servlet of the current chain does not support asynchronous operations.

出现这种错误的原因是: Servlet 或者Filter 实现必须增加<async-supported>true</async-supported>, 如果是通过注解配置的话,需要设置 asyncSupported = true,如下:

@WebServlet(urlPatterns = "/foo", asyncSupported = true)
public class FooServlet extends HttpServlet {
}

二、注解配置

Servlet 3.0及以上版本中,支持通过注解配置。

1. @WebServlet

@WebServlet 用于将一个类声明为 Servlet,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为 Servlet。该注解具有下表给出的一些常用属性(以下所有属性均为可选属性,但是 vlaue 或者 urlPatterns 通常是必需的,且二者不能共存,如果同时指定,通常是忽略 value 的取值)。

用@WebServlet注解的类必须继承javax.servlet.http.HttpServlet。

示例如下:

/**
 * @author Ricky Fung
 */
@WebServlet(urlPatterns = "/foo", asyncSupported = true)
public class FooServlet extends HttpServlet {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final AtomicInteger counter = new AtomicInteger(1);

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

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final int seq = counter.getAndIncrement();
        logger.info("收到请求:{}", seq);
        final AsyncContext context = req.startAsync();
        context.setTimeout(10*1000);
        //1.开启异步线程
        context.start(new Runnable() {
            @Override
            public void run() {

                int time = (int) (Math.random() * 5000);
                logger.info("请求seq:{} 休眠{} ms", seq, time);
                try {
                    sleep(time);
                    logger.info("请求seq:{} 休眠结束,返回结果", seq);
                    context.getResponse().getWriter().write("success "+seq);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    context.complete();
                }

            }
        });
    }

    private void sleep(long millis) throws InterruptedException {
        Thread.sleep(millis);
    }
}

如此配置之后,就可以不必在 web.xml 中配置相应的 和 元素了,容器会在部署时根据指定的属性将该类发布为 Servlet。它的等价的 web.xml 配置形式如下:

    <servlet>
        <servlet-name>fooServlet</servlet-name>
        <servlet-class>com.mindflow.servlet3.demo.servlet.FooServlet</servlet-class>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>fooServlet</servlet-name>
        <url-pattern>/foo</url-pattern>
    </servlet-mapping>

2. @WebInitParam

该注解通常不单独使用,而是配合 @WebServlet 或者 @WebFilter 使用。它的作用是为 Servlet 或者过滤器指定初始化参数,这等价于 web.xml 中 和 的 子标签。

示例如下:

/**
 * @author Ricky Fung
 */
@WebFilter(urlPatterns = "/*", asyncSupported = true, initParams = {@WebInitParam(name = "encoding", value = "utf-8")})
public class DemoFilter  implements Filter {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        logger.info("doFilter start");
        chain.doFilter(request, response);
        logger.info("doFilter start");
    }

    @Override
    public void destroy() {
        logger.info("destroy");
    }
}

3. @WebFilter

@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。该注解具有下表给出的一些常用属性 ( 以下所有属性均为可选属性,但是 value、urlPatterns、servletNames 三者必需至少包含一个,且 value 和 urlPatterns 不能共存,如果同时指定,通常忽略 value 的取值 )。

示例如下:

/**
 * @author Ricky Fung
 */
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class DemoFilter  implements Filter {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        logger.info("doFilter start");
        chain.doFilter(request, response);
        logger.info("doFilter start");
    }

    @Override
    public void destroy() {
        logger.info("destroy");
    }
}

如此配置之后,就可以不必在 web.xml 中配置相应的 和 元素了,容器会在部署时根据指定的属性将该类发布为过滤器。它等价的 web.xml 中的配置形式为:

<filter>
        <filter-name>demoFilter</filter-name>
        <filter-class>com.mindflow.servlet3.demo.filter.DemoFilter</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>demoFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

4. @WebListener

注解WebListener用来注解一个监听者,以便获取web应用上下文中各种操作的事件。用@WebListener注解的类必须实现下列接口的一个:

  • javax.servlet.ServletContextListener
  • javax.servlet.ServletContextAttributeListener
  • javax.servlet.ServletRequestListener
  • javax.servlet.ServletRequestAttributeListener
  • javax.servlet.http.HttpSessionListener
  • javax.servlet.http.HttpSessionAttributeListener

示例:

@WebListener
public class SimpleListener implements ServletContextListener {
   public void contextInitialized(ServletContextEvent sce) {
     ServletContext sc = sce.getServletContext();
     sc.addServlet("fooServlet",  "Sample servlet", "com.mindflow.FooServlet", null, -1);
     sc.addServletMapping("fooServlet", new String[] { "/urlpattern/*" });
 }

如此,则不需要在 web.xml 中配置 标签了。它等价的 web.xml 中的配置形式如下:

<listener> 
    <listener-class>com.mindflow.SimpleListener</listener-class> 
</listener>

5. @MultipartConfig

当应用在一个Servlet上,这个注解表明它期望的请求是mime/multipart类型。与servlet对应的HttpServletRequest对象必须要能够通过方法getParts和getPart来遍历不同的mime附件。javax.serlvet.annotation.MultipartConfig的location属性和的元素会被解析为绝对路径,并且默认值是javax.servlet.context.tempdir。如果指定了一个相对地址,它会相对于tempdir这个位置。绝对路径和相对路径的测试必须通过java.io.File.isAbsolute 来完成。

三、可插拔性

为了给开发者提供更好的可插拔性和更少的配置,servlet 3.0规范引入了web模块化部署描述符片段(web fragment),一个web片段可以是一个web.xml的一部分或者全部,它可以被包含在一个库中或者框架jar文件里面META-INF目录中。WEB-INF/lib目录里面没有web-fragment.xml的普通旧jar也被认为是一个片段。在它里面的所有注解将根据下面第三小节定义的规则被处理。容器将选择和使用像如下定义的规则来做为配置。

一个web片段以这样一种方式来作为web应用的一个逻辑部分--正在web应用里面被使用的框架能够定义所有构件,而不需要让开发者来编辑或者添加信息到web.xml文件中。它几乎能够包含所有web.xml文件中使用的元素。然而描述符的顶级元素必须是web-fragment, 并且对应的描述符文件必须被命名为web-fragment.xml。相关元素的顺序在web-fragment.xml和web.xml之间也不一样。

源码

servlet3x-feature-demo

⚠️ **GitHub.com Fallback** ⚠️