Servlet3.0 的注解

Servlet 允许开发人员采用注解的方式来配置 Servlet、Filter、Listener。

Servlet3.0 规范在 javax.servlet.annotation 包下提供了如下注解。

@WebServlet:用于修饰一个 Servlet 类,用于部署 Servlet 类。

@WebInitParam:用于与 @WebServlet 或 @WebFilter 一起使用,为 Servlet、Filter 配置参数。

@WebListener:用于修饰 Listener 类,用于部署 Listener 类

@WebFilter:用于修饰 Filter 类,用于部署 Filter 类

@MultipartConfig:用于修饰 Servlet,指定该 Servlet 将会负责处理 multipart/form-data 类型的请求(主要用于文件上传)

@ServletSecurity:这是一个与 JAAS 有关的注解,修饰 Servlet 指定该 Servlet 的安全与授权控制

@HttpConstraint:用于与 @ServletSecurity 一起使用,用于指定该 Servlet 的安全与授权控制

@HttpMethodConstraint:用于与 @ServletSecurity 一起使用,用于指定该 Servlet 的安全与授权控制

Servlet3.0 提供的异步处理

在以前的 Servlet 规范中,如果 Servlet 作为控制器调用了一个耗时的业务方法,那么 Servlet 必须等到业务完全返回之后才会生成响应,这将使得 Servlet 对业务方法的调用变成一种阻塞式的调用,因此效率比较低。

Servlet3.0 规范引入了异步处理来解决这个问题,异步处理允许 Servlet 重新发起一条新线程去调用耗时的业务方法,这样就可避免等待。

Servlet3.0 的异步处理是通过 AsyncContext 类来处理的,Servlet 可通过 ServletRequest 的如下两个方法开启异步调用、创建 AsyncContext 对象。

AsyncContext start()

AsyncContext startAsync(ServletRequest, ServletResponse)

重复调用上面的方法将得到同一个 AsyncContext 对象。AsyncContext 对象代表异步处理的上下文,它提供了一些工具方法,可完成设置异步调用的超时时长,dispatch 用于请求、启动后台线程、获取 request、response 对象等功能。

AsyncServlet.java

package com.baiguiren;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date; import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.annotation.*; @WebServlet(urlPatterns="/async", asyncSupported=true)
public class AsyncServlet extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<title>异步调用实例</title>");
out.println("进入 Servlet 的时间:" + new Date() + ".<br/>"); // 创建 AsyncContext,开始异步调用
AsyncContext actx = request.startAsync();
// 设置异步调用的超时时长
actx.setTimeout(60 * 1000);
// 启动异步调用的线程
actx.start(new GetBookTask(actx)); out.println("结束 Servlet 的时间:" + new Date() + ".<br/>");
out.flush();
}
}

  

GetBookTask.java

package com.baiguiren;

import java.util.ArrayList;
import java.util.List; import javax.servlet.*; public class GetBookTask implements Runnable
{
private AsyncContext actx = null; public GetBookTask(AsyncContext actx)
{
this.actx = actx;
} public void run()
{
try {
// 等待 5 秒,以模拟业务的方法执行
Thread.sleep(5 * 1000);
ServletRequest request = actx.getRequest();
List<String> books = new ArrayList<String>();
books.add("book 1");
books.add("book 2");
books.add("book 3");
request.setAttribute("books", books);
actx.dispatch("/async.jsp");
} catch (Exception e) {
e.printStackTrace();
}
}
}

  

上面的 task 方法暂停 5 秒来模拟调用耗时的业务方法,最后调用 AsyncContext 的 dispatch 方法把请求 dispatch 到指定的 JSP 页面。

async.jsp

<%@ page contentType="text/html; charset=UTF-8" session="false" %>
<%@ taglib prefix="c" tagdir="/WEB-INF/tags" %>
<%@ page import="java.util.*" %> <c:forEach items="${books}" /> <%
out.println("业务调用结束的时间:" + new java.util.Date());
if (request.isAsyncStarted()) {
// 完成异步调用
request.getAsyncContext().complete();
}
%>

  

forEach.tag

<%@ tag pageEncoding="UTF-8" import="java.util.*" %>

<%@ attribute name="items" required="true" type="java.util.List" %>

<ul>
<%
for (Object s : items) {
%>
<li><%=s%></li>
<%
}
%>
</ul>

  

对于希望启用异步调用的 Servlet 而言,开发者必须显示指定开启异步调用,为 Servlet 开启异步调用有两种方式。

为 @WebServlet 指定 asyncSupported=true

在 web.xml 文件的 <servlet ../> 元素中增加 <async-supported .../> 子元素,该子元素的值为 true

