Servlet 是一些聽從Java Servlet API的Java類,這些Java類能夠響應請求。儘管Servlet能夠響應任意類型的請求,可是它們使用最普遍的是響應web方面的請求。 Servlet必須部署在Java servlet容器才能使用。雖然不少開發者都使用Java Server Pages(JSP)和Java Server Faces(JSF)等Servlet框架,可是這些技術都要在幕後經過Servlet容器把頁面編譯爲Java Servlet。也就是說,瞭解Java Servlet技術的基礎知識對任何Java web開發者來講是頗有用的。html
在這個教程裏,咱們將會經過下面的專題來全面瞭解Java Servlet技術。java
目錄web
讓咱們一塊兒來一步步地學習Servlet。api
咱們的第一個Servlet是一個只擁有少許代碼的簡單Servlet,目的是讓你只需關注它的行爲。瀏覽器
package com.howtodoinjava.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyFirstServlet extends HttpServlet { private static final long serialVersionUID = -1915463532411657451L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { // Write some content out.println("<html>"); out.println("<head>"); out.println("<title>MyFirstServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>Servlet MyFirstServlet at " + request.getContextPath() + "</h2>"); out.println("</body>"); out.println("</html>"); } finally { out.close(); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //Do some other work } @Override public String getServletInfo() { return "MyFirstServlet"; } }
爲了在web容器裏註冊上面的Servlet,你要爲你的應用建一個web.xml入口文件。緩存
<?xml version="1.0"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <welcome-file-list> <welcome-file>/MyFirstServlet</welcome-file> </welcome-file-list> <servlet> <servlet-name>MyFirstServlet</servlet-name> <servlet-class>com.howtodoinjava.servlets.MyFirstServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyFirstServlet</servlet-name> <url-pattern>/MyFirstServlet</url-pattern> </servlet-mapping> </web-app>
上面的Servlet作了一些重要的事情,你可能想了解的。tomcat
以上全部關於簡單Servlet的內容就是你須要知道的內容。安全
在你的應用加載並使用一個Servlet時,從初始化到銷燬這個Servlet期間會發生一系列的事件。這些事件叫作Servlet的生命週期事件(或方法)。讓咱們一塊兒來進一步瞭解它們。服務器
Servlet生命週期的三個核心方法分別是 init() , service() 和 destroy()。每一個Servlet都會實現這些方法,而且在特定的運行時間調用它們。cookie
1) 在Servlet生命週期的初始化階段,web容器經過調用init()方法來初始化Servlet實例,而且能夠傳遞一個實現 javax.servlet.ServletConfig 接口的對象給它。這個配置對象(configuration object)使Servlet可以讀取在web應用的web.xml文件裏定義的名值(name-value)初始參數。這個方法在Servlet實例的生命週期裏只調用一次。
init方法定義與這相似:
public void init() throws ServletException { //custom initialization code }
2) 初始化後,Servlet實例就能夠處理客戶端請求了。web容器調用Servlet的service()方法來處理每個請求。service() 方法定義了可以處理的請求類型而且調用適當方法來處理這些請求。編寫Servlet的開發者必須爲這些方法提供實現。若是發出一個Servlet沒實現的請求,那麼父類的方法就會被調用而且一般會給請求方(requester)返回一個錯誤信息。
一般,咱們不須要重寫(override)這個方法。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
3) 最後,web容器調用destroy()方法來終結Servlet。若是你想在Servlet的生命週期內關閉或者銷燬一些文件系統或者網絡資源,你能夠調用這個方法來實現。destroy() 方法和init()方法同樣,在Servlet的生命週期裏只能調用一次。
public void destroy() { // }
在大多數狀況下,你一般不須要在你的Servlet裏重寫這些方法。
擴展閱讀:web服務器是如何運做的?
若是你不喜歡使用xml配置而喜歡註解的話,不要緊,Servlets API一樣提供了一些註解接口給你。你能夠像下面的例子同樣使用 @WebServlet 註解而且不須要在web.xml裏爲Servlet註冊任何信息。容器會自動註冊你的Servlet到運行環境,而且像往常同樣處理它。
package com.howtodoinjava.servlets; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name = "MyFirstServlet", urlPatterns = {"/MyFirstServlet"}) public class MyFirstServlet extends HttpServlet { private static final long serialVersionUID = -1915463532411657451L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //Do some work } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //Do some other work } }
若是你在使用IDE(例如eclipse),那麼打包和部署你的應用只須要一個簡單的步驟。右擊項目> Run As > Run As Server。若是還沒配置服務器先配置好服務器,而後就能夠準備開幹了。
若是你沒在使用IDE,那麼你須要作一些額外的工做。好比,使用命令提示符編譯應用,使用ANT去生成war文件等等。但我相信,如今的開發者都在使用IDE來開發。因此我就不在這方面浪費時間了。
當你把咱們的第一個Servlet部署到tomcat上並在瀏覽器輸入「http://localhost:8080/servletexamples/MyFirstServlet」,你會獲得下面的響應。
Java Servlets如此有用的緣由之一是Servlet能動態顯示網頁內容。這些內容能夠從服務器自己、另一個網站、或者許多其餘網絡能夠訪問的資源裏獲取。Servlet不是靜態網頁,它們是動態的。能夠說這是它們最大的優點。
讓咱們來舉個Servlet例子,這個Servlet會顯示當前日期和時間給用戶而且會顯示用戶名和一些自定義的信息。讓咱們來爲這個功能編寫代碼吧。
package com.howtodoinjava.servlets; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name = "CalendarServlet", urlPatterns = {"/CalendarServlet"}) public class CalendarServlet extends HttpServlet { private static final long serialVersionUID = -1915463532411657451L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Map<String,String> data = getData(); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { // Write some content out.println("<html>"); out.println("<head>"); out.println("<title>CalendarServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>Hello " + data.get("username") + ", " + data.get("message") + "</h2>"); out.println("<h2>The time right now is : " + new Date() + "</h2>"); out.println("</body>"); out.println("</html>"); } finally { out.close(); } } //This method will access some external system as database to get user name, and his personalized message private Map<String, String> getData() { Map<String, String> data = new HashMap<String, String>(); data.put("username", "Guest"); data.put("message", "Welcome to my world !!"); return data; } }
當你在tomcat裏運行上面的Servlet並在瀏覽器裏輸入「http://localhost:8080/servletexamples/CalendarServlet」,你會得得下面的響應。
Servlet能夠輕鬆建立一個基於請求和響應生命週期的web應用。它們可以提供HTTP響應而且可使用同一段代碼來處理業務邏輯。處理業務邏輯的能力使Servlet比標準的HTML代碼更強大。
現實世界裏的應用,一個HTML網頁表單包含了要發送給Servlet的參數。Servlet會以某種方式來處理這些參數而且 返回一個客戶端可以識別的響應。在對象是HttpServlet的狀況下,客戶端是web瀏覽器,響應是web頁面。<form>的 action屬性指定了使用哪一個Servlet來處理表單裏的參數值。
爲了獲取請求參數,須要調用 HttpServletRequest 對象的 getParameter() 方法,而且傳遞你要獲取的輸入參數的id給該方法。
String value1 = req.getParameter("param1"); String value1 = req.getParameter("param2");
一旦獲取了參數值,它們就會在須要時被處理。對客戶端的響應和咱們上面部分討論的同樣。咱們使用 HttpServletResponse 對象給客戶端發送響應。
request和response處理的基本使用能夠是這樣的:
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); String username = request.getParameter("username"); String password = request.getParameter("password"); boolean success = validateUser(username, password); try { // Write some content out.println("<html>"); out.println("<head>"); out.println("<title>LoginServlet</title>"); out.println("</head>"); out.println("<body>"); if(success) { out.println("<h2>Welcome Friend</h2>"); }else{ out.println("<h2>Validate your self again.</h2>"); } out.println("</body>"); out.println("</html>"); } finally { out.close(); } }
爲了發送內容給客戶端,你須要使用從 HttpServletResponse 裏獲取的 PrintWriter 對象。任何寫到這個對象的內容都會被寫進outputstream裏,並會把內容發送回給客戶端。
有時候,知道應用服務器容器(the application server container)裏某些事件發生的時間是頗有用的。這個概念適用於不少狀況,但它一般用在開啓應用時初始化應用或者關閉應用時清理應用。能夠在應用裏 註冊一個監聽器(listener)來顯示應用何時開啓或者關閉。所以,經過監聽這些事件,Servlet能夠在一些事件發生時執行相應的動做。
爲了建立一個基於容器事件執行動做的監聽器,你必須建立一個實現 ServletContextListener 接口的類。這個類必須實現的方法有 contextInitialized() 和 contextDestroyed()。這兩個方法都須要 ServletContextEvent 做爲參數,而且在每次初始化或者關閉Servlet容器時都會被自動調用。
爲了在容器註冊監聽器,你可使用下面其中一個方法:
1) 利用 @WebListener 註解。
2) 在web.xml應用部署文件裏註冊監聽器。
3) 使用 ServletContext 裏定義的 addListener() 方法
請注意,ServletContextListener 不是Servlet API裏惟一的監聽器。這裏還有一些其餘的監聽器,好比:
javax.servlet.ServletRequestListener javax.servlet.ServletRequestAttrbiteListener javax.servlet.ServletContextListener javax.servlet.ServletContextAttributeListener javax.servlet.HttpSessionListener javax.servlet.HttpSessionAttributeListener
根據你要監聽的事件選擇他們來實現你的監聽器類。好比,每當建立或銷燬一個用戶session時,HttpSessionListener 就會發出通知。
如今的大多數應用都須要設置一些在應用/控制器(controller)啓動時能夠傳遞的配置參數(configuration parameters)。Servlet一樣能夠接受初始化參數,並在處理第一個請求前來使用它們來構建配置參數。
顯然,你也能夠在Servlet裏硬編碼配置值。可是這樣作的話,在Servlet發生改動時你須要再次從新編譯整個應用。沒有人喜歡這樣作。
<web-app> <servlet> <servlet-name>SimpleServlet</servlet-name> <servlet-class>com.howtodoinjava.servlets.SimpleServlet</servlet-class> <!-- Servlet init param --> <init-param> <param-name>name</param-name> <param-value>value</param-value> </init-param> </servlet> </web-app>
設置後,你就能夠在代碼裏調用 getServletConfig.getInitializationParameter() 並傳遞參數名給該方法來使用參數。就像下面展現的代碼同樣:
String value = getServletConfig().getInitParameter("name");
Web過濾器在給定的URL被訪問時對請求進行預處理並調用相應的功能是頗有用的。相 比於直接調用給定URL請求的Servlet,包含相同URL模式的過濾器(filter)會在Servlet調用前被調用。這在不少狀況下是頗有用的。 或許最大的用處就是執行日誌,驗證或者其餘不須要與用戶交互的後臺服務。
過濾器必需要實現 javax.servlet.Filter 接口。這個接口包含了init(),descriptor()和doFilter()這些方法。init()和destroy()方法會被容器調用。 doFilter()方法用來在過濾器類裏實現邏輯任務。若是你想把過濾器組成過濾鏈(chain filter)或者存在多匹配給定URL模式的個過濾器,它們就會根據web.xml裏的配置順序被調用。
爲了在web.xml裏配置過濾器,須要使用<filter>和<filter-mapping> XML元素以及相關的子元素標籤。
<filter> <filter-name>LoggingFilter</filter-name> <filter-class>LoggingFilter</filter-class> </filter> <filter-mapping> <filter-name>LogingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 若是你要使用註解來爲特定的servlet配置過濾器,你可使用@WebFilter註解。
幾乎全部的web應用都必須有下載文件的功能。爲了下載一個文件,Servlet必須提供一個和下載文件類型匹配的響應類型。一樣,必須在響應頭裏指出該響應包含附件。就像下面的代碼。
String mimeType = context.getMimeType( fileToDownload ); response.setContentType( mimeType != null ? mimeType : "text/plain" ); response.setHeader( "Content-Disposition", "attachment; filename="" + fileToDownload + """ );
經過調用 ServletContext.getResourceAsStream() 方法並傳遞文件路徑給該方法,你能夠獲取要下載的文件(文件保存在文件系統)的引用。這個方法會返回一個輸入流(InputStream)對 象,咱們能夠用這個對象來讀取文件內容。當讀取文件時,咱們建立一個字節緩存區(byte buffer)從文件裏獲取數據塊。最後的工做就是讀取文件內容而且把它們複製到輸出流。咱們使用while循環來完成文件的讀取,這個循環直到讀取了文 件的全部內容纔會跳出循環。咱們使用循環來讀進數據塊並把它寫進輸出流。把全部數據寫進輸出流後,ServletOutputStream 對象的flush方法就會被調用而且清空內容和釋放資源。
看這段簡單的代碼:
private void downloadFile(HttpServletRequest request, HttpServletResponse response, String fileToDownload) throws IOException { final int BYTES = 1024; int length = 0; ServletOutputStream outStream = response.getOutputStream(); ServletContext context = getServletConfig().getServletContext(); String mimeType = context.getMimeType( fileToDownload ); response.setContentType( mimeType != null ? mimeType : "text/plain" ); response.setHeader( "Content-Disposition", "attachment; filename="" + fileToDownload + """ ); InputStream in = context.getResourceAsStream("/" + fileToDownload); byte[] bbuf = new byte[BYTES]; while ((in != null) && ((length = in.read(bbuf)) != -1)) { outStream.write(bbuf, 0, length); } outStream.flush(); outStream.close(); }
有時候,你的應用須要把一個Servlet要處理的請求轉讓給另外的Servlet來處理並完成任務。並且,轉讓請求時不能重定向客戶端的URL。即瀏覽器地址欄上的URL不會改變。
在 ServletContext 裏已經內置了實現上面需求的方法。因此,當你獲取了 ServletContext 的引用,你就能夠簡單地調用getRequestDispatcher() 方法去獲取用來轉發請求的 RequestDispatcher 對象。當調用 getRequestDispatcher() 方法時,須要傳遞包含servlet名的字符串,這個Servlet就是你用來處理轉讓請求的Servlet。獲取 RequestDispatcher 對象後,經過傳遞 HttpServletRequest 和HttpServletResponse 對象給它來調用轉發方法。轉發方法負責對請求進行轉發。
RequestDispatcher rd = servletContext.getRequestDispatcher("/NextServlet"); rd.forward(request, response);
儘管有時候,你不想在Servlet發送重定向時通知用戶,就像咱們在上面那段看到的同樣。可是在某些狀況下,咱們確實想要通知用戶。當應用內的特定URL被訪問時,你想把瀏覽器的URL重定向到另一個。
要實現這種功能,你須要調用 HttpServletResponse 對象的sendRedirect()方法。
httpServletResponse.sendRedirect("/anotherURL"); 這個簡單的重定向,與servlet鏈(servlet chaining)相反,不須要傳遞目標地址的HttpRequest對象。
不少應用都想在客戶端機器裏保存用戶當前的瀏覽歷史。目的是當用戶再次使用應用時,他可以從上次離開的地方開始瀏覽。爲了實現這個需求,一般使用cookies。你能夠把它看做是保存在客戶端機器裏的鍵值對基本數據。當使用瀏覽器打開應用時,應用能夠對這些數據進行讀寫。
爲了建立cookie,須要實例化一個新的 javax.servlet.http.Cookie 對象而且爲它分配名稱和值。實例化cookie後,能夠設置屬性來配置cookie。在這個例子裏,咱們使用 setMaxAge() 和 setHttpOnly() 方法來設置cookie的生命週期和防範客戶端腳本。
從Servlet3.0 API開始,已經能夠把cookie標記爲HTTP only了。這使cookie能夠防範客戶端腳本的攻擊,使cookie更加安全。 Cookie cookie = new Cookie("sessionId","123456789"); cookie.setHttpOnly(true); cookie.setMaxAge(-30); response.addCookie(cookie);
這裏的response是傳遞給doXXX()方法的 HttpServletResponse 實例。
要讀取服務端的cookie信息,使用下面代碼:
Cookie[] cookies = request.getCookies(); for(Cookie cookie : cookies) { //cookie.getName(); //cookie.getValue() }
這就是這篇教程裏關於Servlet技術的所有內容了。歡迎評論和回饋。