Servlet3.0的新特性
注意:Servlet3.0的项目一定要使用Tomcat7.0才能看到效果!!
1、新增标注支持
在Servlet3.0的部署描述文件web.xml的顶层标签<web-app>中有一个metadata-complete属性,如果把该属性的值设置为true,则容器在部署时只依赖于web.xml部署文件中的配置,会忽略所以的标注(同时也会跳过web-fragment.xml的扫描,即禁用可插性支持);如果把该属性的值设置为false或者不配置该属性,则表示启用标注支持和可插性支持。
1)WebServlet标注
@WebServlet用于将一个类声明为Servlet,该标注将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为Servlet。
2)WebInitParam标注
@WebInitParam标注通常不单独使用,而是配合@WebServlet或者@WebFilter使用。它的作业是为Servlet或者过滤器指定初始化参数,这等价于web.xml中<servlet>和<filter>的<init-param>子标签
属性名 | 类型 | 是否可选 | 描述 |
name | String | 否 | 指定参数的名字,等价于<param-name> |
value | String | 否 | 指定参数的值,等价于<param-value> |
description | String | 是 | 指定参数的描述,等价于<description> |
创建一个Servlet3Annotation类:
package com.yyq.servlet3.annotation; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by gao on 16-4-14. */ @WebServlet(name = "servlet3annotation", urlPatterns = {"/servlet3"}, description = "servletinfo", displayName = "abc", asyncSupported = true, loadOnStartup = -1, initParams = {@WebInitParam(name = "username", value = "YangYuqin")}) public class Servlet3Annotation extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取ServletConfig的实例 ServletConfig config = this.getServletConfig(); //获取指定参数名称的值 String name = config.getInitParameter("username"); resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head><title>Servlet3应用实例</title></head>"); out.println("<body>"); out.print("获取InitParamServlet的初始化参数\"username\"的字符串值:" + name); out.println("</body>"); out.println("</html>"); } @Override public void destroy() { //空 } @Override public void init() throws ServletException { //空 } }
启动Tomcat,输入:http://localhost:8080/servlet3
3)WebFilter标注
@WebFilter用于将一个类声明为过滤器,该标注将会在部署时被容器处理。以下属性均为可选属性,但是value、urlPatterns、servletNames三者必需至少包含一个,且value和urlPattern不能共存,如果同时指定,通常忽略value的取值。
属性名 | 类型 | 描述 |
filterName | String | 指定过滤器的name属性,等价于<filter-name> |
value | String[] | 该属性等价于urlPatterns属性,两个属性不能同时使用 |
urlPatterns | String[] | 指定一组Servlet的URL匹配模式,等价于<url-pattern>标签 |
servletNames | String[] | @WebServlet中的name属性的取值,或者是web.xml中<servlet-name>的取值 |
initParams | WebInitParam[] | 指定一组Servlet初始化参数,等价于<init-param>标签 |
dispatcherTypes | DispatcherType | 指定过滤器的转发模式。具体取值包括:ASYNC、ERROR、FORWARD、INCLUDE、REQUEST |
asyncSupported | boolean | 声明过滤器是否支持异步操作模式,等价于<async-supported>标签 |
description | String | 该Servlet的描述信息,等价于<description>标签 |
displayName | String | 该Servlet的显示名,通常配合工具使用,等价于<display-name>标签 |
package com.yyq.servlet3.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import java.io.IOException; /** * Created by gao on 16-4-14. */ @WebFilter(servletNames = {"servlet3filterannotation"}, filterName = "characterFilter", initParams = {@WebInitParam(name = "encoding", value = "UTF-8")}) public class Servlet3FilterAnnotation implements Filter { private FilterConfig filterConfig = null; @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //获取此Filter的初始参数的值 String encoding = filterConfig.getInitParameter("encoding"); System.out.println(encoding); //设置请求数据的编码方式 servletRequest.setCharacterEncoding(encoding); //把请求和响应对象传给过滤链中的下一个要调用的过滤器或Servlet filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { //空 } }
4)WebListener标注
该标注用于将类声明为监听器。
属性名 | 类型 | 是否可选 | 描述 |
value | String | 是 | 该监听器的描述信息 |
监听类:
package com.yyq.servlet3.listener; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import java.util.LinkedList; import java.util.List; /** * Created by gao on 16-4-14. */ @WebListener("This is the Listener") public class Servlet3Listener implements ServletContextListener, HttpSessionAttributeListener, HttpSessionListener { private ServletContext application = null; //往会话中添加属性时回调的方法 public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) { //取得用户名列表 List<String> online = (List<String>) this.application.getAttribute("online"); if ("username".equals(httpSessionBindingEvent.getName())) { //将当前用户名添加到列表中 online.add((String) httpSessionBindingEvent.getValue()); } //将添加后的列表重新设置到application属性中 this.application.setAttribute("online", online); } //以下方法用空实现 @Override public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) { } @Override public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) { } @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } //应用上下文初始化会回调的方法 @Override public void contextInitialized(ServletContextEvent servletContextEvent) { //初始化一个application对象 this.application = servletContextEvent.getServletContext(); //设置一个列表属性,用于保存在线用户名 this.application.setAttribute("online",new LinkedList<String>()); } //会话销毁时会回调的方法 @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { //取得用户名列表 List<String> online = (List<String>) this.application.getAttribute("online"); //取得当前用户名 String username = (String) httpSessionEvent.getSession().getAttribute("username"); //将此用户名从列表中删除 online.remove(username); //将删除后的列表重新设置到application属性中 this.application.setAttribute("online", online); } }
登录Servlet类:
package com.yyq.servlet3.listener; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.List; /** * Created by gao on 16-4-14. */ @WebServlet(name = "servlet3login",urlPatterns = {"/login"}) public class Servlet3Login extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置响应内容类型 req.setCharacterEncoding("utf-8"); //获取请求参数中的用户名 String username = req.getParameter("username"); //往Session中添加属性 //会触发HttpSessionAttributeListener中的attributeAdded方法 if (username != null && !username.equals("")) { req.getSession().setAttribute("username", username); } //从应用上下文中获取在线用户名列表 List<String> online = (List<String>) getServletContext().getAttribute("online"); resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head><title>用户列表</title></head>"); out.println("<body>"); out.println("当前用户是:" + username); out.println("<hr/><h3>在线用户列表</h3>>"); int size = ((online == null) ? 0 : online.size()); for (int i = 0; i < size; i++) { if (i > 0) { out.println("<br />"); } out.println(i + 1 + "." + online.get(i)); } //注意:要对连接URL进行自动重写处理 out.println("<hr/><a href=\"" + resp.encodeURL("logout") + "\">注销</a>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override public void destroy() { //空 } @Override public void init() throws ServletException { //空 } }
注销Servlet类:
package com.yyq.servlet3.listener; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.List; /** * Created by gao on 16-4-14. */ @WebServlet(name = "servlet3logout", urlPatterns = {"/logout"}) public class Servlet3Logout extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //设置响应内容类型 req.setCharacterEncoding("utf-8"); //销毁会话,会触发SessionLinstener中的sessionDestroyed方法 req.getSession().invalidate(); //从应用上下文中获取在线用户名列表 List<String> online = (List<String>)getServletContext().getAttribute("online"); resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<head><title>用户列表</title></head>"); out.println("<body>"); out.print("<h3>在线用户列表</h3>"); int size = ((online == null) ? 0 : online.size()); for (int i = 0; i < size; i++) { if (i > 0) { out.println("<br />"); } out.println(i + 1 + "." + online.get(i)); } out.println("<hr><a href=\"index.jsp\">主页</hr>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } @Override public void destroy() { //空 } @Override public void init() throws ServletException { //空 } }
登录页面:index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="登录">
<br />
<br />
</form>
</body>
</html>
5)MultipartConfig标注
该标注主要是为了辅助Servlet3.0中HttpServletRequest提供的对上传文件的支持。该标注标注在Servlet上,表示该Servlet希望处理的请求的MIME类型是multipart/form-data。
属性名 | 类型 | 是否可选 | 描述 |
fileSizeThreshold | int | 是 | 当数据量大于该值时,内容将被写入文件 |
location | String | 是 | 存放生成的文件地址 |
maxFileSize | long | 是 | 允许上传的文件最大值。默认值为-1,表示没有限制 |
maxRequestSize | long | 是 | 针对该multipart/form-data请求的最大数量,默认值为-1,表示没有限制 |
文件上传页面upload.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" import="java.util.*" pageEncoding="UTF-8" %> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Servlet3.0 上传文件</title> </head> <body> <form action="uploadfile" method="post" enctype="multipart/form-data"> <table> <tr> <td> 选择文件: </td> <td> <input type="file" name="file"/> </td> </tr> <tr> <td>描述: </td> <td> <input type="text" name="description"/> </td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交"/> <input type="reset" value="重置"/> </td> </tr> </table> </form> </body> </html>
处理上传文件的Servlet:
package com.yyq.servlet3.multipartconfig; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.UUID; /** * Created by gao on 16-4-14. */ @WebServlet(name = "upFile", urlPatterns = {"/uploadfile"}) @MultipartConfig(maxFileSize = 500000, maxRequestSize = -1) public class FileUploadServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //resp.setContentType("text/html;charset=utf-8"); //获取请求参数值 Part part = req.getPart("file"); //存储路径 String storePath = req.getServletContext().getRealPath("/temp"); //Servlet3没有提供直接获取文件名,后缀名的方法,需要从请求头中解析出来 //获取请求头 String header = part.getHeader("content-disposition"); //获取文件后缀名 //String suffix = parseFileName(header); String name = parseFileName(header); //重新命名 //String name = UUID.randomUUID() + suffix; //把文件写到指定路径 part.write(storePath + File.separator + name); // PrintWriter out = resp.getWriter(); // out.println("上传成功"); // out.flush(); // out.close(); //获得文件描述信息 String description = req.getParameter("description"); req.setAttribute("f", name); req.setAttribute("des", description); req.getRequestDispatcher("info.jsp").forward(req, resp); } /* *根据请求头解析出上传文件的后缀名称 */ /** * 根据请求头解析出文件名 * 请求头的格式:火狐和google浏览器下:form-data; name="file"; filename="snmp4j--api.zip" * IE浏览器下:form-data; name="file"; filename="E:\snmp4j--api.zip" * * @param header 请求头 * @return 文件名 */ public String parseFileName(String header) { //return header.substring(header.lastIndexOf("."), header.length() - 1); /** * String[] tempArr1 = header.split(";");代码执行完之后,在不同的浏览器下,tempArr1数组里面的内容稍有区别 * 火狐或者google浏览器下:tempArr1={form-data,name="file",filename="snmp4j--api.zip"} * IE浏览器下:tempArr1={form-data,name="file",filename="E:\snmp4j--api.zip"} */ String[] tempArr1 = header.split(";"); /** *火狐或者google浏览器下:tempArr2={filename,"snmp4j--api.zip"} *IE浏览器下:tempArr2={filename,"E:\snmp4j--api.zip"} */ String[] tempArr2 = tempArr1[2].split("="); //获取文件名,兼容各种浏览器的写法 String fileName = tempArr2[1].substring(tempArr2[1].lastIndexOf("\\") + 1).replaceAll("\"", ""); return fileName; } @Override public void destroy() { //空 } @Override public void init() throws ServletException { //空 } }
显示上传文件和描述信息的页面info.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" import="java.util.*" pageEncoding="UTF-8" %> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path +"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Servlet3.0 上传文件</title> </head> <body> <h3><%=request.getAttribute("des")%></h3> <img alt = "servlet3" src="<%=basePath %>temp/<%=request.getAttribute("f")%>"> </body> </html>
2、异步处理支持
Servlet3.0支持异步处理支持,Servlet接收到请求之后,可能首先需要对请求携带的数据进行一些预处理;接着,Servlet线程将请求转交给一个异步线程来执行业务处理,线程本身返回至容器,此时Servlet还没有生成响应数据,异步线程处理完业务以后,可以直接生成响应数据(异步线程拥有ServletRequest和ServletResponse对象的引用),或者将请求继续转发给其他Servlet。
1)对于使用传统的部署描述文件web.xml配置Servlet和过滤器的情况,Servlet3.0为<servlet>和<filter>标签增加了<async-supported>子标签,该标签的默认取值为false,要启用异步处理支持,则将其设为true即可。
<servlet> <servlet-name>DemoServlet</servlet-name> <servlet-class>com.yyq.servlet3.asyncsupported.AsyncDemoServlet</servlet-class> <async-supported>true</async-supported> </servlet>
2)对于使用Servlet3.0提供的@WebServlet和@WebFilter进行Servlet或过滤器配置的情况,这两个标注都提供了asyncSupported属性,默认该属性的取值为false,要启动异步处理支持,只需将该属性设置为true即可。
package com.yyq.servlet3.asyncsupported; import javassist.bytecode.analysis.Executor; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; /** * Created by gao on 16-4-15. */ @WebServlet(urlPatterns = {"/asyncdemo"}, asyncSupported = true) public class AsyncDemoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("进入Servlet的时间:" + new Date() + "."); out.flush(); //在子线程中执行业务调用,并由其负责输出响应,主线程退出 AsyncContext ctx = req.startAsync(); new Thread(new Executor(ctx)).start(); out.println("结束Servlet的时间:" + new Date() + "."); out.flush(); } public class Executor implements Runnable { private AsyncContext ctx = null; public Executor(AsyncContext ctx) { this.ctx = ctx; } public void run() { try { //等待10秒钟,以模拟业务方法的执行 Thread.sleep(10000); PrintWriter out = ctx.getResponse().getWriter(); out.println("业务处理完毕的时间:" + new Date() + "."); out.flush(); ctx.complete(); }catch (Exception e){ e.printStackTrace(); } } } @Override public void destroy() { //空 } @Override public void init() throws ServletException { //空 } }
启动Tomcat,输入:http://localhost:8080/asyncdemo
3、可插性支持
Servlet3.0新增的可插性(Pluggability)支持则将Servlet配置的灵活性提升到了新的高度。使用该特性,现在我们可以在不修改已有Web应用的前提下,只需将按照一定格式打包成的JAR包放到WEB-INF/lib目录下,即可实现新的功能的扩充,不需要额外的配置。Servlet3.0引入了称为“Web模块部署描述文件片段”的web-fragment.xml来实现可插性的。web-fragment.xml部署描述文件可以定义一切可以在web.xml中定义的内容。
1)新建的Servlet类:
package com.yyq.servlet3.pluggability; import javax.naming.NamingException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by gao on 16-4-15. */ public class FragmentDemoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=utf-8"); PrintWriter out = resp.getWriter(); out.println("这是我Servlet3.0的第一个可插性示例"); out.flush(); } @Override public void destroy() { //空 } @Override public void init() throws ServletException { //空 } }
2)在web目录下新建一个目录META-INF,在该目录下新建一个web-fragment.xml模块部署描述符文件片段:
<?xml version="1.0" encoding="UTF-8"?> <web-fragment xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd" metadata-complete="true"> <!--给当前配置文件定义一个名称--> <name>FragmentA</name> <servlet> <servlet-name>fragmentDemo</servlet-name> <servlet-class>com.yyq.servlet3.pluggability.FragmentDemoServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>fragmentDemo</servlet-name> <url-pattern>/fragment</url-pattern> </servlet-mapping> </web-fragment>
3)将FragmentDemoServlet和META-INF目录一起打包成JAR包,假如JAR包叫fragment.jar。
4)将fragment.jar放到其他Web项目中的WEB\lib目录中,然后访问http://localhost:8080/fragment即可。