Servlet3.0中Servlet的使用(註解&&配置檔案)
Servlet3.0中Servlet的使用
目錄
1.註解配置
2.非同步呼叫
3.檔案上傳
相對於之前的版本,Servlet3.0中的Servlet有以下改進:
l 支援註解配置。
l 支援非同步呼叫。
l 直接有對檔案上傳的支援。
在這篇文章中我將主要講這三方面的應用示例。
1.註解配置
在以往我們的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示例。
- 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
- 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代表一個初始化引數。
Java程式碼- 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();
- }
- }
2.非同步呼叫
在Servlet3.0中,在Servlet內部支援非同步處理。它的邏輯是當我們請求一個Servlet時,我們的Servlet可以先返回一部分內容給客戶端。然後在Servlet內部非同步處理另外一段邏輯,等到非同步處理完成之後,再把非同步處理的結果返回給客戶端。這意味著當我們的Servlet在處理一段比較費時的業務邏輯時,我們可以先返回一部分資訊給客戶端,然後非同步處理費時的業務,而不必讓客戶端一直等待所有的業務邏輯處理完。等到非同步處理完之後,再把對應的處理結果返回給客戶端。
非同步呼叫是通過當前HttpServletRequest的startAsync()方法開始的,它返回一個AsyncContext。之後我們可以呼叫AsyncContext的start()方法來新起一個執行緒進行非同步呼叫。在新執行緒內部程式的最後我們最好是呼叫一下當前AsyncContext的complete()方法,否則非同步呼叫的結果需要等到設定的超時時間過後才會返回到客戶端。另外當非同步呼叫超時以後會接著呼叫非同步任務,即新起的執行緒。
Java程式碼- 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。
Xml程式碼- <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()之後,我們可以自己新起執行緒進行非同步處理。
Java程式碼- @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是一個介面,裡面定義了四個方法,分別是針對於非同步呼叫開始、結束、出錯和超時的。
Java程式碼- 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 [email protected]
- */
- 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("非同步呼叫超時……");
- }
- }
- }
注:
對於正常執行的非同步呼叫而言上述程式碼中開始是沒有監聽到的,只有在非同步呼叫超時,重新執行非同步任務的時候才有監聽到非同步呼叫的開始。不過如果需要監聽非同步第一次開始的話,我們可以在非同步呼叫開始的時候做相應的監聽器監聽到非同步呼叫開始時需要做的內容。
3.檔案上傳
在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屬性,如:
Xml程式碼- <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,我們都可以給它們設定幾個屬性。
l file-size-threshold:數字型別,當檔案大小超過指定的大小後將寫入到硬碟上。預設是0,表示所有大小的檔案上傳後都會作為一個臨時檔案寫入到硬碟上。
l location:指定上傳檔案存放的目錄。當我們指定了location後,我們在呼叫Part的write(String fileName)方法把檔案寫入到硬碟的時候可以,檔名稱可以不用帶路徑,但是如果fileName帶了絕對路徑,那將以fileName所帶路徑為準把檔案寫入磁碟。
l max-file-size:數值型別,表示單個檔案的最大大小。預設為-1,表示不限制。當有單個檔案的大小超過了max-file-size指定的值時將丟擲IllegalStateException異常。
l max-request-size:數值型別,表示一次上傳檔案的最大大小。預設為-1,表示不限制。當上傳時所有檔案的大小超過了max-request-size時也將丟擲IllegalStateException異常。
上面的屬性是針對於web.xml中配置Servlet而言的,其中的每一個屬性都對應了multipart-config元素下的一個子元素。對於基於註解配置的Servlet而言,@MultipartConfig的屬性是型別的,我們只需把上述對應屬性中間的槓去掉,然後把對應字母大寫即可,如maxFileSize。
下面給出Servlet3.0中檔案上傳的一個示例。
Html:
Html程式碼- <form method="post" action="servlet/upload" enctype="multipart/form-data">
- <input type="file" name="upload"/>
- <input type="submit" value="upload"/>
- </form>
對應Servlet:
Java程式碼- @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的內容的話,那它就是空的,系統會丟擲異常說找不到對應的檔案。