对于支持异步调用的 Servlet 来说,当 Servlet 以异步方式启用新线程之后,该 Servlet 的执行并不会阻塞,该 Servlet 将可以向客户端浏览器生成响应 --- 当新线程执行完成后,新线程生成的响应再次被送往客户端浏览器。

当 Servlet 启用异步调用的线程之后,该线程的执行过程对开发者是透明的。但在有些情况下,开发者需要了解该异步线程的执行细节,并针对特定的执行结果进行针对性处理,这可借助于 Servlet3.0 提供的异步监听器来实现。

异步监听器需要实现 AsyncListener 接口,实现该接口的监听器类需要实现如下 4 个方法。

onStartAsync(AsyncEvent event):当异步调用开始时触发该方法

onComplete(AsyncEvent event):当异步调用完成时触发该方法

onError(AsyncEvent event):当异步调用出错时触发该方法

onTimeout(AsyncEvent event):当异步调用超时时触发该方法

MyAsyncListener.java

package com.baiguiren;

import java.io.IOException;
import java.util.Date; import javax.servlet.*;
import javax.servlet.annotation.*; @WebListener
public class MyAsyncListener implements AsyncListener
{
public void onComplete(AsyncEvent event) throws IOException
{
System.out.println("---异步调用完成---" + new Date());
} public void onError(AsyncEvent event) throws IOException
{
} public void onStartAsync(AsyncEvent event) throws IOException
{
System.out.println("---异步调用开始---" + new Date());
} public void onTimeout(AsyncEvent event) throws IOException
{
}
}

  

虽然上面的 Listener 监听器类可以监听异步调用开始、异步调用完成两个时间,但从实际运行的结果来看,它并不能监听到异步调用开始事件,这可能是因为注册该监听器时异步调用已经开始了的缘故。

改进的 Servlet API

Servlet3.0 还有一个改变是改进了部分 API,这种改进很好地简化了 java web 开发。其中两个较大的改进是:

HttpServletRequest 增加了对文件上传的支持

ServletContext 允许通过编程的方式动态注册 Servlet、Filter。

HttpServletRequest 提供了如下两个方法来处理文件上传:

Part getPart(String name):根据名称来获取文件上传域

Collection<Part> getParts():获取所有的文件上传域

上面两个方法的返回值都涉及一个 API:Part,每个 Part 对象对应于一个文件上传域,该对象提供了大量方法来访问上传文件的文件类型、大小、输入流等,并提供了一个 write(String file) 方法将上传文件写入磁盘。

upload.jsp

<%@ page contentType="text/html; charset=UTF-8" %>

<html>
<head>
<title>文件上传</title>
</head>
<body>
<form method="post" action="upload" enctype="multipart/form-data">
文件名:<input type="text" id="name" name="name" /><br/>
选择文件:<input type="file" id="file" name="file"/><br/>
<input type="submit" value="上传" /><br/>
</form>
</body>
</html>

  

UploadServlet.java

package com.baiguiren;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection; import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*; @WebServlet(name="upload", urlPatterns={"/upload"})
@MultipartConfig
public class UploadServlet extends HttpServlet
{
public void service(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
request.setCharacterEncoding("UTF-8");
// 获取普通请求参数
String name = request.getParameter("name");
out.println("普通的 name 参数为:" + name + "<br/>"); // 获取文件上传域
Part part = request.getPart("file");
// 获取上传文件的文件类型
out.println("上传文件的类型为:" + part.getContentType() + "<br/>");
// 获取上传文件的大小
out.println("上传文件的大小为:" + part.getSize() + "<br/>");
// 获取该文件上传域的 Header Name
Collection<String> headerNames = part.getHeaderNames();
// 遍历文件上传域的 Header Name、Value
for (String headerName : headerNames)
{
out.println(headerName + "---->" + part.getHeader(headerName) + "<br/>");
}
// 获取包含文件原始名的字符串
String fileNameInfo = part.getHeader("content-disposition");
// 提取上传文件的原始文件名
String fileName = fileNameInfo.substring(fileNameInfo.indexOf("filename=\"") + 10, fileNameInfo.length() - 1);
// 将上传的文件写入服务器
part.write(getServletContext().getRealPath("/uploadFiles") + "/" + fileName);
}
}

  

上面 Servlet 使用了 @MultipartConfig 修饰,处理文件上传的 Servlet 应该使用该注解修饰。也可以在 web.xml 中的 <servlet.../> 子元素中添加 <multipart-config.../> 子元素来达到同样效果。

ServletContext 则提供了如下方法来动态地注册 Servlet、Filter,并允许动态设置 Web 应用的初始化参数。

多个重载的 addServlet() 方法:动态地注册 Servlet

多个重载的 addFilter() 方法:动态地注册 Filter

多个重载的 addListener() 方法:动态地注册 Listener

setInitParameter(String name, String value) 方法:为 web 应用设置初始化参数。

