目錄html
1.註解配置java
2.異步調用web
3.文件上傳數組
相對於以前的版本,Servlet3.0中的Servlet有如下改進:app
支持註解配置。異步
支持異步調用。async
直接有對文件上傳的支持。ide
在這篇文章中我將主要講這三方面的應用示例。post
在以往咱們的Servlet都須要在web.xml文件中進行配置(Servlet3.0一樣支持),可是在Servlet3.0中引入了註解,咱們只須要在對應的Servlet類上使用@WebServlet註解進行標記,咱們的應用啓動以後就能夠訪問到該Servlet。對於一個@WebServlet而言,有一個屬性是必需要的,那就是它的訪問路徑。@WebServlet中有兩個屬性能夠用來表示Servlet的訪問路徑,分別是value和urlPatterns。value和urlPatterns都是數組形式,表示咱們能夠把一個Servlet映射到多個訪問路徑,可是value和urlPatterns不能同時使用。若是同時使用了value和urlPatterns,咱們的Servlet是沒法訪問到的。下面是一個使用@WebServlet的簡單Servlet示例。this
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * * Servlet3.0支持使用註解配置Servlet。咱們只需在Servlet對應的類上使用@WebServlet進行標註, * 咱們就能夠訪問到該Servlet了,而不須要再在web.xml文件中進行配置。@WebServlet的urlPatterns * 和value屬性均可以用來表示Servlet的部署路徑,它們都是對應的一個數組。 */ @WebServlet(name="exampleServlet", urlPatterns="/servlet/example") public class ExampleServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write("Hello User."); } }
使用@WebServlet時也能夠配置初始化參數,它是經過@WebServlet的initParams參數來指定的。initParams是一個@WebInitParam數組,每個@WebInitParam表明一個初始化參數。
import java.io.IOException; import java.util.Enumeration; 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; /** * 帶初始化參數的Servlet * WebServlet的屬性initParams能夠用來指定當前Servlet的初始化參數,它是一個數組, * 裏面每個@WebInitParam表示一個參數。 */ @WebServlet(value="/servlet/init-param", initParams={@WebInitParam(name="param1", value="value1")}) public class WebInitParamServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; @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 { Enumeration<String> paramNames = this.getServletConfig().getInitParameterNames(); String paramName; while (paramNames.hasMoreElements()) { paramName = paramNames.nextElement(); resp.getWriter().append(paramName + " = " + this.getServletConfig().getInitParameter(paramName)); } resp.getWriter().close(); } }
在Servlet3.0中,在Servlet內部支持異步處理。它的邏輯是當咱們請求一個Servlet時,咱們的Servlet能夠先返回一部份內容給客戶端。而後在Servlet內部異步處理另一段邏輯,等到異步處理完成以後,再把異步處理的結果返回給客戶端。這意味着當咱們的Servlet在處理一段比較費時的業務邏輯時,咱們能夠先返回一部分信息給客戶端,而後異步處理費時的業務,而沒必要讓客戶端一直等待全部的業務邏輯處理完。等到異步處理完以後,再把對應的處理結果返回給客戶端。
異步調用是經過當前HttpServletRequest的startAsync()方法開始的,它返回一個AsyncContext。以後咱們能夠調用AsyncContext的start()方法來新起一個線程進行異步調用。在新線程內部程序的最後咱們最好是調用一下當前AsyncContext的complete()方法,不然異步調用的結果須要等到設置的超時時間事後纔會返回到客戶端。另外當異步調用超時之後會接着調用異步任務,即新起的線程。
import java.io.IOException; import java.io.PrintWriter; 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; /** * 支持異步返回的Servlet * 對於Servlet的異步返回,首先咱們必須指定@WebServlet的asyncSupported屬性爲true(默認是false),同時在它以前的Filter * 的asyncSupported屬性也必須是true,不然傳遞過來的request就是不支持異步調用的。 * */ @WebServlet(value="/servlet/async", asyncSupported=true) public class AsyncServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; @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/plain;charset=UTF-8"); final PrintWriter writer = resp.getWriter(); writer.println("異步以前輸出的內容。"); writer.flush(); //開始異步調用,獲取對應的AsyncContext。 final AsyncContext asyncContext = req.startAsync(); //設置超時時間,當超時以後程序會嘗試從新執行異步任務,即咱們新起的線程。 asyncContext.setTimeout(10*1000L); //新起線程開始異步調用,start方法不是阻塞式的,它會新起一個線程來啓動Runnable接口,以後主程序會繼續執行 asyncContext.start(new Runnable() { @Override public void run() { try { Thread.sleep(5*1000L); writer.println("異步調用以後輸出的內容。"); writer.flush(); //異步調用完成,若是異步調用完成後不調用complete()方法的話,異步調用的結果須要等到設置的超時 //時間過了以後才能返回到客戶端。 asyncContext.complete(); } catch (Exception e) { e.printStackTrace(); } } }); writer.println("可能在異步調用前輸出,也可能在異步調用以後輸出,由於異步調用會新起一個線程。"); writer.flush(); } }
對於一個Servlet若是要支持異步調用的話咱們必須指定其asyncSupported屬性爲true(默認是false)。使用@WebServlet註解標註的Servlet咱們能夠直接指定其asyncSupported屬性的值爲true,如:
@WebServlet(value=」/servlet/async」, asyncSupported=true)。而對於在web.xml文件中進行配置的Servlet來講,咱們須要在配置的時候指定其asyncSupported屬性爲true。
<servlet> <servlet-name>xxx</servlet-name> <servlet-class>xxx</servlet-class> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>xxx</servlet-name> <url-pattern>xxx</url-pattern> </servlet-mapping>
Servlet的異步調用程序的關鍵是要調用當前HttpServletRequest的startAsync()方法。至於利用返回的AsyncContext來新起一個線程進行異步處理就不是那麼的必須了,由於在HttpServletRequest startAsync()以後,咱們能夠本身新起線程進行異步處理。
@WebServlet(value="/servlet/async", asyncSupported=true) public class AsyncServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; @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/plain;charset=UTF-8"); final PrintWriter writer = resp.getWriter(); writer.println("異步以前輸出的內容。"); writer.flush(); //開始異步調用,獲取對應的AsyncContext。 final AsyncContext asyncContext = req.startAsync(); //設置超時時間,當超時以後程序會嘗試從新執行異步任務,即咱們新起的線程。 asyncContext.setTimeout(10*1000L); Runnable r = new Runnable() { @Override public void run() { try { Thread.sleep(5*1000L); writer.println("異步調用以後輸出的內容。"); writer.flush(); //異步調用完成 asyncContext.complete(); } catch (Exception e) { e.printStackTrace(); } } }; Thread t = new Thread(r); //開啓本身的線程進行異步處理 t.start(); writer.println("可能在異步調用前輸出,也可能在異步調用以後輸出,由於異步調用會新起一個線程。"); writer.flush(); } }
異步調用監聽器
當咱們須要對異步調用作一個詳細的監聽的時候,好比監聽它是否超時,咱們能夠經過給AsyncContext設置對應的監聽器AsyncListener來實現這一功能。AsyncListener是一個接口,裏面定義了四個方法,分別是針對於異步調用開始、結束、出錯和超時的。
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 支持異步返回的Servlet * 對於Servlet的異步返回,首先咱們必須指定@WebServlet的asyncSupported屬性爲true(默認是false),同時在它以前的Filter * 的asyncSupported屬性也必須是true,不然傳遞過來的request就是不支持異步調用的。 * */ @WebServlet(value="/servlet/async2", asyncSupported=true) public class AsyncServlet2 extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; @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/plain;charset=UTF-8"); final PrintWriter writer = resp.getWriter(); writer.println("異步以前輸出的內容。"); writer.flush(); //開始異步調用,獲取對應的AsyncContext。 final AsyncContext asyncContext = req.startAsync(); //設置當前異步調用對應的監聽器 asyncContext.addListener(new MyAsyncListener()); //設置超時時間,當超時以後程序會嘗試從新執行異步任務,即咱們新起的線程。 asyncContext.setTimeout(10*1000L); //新起線程開始異步調用,start方法不是阻塞式的,它會新起一個線程來啓動Runnable接口,以後主程序會繼續執行 asyncContext.start(new Runnable() { @Override public void run() { try { Thread.sleep(5*1000L); writer.println("異步調用以後輸出的內容。"); writer.flush(); //異步調用完成 asyncContext.complete(); } catch (Exception e) { e.printStackTrace(); } } }); writer.println("可能在異步調用前輸出,也可能在異步調用以後輸出,由於異步調用會新起一個線程。"); writer.flush(); } /** * 異步調用對應的監聽器 * @author Yeelim * @date 2014-2-8 * @mail yeelim-zhang@todaytech.com.cn */ private class MyAsyncListener implements AsyncListener { @Override public void onComplete(AsyncEvent event) throws IOException { System.out.println("異步調用完成……"); event.getSuppliedResponse().getWriter().println("異步調用完成……"); } @Override public void onError(AsyncEvent event) throws IOException { System.out.println("異步調用出錯……"); event.getSuppliedResponse().getWriter().println("異步調用出錯……"); } @Override public void onStartAsync(AsyncEvent event) throws IOException { System.out.println("異步調用開始……"); event.getSuppliedResponse().getWriter().println("異步調用開始……"); } @Override public void onTimeout(AsyncEvent event) throws IOException { System.out.println("異步調用超時……"); event.getSuppliedResponse().getWriter().println("異步調用超時……"); } } }
注:
對於正常執行的異步調用而言上述代碼中開始是沒有監聽到的,只有在異步調用超時,從新執行異步任務的時候纔有監聽到異步調用的開始。不過若是須要監聽異步第一次開始的話,咱們能夠在異步調用開始的時候作相應的監聽器監聽到異步調用開始時須要作的內容。
在Servlet3.0中上傳文件變得很是簡單。咱們只需經過request的getPart(String partName)獲取到上傳的對應文件對應的Part或者經過getParts()方法獲取到全部上傳文件對應的Part。以後咱們就能夠經過part的write(String fileName)方法把對應文件寫入到磁盤。或者經過part的getInputStream()方法獲取文件對應的輸入流,而後再對該輸入流進行操做。要使用request的getPart()或getParts()方法對上傳的文件進行操做的話,有兩個要注意的地方。首先,用於上傳文件的form表單的enctype必須爲multipart/form-data;其次,對於使用註解聲明的Servlet,咱們必須在其對應類上使用@MultipartConfig進行標註,而對於在web.xml文件進行配置的Servlet咱們也須要指定其multipart-config屬性,如:
<servlet> <servlet-name>xxx</servlet-name> <servlet-class>xxx.xxx</servlet-class> <multipart-config></multipart-config> </servlet> <servlet-mapping> <servlet-name>xxx</servlet-name> <url-pattern>/servlet/xxx</url-pattern> </servlet-mapping>
無論是基於註解的@MultipartConfig,仍是基於web.xml文件配置的multipart-config,咱們均可以給它們設置幾個屬性。
file-size-threshold:數字類型,當文件大小超過指定的大小後將寫入到硬盤上。默認是0,表示全部大小的文件上傳後都會做爲一個臨時文件寫入到硬盤上。
location:指定上傳文件存放的目錄。當咱們指定了location後,咱們在調用Part的write(String fileName)方法把文件寫入到硬盤的時候能夠,文件名稱能夠不用帶路徑,可是若是fileName帶了絕對路徑,那將以fileName所帶路徑爲準把文件寫入磁盤。
max-file-size:數值類型,表示單個文件的最大大小。默認爲-1,表示不限制。當有單個文件的大小超過了max-file-size指定的值時將拋出IllegalStateException異常。
max-request-size:數值類型,表示一次上傳文件的最大大小。默認爲-1,表示不限制。當上傳時全部文件的大小超過了max-request-size時也將拋出IllegalStateException異常。
上面的屬性是針對於web.xml中配置Servlet而言的,其中的每個屬性都對應了multipart-config元素下的一個子元素。對於基於註解配置的Servlet而言,@MultipartConfig的屬性是類型的,咱們只需把上述對應屬性中間的槓去掉,而後把對應字母大寫便可,如maxFileSize。
下面給出Servlet3.0中文件上傳的一個示例。
Html:
<form method="post" action="servlet/upload" enctype="multipart/form-data"> <input type="file" name="upload"/> <input type="submit" value="upload"/> </form>
對應Servlet:
@WebServlet("/servlet/upload") @MultipartConfig public class FileUploadServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 1L; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); Part part = req.getPart("upload"); //格式如:form-data; name="upload"; filename="YNote.exe" String disposition = part.getHeader("content-disposition"); System.out.println(disposition); String fileName = disposition.substring(disposition.lastIndexOf("=")+2, disposition.length()-1); String fileType = part.getContentType(); long fileSize = part.getSize(); System.out.println("fileName: " + fileName); System.out.println("fileType: " + fileType); System.out.println("fileSize: " + fileSize); String uploadPath = req.getServletContext().getRealPath("/upload"); System.out.println("uploadPath" + uploadPath); part.write(uploadPath + File.separator +fileName); } }
對於Servlet3.0中的文件上傳還有一個須要注意的地方,當咱們把Part寫入到硬盤之後,咱們原先的Part(也就是以前的臨時文件)可能已經刪了,這個時候若是咱們再次去訪問Part的內容的話,那它就是空的,系統會拋出異常說找不到對應的文件。