Servlet3.0特性
Servlet3.0规范是JavaEE6.0规范中的子规范。其要求运行环境最低是JDK6.0,Tomcat7.0。而之前学习的是Servlet2.5版本的规范,其是JavaEE5.0规范的子规范。其要求运行环境最低是JDK5.0,Tomcat5.0。
在Eclipse中使用Servlet3.0规范,则需要在创建动态Web工程时就要指定。此时创建的Web工程中默认是没有web.xml文件的。
注解
Servlet3.0规范允许在定义Servlet,Filter,Listener三大组件时使用注解,而不用在web.xml进行注册了。Servlet3.0规范允许Web项目没有web.xml配置文件。
Servlet注解
Servlet3.0规范中使用@WebServlet()注解来注册当前Servlet类。该注解具有多个属性,常用属性的类型与意义如下表所示。
value&urlPatterns
当value或urlPatterns有多个属性值时
作用与下图一致
当只使用value属性且数组中只有一个值时可以省略大括号和属性名
name
作用与web.xml中<servlet-name>标签相同
initParams
loadOnStartup
不设置默认为-1
运行
Filter注解
Servlet3.0规范中使用@WebFilter()注解来注册当前的Filter类。
大部分属性和上方相似,只看不同的部分
servletNames
过滤访问servletNames数组中对应Servlet的请求
dispatcherTypes
设置过滤的请求类型
Listener注解
当两种注册方式同时存在
Servlet
此时两个url都可以访问该Servlet,相当于有多个url
但是如果两种注册方式url相同时,项目启动就会出错
Filter
url-pattern相同此时发送请求发现,会执行两次Filter
更改其中一个是两者不同,发送其中一个拦截的请求Filter执行了一次
若对于Filter采用了两种方式同时注册则需注意:无论url-pattern的值是否相同,其都是作为两个独立的Filter。
Listener
仅仅相当于一个Listener
当设置如下
则表示对三大组件的注册方式只使用web.xml中的,忽略注解。
若为false,或不设置则为同时起作用。
文件上传
Servlet3.0提供了专门的文件上传API。HttpServletRequest的getPart方法可以完成单个文件上传,而getParts()方法可以完成多个文件上传。注意:这两个方法是从Servlet3.0开始定义的。
@WebServlet("/uploadServlet")
@MultipartConfig // 表明当前Servlet可以处理Multipart请求
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取服务端保存上传文件的目录路径
String path = this.getServletContext().getRealPath("/images");
// 获取请求中的上传文件
Part part = request.getPart("photo");
// 获取指定的头部属性 解析出文件名
String header = part.getHeader("Content-Disposition");
int index = header.lastIndexOf("=");
String fileName = header.substring(index + 2, header.length() - 1);
// 完成文件上传
part.write(path + "/" + fileName);
}
}
异步处理
这里讲的是服务器端的异步处理。AJAX是客户端的异步处理。
为什么要使用Servlet异步?
Servlet是单例多线程的,当一个请求到达服务器后,服务器会马上为该请求创建一个相应的Servlet线程,为该请求服务,那么,一个请求就一定会有一个Servlet线程为之服务吗?实则不然,服务器会为每一个Servlet实例创建一个线程池,而线程池中该Servlet实例的线程对象并不是取之不尽的,而是有上限的。当达到该上限后,再有请求要访问该Servlet,那么就只能等待了。只有当又有了空闲的Servlet线程对象后才能为该请求分配Sevlet线程对象。
对于Servlet来说,其最典型的工作一般分为三步:
一、接受请求提交参数
二、根据提交参数调用Service层代码对参数进行运算
三、接收到Service层的运算结果后,将结果响应给客户端
其中第二步有可能是一个耗时运算。在Service层代码对参数进行运算过程中,Servlet处于阻塞状态,等待的运算结果。如果在Service层代码运算过程中,将Servlet线程释放回Servlet线程池,当Service运算结果出来后,再给出用户响应,这样Servlet线程就不会被长时间占用了。而Servlet3.0的异步处理,即使用 异步Servlet 与 异步子线程 完成的异步处理解决的就是这个问题。
ComputeThread
//定义一个子线程,进行较耗时的运算
public class ComputeThread implements Runnable {
private PrintWriter out;
public ComputeThread(PrintWriter out) {
this.out = out;
}
@Override
public void run() {
out.print("子线程运行<br>");
int sum = 0;
try {
for (int i = 0; i <= 10; i++) {
sum += i;
System.out.println("i = " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
out.print(sum);
out.print("子线程结束<br>");
}
}
SomeServlet
public class SomeServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("主线程开始运行<br>");
// 一个耗时的运算过程(子线程)
ComputeThread computeThread = new ComputeThread(out);
Thread subThread = new Thread(computeThread);
subThread.start();
out.print("主线程运行结束<br>");
}
}
访问SomeServlet发现主线程在子线程开始运算后,直接给出了响应
而此时子线程依旧还在执行中,但是其运行结果在客户端是看不到的,输出流已经关闭了。
在子线程开启后,让主线程不要直接关闭,等待子线程结束
但是此时的访问用户等待时间太长了。
要解决上方问题,就要用到Servlet异步处理。
Servlet基本异步处理的实现
ComputeThread
//定义一个子线程,进行较耗时的运算
public class ComputeThread implements Runnable {
private AsyncContext ac;
public ComputeThread(AsyncContext ac) {
this.ac = ac;
}
@Override
public void run() {
try {
ServletResponse response = ac.getResponse();
PrintWriter out = response.getWriter();
int sum = 0;
out.print("子线程运行<br>");
for (int i = 0; i <= 10; i++) {
sum += i;
System.out.println("i = " + i);
Thread.sleep(1000);
}
out.print(sum + "<br>");
out.print("子线程结束<br>");
// 通知主线程,子线程执行完毕
// ac.complete();
// 将异步上下文对象的request,response转发
ac.dispatch("/show.jsp");
} catch (Exception e) {
e.printStackTrace();
}
}
}
SomeServlet
//asyncSupported = true 表示当前Servlet支持异步处理
@WebServlet(urlPatterns = "/some", asyncSupported = true)
public class SomeServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("主线程开始运行<br>");
// 获取异步上下文对象
AsyncContext ac = request.startAsync();
// 一个耗时的运算过程(子线程)
ComputeThread computeThread = new ComputeThread(ac);
// // 设置异步上下文对象的超时时限3s(时间到将异步对象销毁)默认30s
// ac.setTimeout(3000);
// 开启异步上下文对象
ac.start(computeThread);
out.print("主线程运行结束<br>");
}
}
对于异步上下文对象ac的总结方式:
在异步子线程中使用ac.complete()方法:该方法用于结束异步操作,并将与当前异步对象相关的request与response对象销毁。
在异步子线程中使用ac.dispatch()方法:该方法在结束异步操作的同时,会将参数所指定的页面内容包含到当前异步对象相关的标准输出流中。其执行效果相当于RequestDispatcher对象的include()方法的执行效果。
在异步Servlet主线程中设置ac的超时时限到达时,异步对象及其相关的request与response对象销毁。
只要异步对象的相关对象(request,response)被占用,异步对象就不会被销毁。主线程就不会结束
在异步Servlet中仅仅启动耗时的子线程,在子线程中 不应使用request和response 或 尽快将request和response释放掉。
真正应用的用法
ComputeThread
//定义一个子线程,进行较耗时的运算
public class ComputeThread implements Runnable {
private AsyncContext ac;
public ComputeThread(AsyncContext ac) {
this.ac = ac;
}
@Override
public void run() {
try {
HttpServletRequest request = (HttpServletRequest) ac.getRequest();
HttpSession session = request.getSession();
int sum = 0;
for (int i = 0; i <= 10; i++) {
sum += i;
System.out.println("i = " + i);
Thread.sleep(1000);
}
// 对运算结果进行分析
String message = "注册失败,请重新注册";
if (sum == 55) {
message = "恭喜注册成功";
}
// 将分析结果存放到Session域
session.setAttribute("message", message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
RegisterServlet
//asyncSupported = true 表示当前Servlet支持异步处理
@WebServlet(urlPatterns = "/register", asyncSupported = true)
public class RegisterServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取异步上下文对象
AsyncContext ac = request.startAsync();
// 一个耗时的运算过程(子线程)
ComputeThread computeThread = new ComputeThread(ac);
// 设置异步上下文对象的超时时限3s(时间到将异步对象销毁)默认30s
ac.setTimeout(3000);
// 开启异步上下文对象
ac.start(computeThread);
// 主线程结束后给客户端的信息
response.getWriter().println("Please proceed to email confirmation of your registration information");
}
}
email.jsp
Servlet异步监听器
组件可插性
JavaEE6.0项目支持将打为Jar包的Servlet,Filter,Listener直接插入到正在运行的Web项目中,当然这些Jar包同时包含有相对应的配置文件。
新建web片段工程
创建Servlet
将项目打包
使用
将其放在项目的lib文件夹下,运行项目即可。
三大组件的动态注册
Servlet
MyServletContextListener
@WebListener
public class MyServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
// 获取ServletContext
ServletContext sc = sce.getServletContext();
// 动态注册Servlet
// 两参数一般是由配置文件获取而来
String servletName = "someServlet";
String servletClass = "servlets.SomeServlet";
Dynamic srd = sc.addServlet(servletName, servletClass);
// 为Servlet指定url-pattern
srd.addMapping("/some");
// 添加初始化参数
srd.setInitParameter("username", "gyq");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("password", "nicaia");
hashMap.put("describe", "wobucai");
srd.setInitParameters(hashMap);
}
}
SomeServlet
public class SomeServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("SomeServlet动态注册<br>");
// 遍历输出初始化参数
Enumeration<String> names = this.getInitParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
String value = this.getInitParameter(name);
out.println(name + "=" + value + "<br>");
}
}
}
Filter
Listener