Servlet3.1 新增的非阻塞式 IO

Servlet 底层的 IO 是通过如下两个 IO 流来支持的。

ServletInputStream:Servlet 用于读取数据的输入流。

ServletOutputStream:Servlet 用于输出数据的输出流。

以 Servlet 读取数据为例,传统的读取方式采用阻塞式 IO -- 当 Servlet 读取浏览器提交的数据时,如果数据暂时不可用,或数据没有读取完成,Servlet 当前所在线程将会被阻塞,无法继续向下执行。

从 Servlet 3.1 开始,ServletInputStream 新增了一个 setReadListener(ReadListener readListener) 方法,该方法允许以非阻塞式 IO 读取数据,实现 ReadListener监听器需要实现如下三个方法。

onAllDataRead():当所有数据读取完成时触发该方法

onDataAvailable():当有数据可用时触发该方法

onError(Throwable t):当读取数据出现错误时触发该方法

类似地,ServletOutputStream 也提供了 setWriterListener(WriteListener writeListener) 方法,通过这种方式,可以让 ServletOutputStream 以非阻塞 IO 进行输出。

在 Servlet 中使用非阻塞 IO 非常简单,按如下步骤进行即可。

1、调用 ServletRequest 的 startAsync() 方法开启异步模式。

2、通过 ServletRequest 获取 ServletInputStream,并为 ServletInputStream 设置监听器(ReadListener 实现类)。

3、实现 ReadListener 接口实现监听器,在该监听器的方法中以非阻塞方式获取数据。

AsyncServlet1.java

package com.baiguiren;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date; import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.annotation.*; @WebServlet(urlPatterns="/async1", asyncSupported=true, loadOnStartup=1)
public class AsyncServlet1 extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<title>非阻塞 IO 实例</title>");
out.println("进入 Servlet 的时间:" + new Date() + ".<br/>"); // 创建 AsyncContext,开始异步调用
AsyncContext actx = request.startAsync();
// 设置异步调用的超时时长
actx.setTimeout(60 * 1000); // 为输入流注册监听器
ServletInputStream input = request.getInputStream();
input.setReadListener(new MyReadListener(input, actx)); out.println("结束 Servlet 的时间:" + new Date() + ".<br/>");
out.flush();
}
}

  

MyReadListener.java

package com.baiguiren;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.annotation.*; public class MyReadListener implements ReadListener
{
private ServletInputStream input; private AsyncContext context; public MyReadListener(ServletInputStream input, AsyncContext context)
{
this.input = input;
this.context = context;
} @Override
public void onDataAvailable()
{
System.out.println("数据可用!"); try {
// 暂停 5 秒,模拟耗时操作
Thread.sleep(5000);
StringBuilder sb = new StringBuilder();
int len = -1;
byte[] buff = new byte[1024]; // 采用原始 IO 方式读取浏览器向 Servlet 提交的数据
while (input.isReady() && (len = input.read(buff)) > 0)
{
String data = new String(buff, 0, len);
sb.append(data);
} System.out.println(sb);
// 将数据设置为 request 范围的属性
context.getRequest().setAttribute("info", sb.toString()); // 转发到视图页面
context.dispatch("/async1.jsp");
} catch (Exception ex) {
ex.printStackTrace();
}
} @Override
public void onAllDataRead()
{
System.out.println("数据读取完成");
} @Override
public void onError(Throwable t)
{
t.printStackTrace();
}
}

  

async1.jsp

<%@ page contentType="text/html; charset=UTF-8" session="false" %>

<%
out.println("业务调用结束的时间:" + new Date()); out.println(request.getAttribute("info"));
if (request.isAsyncStarted()) {
// 完成异步调用
request.getAsyncContext().complete();
}
%>

  

上面程序中的 MyReadListener 的 onDataAvailable() 方法先暂停 5 秒,用于模拟耗时操作,接下来程序使用普通 IO 流读取浏览器提交的数据。

如果程序直接让 Servlet 读取浏览器提交的数据,那么该 Servlet 就需要阻塞 5 秒,不能继续向下执行;改为非阻塞 IO 进行读取,虽然读取数据的 IO 操作需要 5 秒,但它不会阻塞 Servlet 执行,因此可以提升 Servlet 的性能。

