Servlet3.x 新特性小结 - TFdream/blog GitHub Wiki
Servlet3.0支持以下几个主要特性:
- 异步请求
- 注解配置
- 可插拔性(pluggability)支持
- ServletContext运行时动态部署
- 支持上传文件
- 非阻塞IO
从Servlet3.0开始,具备异步请求响应编程模型。即servlet实例所在线程可以不用等待response写返回,而直接释放serlvet线程,因为有些response有比较大的开销,可能会消耗过大的时间,造成线程阻塞,比如等待JDBC连接,等待网络请求等。
- 使用线程池
/**
* @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);
}
}
- 不采用线程池
/**
* @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及以上版本中,支持通过注解配置。
@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>
该注解通常不单独使用,而是配合 @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");
}
}
@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>
注解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>
当应用在一个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之间也不一样。