Servlet3.0 新特性的更多相关文章

  1. Servlet3.0新特性

    1 Servlet3.0新特性概述 使用要求:MyEclipse10.0或以上版本,发布到Tomcat7.0或以上版本,创建JavaEE6.0应用! Servlete3.0的主要新特性如下三部分: 使 ...

  2. 【servlet3.0新特性】Annotation注解配置

    servlet3.0新特性Servlet3.0引入的若干重要新特性,包括异步处理.新增的注解支持.可插性支持等等,为读者顺利向新版本过渡扫清障碍.Servlet3.0新特性概述Servlet3.0作为 ...

  3. 【Servlet3.0新特性】第03节_文件上传

    这是一个Web Project 首先是web.xml <?xml version="1.0" encoding="UTF-8"?> <web- ...

  4. 使用Servlet3.0新特性asyncSupported=true时抛异常java.lang.IllegalStateException: Not supported

    最近在运用Servlet3.0新特性:异步处理功能的时候出现以下了2个问题: 运行时会抛出以下两种异常: 一月 19, 2014 3:07:07 下午 org.apache.catalina.core ...

  5. Java基础加强-(注解,动态代理,类加载器,servlet3.0新特性)

    1.   Annotation注解 1.1.  Annotation概述 Annotation是JDK 5.0以后提供对元数据的支持,可以在编译.加载和运行时被读取,并执行相应的处理.所谓Annota ...

  6. Servlet3.0新特性(从注解配置到websocket编程)

    Servlet3.0的出现是servlet史上最大的变革,其中的许多新特性大大的简化了web应用的开发,为广大劳苦的程序员减轻了压力,提高了web开发的效率.主要新特性有以下几个: 引入注解配置 支持 ...

  7. Java自学手记——servlet3.0新特性

    servlet3.0出来已经很久了,但市场上尚未普遍应用,servlet3.0有三个比较重要的新特性:使用注解来代替配置文件,异步处理以及上传组件支持. 支持servlet3.0的要求:MyEclip ...

  8. Servlet3.0新特性WebFilter(Annotation Filter)详解

    摘要: Servlet3.0作为J2EE 6规范一部分,并随J2EE6一起发布,WeFilter是过滤器注解,是Servlet3.0的新特性,不需要在web.xml进行配置,简化了配置. Name T ...

  9. Servlet3.0新特性使用详解

    可插拔的Web框架 几乎所有基于Java的web框架都建立在servlet之上.现今大多数web框架要么通过servlet.要么通过Web.xml插入.利用标注(Annotation)来定义servl ...

  10. servlet3.0 新特性——异步处理

    Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下: 首先,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理: 接着,调用业务接口的某些方法,以完成业 ...

随机推荐

  1. 2017年4月8日Office 365 使用CSV文件导入邮件组

    国内版 第一步: Import-Module msonline Connect-MsolService 输入用户名密码 第二步: Get-MsolUser" 第三步: Set-Executi ...

  2. Linux(Contos7.5)环境搭建之Linux远程登录(一)

    1.下载<putty-0.70cn.zip>工具包 2.解压到适合的文件夹下

  3. 阅读笔记《我是一只IT小小鸟》

    我是一只IT小小鸟 我们在尝试新的事物的时候,总是会遇到各种各样的困难,不同的人会在碰壁不同的次数之后退出.用程序员喜欢的话来说就是,我们都在for循环,区别在于你是什么情况下break;的.有的人退 ...

  4. Redis的sentinel机制(sentinel节点IP为:192.168.23.10) “哨兵”

    万一主节点打击,主从模型将会停止工作,为了解决这个问题,Redis提供了一个sentinel(哨兵),以此来实现主从切换的功能,一旦主节点宕机了,sentinel将会在从节点中挑一个作为主节点.与zo ...

  5. 3dContactPointAnnotationTool开发日志(三二)

      今天就是看怎么把论文的python源码预测出来的smpl模型的姿势和形状参数弄到unity版本的smpl里,但是python版本的和unity版本的不一样.   先看看他的fit_3d.py:   ...

  6. eclipse生成jar包 注意事项!

    原文转自:http://www.cnblogs.com/zhangfei/archive/2013/01/22/2871075.html 第一:普通类导出jar包,我说的普通类就是指此类包含main方 ...

  7. [翻译]API Guides - Layouts

    官方文档地址:http://developer.android.com/guide/topics/ui/declaring-layout.html PS:API Guides里面的内容不免都简单些,翻 ...

  8. TP中CURD操作

    CURD操作 CURD操作也就是模型操作数据表的基本操作.C(Create).U(Update).R(Read).D(Delete)操作就是增删改查操作. 6.1.增加操作 回想一下在mysql中增加 ...

  9. 分布式架构核心RPC原理

    在应用的迭代演进过程中,随着系统访问量提高,业务复杂度提高,代码复杂度提高,应用逐渐从单体式架构向面向服务的分布式架构转变.RPC(Remote Procedure Call Protocol远程过程 ...

  10. Delphi 组件渐进开发浅谈(一)——由简入繁

    最近业余时间在写游戏修改器玩,对于Delphi自带的组件总觉得差强人意,需要书写大量冗余代码,如果大量使用第三方组件,在以后的移植和与他人交互时也不是很方便,因此便产生了自己封装组件的想法. 实际上这 ...