Java Servlet完全教程
Servlet 是一些遵從Java Servlet API的Java類,這些Java類可以響應(yīng)請求。盡管Servlet可以響應(yīng)任意類型的請求,但是它們使用最廣泛的是響應(yīng)web方面的請求。 Servlet必須部署在Java servlet容器才能使用。雖然很多開發(fā)者都使用Java Server Pages(JSP)和Java Server Faces(JSF)等Servlet框架,但是這些技術(shù)都要在幕后通過Servlet容器把頁面編譯為Java Servlet。也就是說,了解Java Servlet技術(shù)的基礎(chǔ)知識對任何Java web開發(fā)者來說是很有用的。
在這個教程里,我們將會通過下面的專題來全面了解Java Servlet技術(shù)。
目錄編寫你的第一個ServletServlet生命周期方法使用@WebServlet注解開發(fā)Servlet打包和部署Servlet到Tomcat服務(wù)器編寫動態(tài)的Servlet響應(yīng)內(nèi)容處理Servlet請求和響應(yīng)監(jiān)聽Servlet容器事件傳遞Servlet初始化參數(shù)為特定的URL請求添加Servlet過濾器使用Servlet下載二進制文件使用RequestDispatcher.forward()轉(zhuǎn)發(fā)請求到另一個Servlet使用HttpServletResponse.sendRedirect()重定向請求到另一個Servlet使用Servlets讀寫Cookie讓我們一起來一步步地學習Servlet。
編寫你的第一個Servlet我們的第一個Servlet是一個只擁有少量代碼的簡單Servlet,目的是讓你只需關(guān)注它的行為。
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;@Overrideprotected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {response.setContentType('text/html;charset=UTF-8');PrintWriter out = response.getWriter();try {// Write some contentout.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();}}@Overrideprotected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {//Do some other work}@Overridepublic String getServletInfo() {return 'MyFirstServlet';}}
為了在web容器里注冊上面的Servlet,你要為你的應(yīng)用建一個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/javaeehttp://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做了一些重要的事情,你可能想了解的。
MyFirstServlet類繼承了HttpServlet。這個繼承是必須的,因為所有的Servlet必須是要么繼承了 javax.servlet.GenericServlet 的普通Servlet,要么是繼承了 javax.servlet.http.HttpServlet 的HTTP Servlet。重新 doGet() 和 doPost() 方法。這兩個方法都已在 HttpServlet 類里定義了。當一個GET或POST請求到來時,它就會被映射到相應(yīng)的方法里。例如,如果你向這個servlet發(fā)送一個HTTP GET請求,doGet()方法就會被調(diào)用。這里也有一些其他有用的方法。你可以重寫它們來在運行時控制應(yīng)用。例如getServletInfo()。HttpServletRequest 和 HttpServletResponse 是所有doXXX()方法的默認參數(shù)。我們會在后面的章節(jié)里詳細學習這些對象。以上所有關(guān)于簡單Servlet的內(nèi)容就是你需要知道的內(nèi)容。
Servlet生命周期方法在你的應(yīng)用加載并使用一個Servlet時,從初始化到銷毀這個Servlet期間會發(fā)生一系列的事件。這些事件叫做Servlet的生命周期事件(或方法)。讓我們一起來進一步了解它們。
Servlet生命周期的三個核心方法分別是 init() , service() 和 destroy()。每個Servlet都會實現(xiàn)這些方法,并且在特定的運行時間調(diào)用它們。
1) 在Servlet生命周期的初始化階段,web容器通過調(diào)用init()方法來初始化Servlet實例,并且可以傳遞一個實現(xiàn) javax.servlet.ServletConfig 接口的對象給它。這個配置對象(configuration object)使Servlet能夠讀取在web應(yīng)用的web.xml文件里定義的名值(name-value)初始參數(shù)。這個方法在Servlet實例的生命周期里只調(diào)用一次。
init方法定義與這類似:
public void init() throws ServletException {//custom initialization code}
2) 初始化后,Servlet實例就可以處理客戶端請求了。web容器調(diào)用Servlet的service()方法來處理每一個請求。service() 方法定義了能夠處理的請求類型并且調(diào)用適當方法來處理這些請求。編寫Servlet的開發(fā)者必須為這些方法提供實現(xiàn)。如果發(fā)出一個Servlet沒實現(xiàn)的請求,那么父類的方法就會被調(diào)用并且通常會給請求方(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 logicdoGet(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 lessmaybeSetLastModified(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容器調(diào)用destroy()方法來終結(jié)Servlet。如果你想在Servlet的生命周期內(nèi)關(guān)閉或者銷毀一些文件系統(tǒng)或者網(wǎng)絡(luò)資源,你可以調(diào)用這個方法來實現(xiàn)。destroy() 方法和init()方法一樣,在Servlet的生命周期里只能調(diào)用一次。
public void destroy() {//}
在大多數(shù)情況下,你通常不需要在你的Servlet里重寫這些方法。
擴展閱讀:web服務(wù)器是如何運作的?
使用@WebServlet注解來開發(fā)Servlet如果你不喜歡使用xml配置而喜歡注解的話,沒關(guān)系,Servlets API同樣提供了一些注解接口給你。你可以像下面的例子一樣使用 @WebServlet 注解并且不需要在web.xml里為Servlet注冊任何信息。容器會自動注冊你的Servlet到運行環(huán)境,并且像往常一樣處理它。
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 }}打包和部署Servlet到Tomcat服務(wù)器
如果你在使用IDE(例如Eclipse),那么打包和部署你的應(yīng)用只需要一個簡單的步驟。右擊項目> Run As > Run As Server。如果還沒配置服務(wù)器先配置好服務(wù)器,然后就可以準備開干了。
如果你沒在使用IDE,那么你需要做一些額外的工作。比如,使用命令提示符編譯應(yīng)用,使用ANT去生成war文件等等。但我相信,現(xiàn)在的開發(fā)者都在使用IDE來開發(fā)。所以我就不在這方面浪費時間了。
當你把我們的第一個Servlet部署到tomcat上并在瀏覽器輸入“http://localhost:8080/servletexamples/MyFirstServlet”,你會得到下面的響應(yīng)。
Java Servlets如此有用的原因之一是Servlet能動態(tài)顯示網(wǎng)頁內(nèi)容。這些內(nèi)容可以從服務(wù)器本身、另外一個網(wǎng)站、或者許多其他網(wǎng)絡(luò)可以訪問的資源里獲取。Servlet不是靜態(tài)網(wǎng)頁,它們是動態(tài)的。可以說這是它們最大的優(yōu)勢。
讓我們來舉個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”,你會得得下面的響應(yīng)。
Servlet可以輕松創(chuàng)建一個基于請求和響應(yīng)生命周期的web應(yīng)用。它們能夠提供HTTP響應(yīng)并且可以使用同一段代碼來處理業(yè)務(wù)邏輯。處理業(yè)務(wù)邏輯的能力使Servlet比標準的HTML代碼更強大。
現(xiàn)實世界里的應(yīng)用,一個HTML網(wǎng)頁表單包含了要發(fā)送給Servlet的參數(shù)。Servlet會以某種方式來處理這些參數(shù)并且 返回一個客戶端能夠識別的響應(yīng)。在對象是HttpServlet的情況下,客戶端是web瀏覽器,響應(yīng)是web頁面。<form>的 action屬性指定了使用哪個Servlet來處理表單里的參數(shù)值。
為了獲取請求參數(shù),需要調(diào)用 HttpServletRequest 對象的 getParameter() 方法,并且傳遞你要獲取的輸入?yún)?shù)的id給該方法。
String value1 = req.getParameter('param1');String value1 = req.getParameter('param2');
一旦獲取了參數(shù)值,它們就會在需要時被處理。對客戶端的響應(yīng)和我們上面部分討論的一樣。我們使用 HttpServletResponse 對象給客戶端發(fā)送響應(yīng)。
request和response處理的基本使用可以是這樣的:
@Overrideprotected 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 contentout.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(); }}
為了發(fā)送內(nèi)容給客戶端,你需要使用從 HttpServletResponse 里獲取的 PrintWriter 對象。任何寫到這個對象的內(nèi)容都會被寫進outputstream里,并會把內(nèi)容發(fā)送回給客戶端。
監(jiān)聽Servlet容器事件有時候,知道應(yīng)用服務(wù)器容器(the application server container)里某些事件發(fā)生的時間是很有用的。這個概念適用于很多情況,但它通常用在開啟應(yīng)用時初始化應(yīng)用或者關(guān)閉應(yīng)用時清理應(yīng)用。可以在應(yīng)用里 注冊一個監(jiān)聽器(listener)來顯示應(yīng)用什么時候開啟或者關(guān)閉。因此,通過監(jiān)聽這些事件,Servlet可以在一些事件發(fā)生時執(zhí)行相應(yīng)的動作。
為了創(chuàng)建一個基于容器事件執(zhí)行動作的監(jiān)聽器,你必須創(chuàng)建一個實現(xiàn) ServletContextListener 接口的類。這個類必須實現(xiàn)的方法有 contextInitialized() 和 contextDestroyed()。這兩個方法都需要 ServletContextEvent 作為參數(shù),并且在每次初始化或者關(guān)閉Servlet容器時都會被自動調(diào)用。
為了在容器注冊監(jiān)聽器,你可以使用下面其中一個方法:
1) 利用 @WebListener 注解。
2) 在web.xml應(yīng)用部署文件里注冊監(jiān)聽器。
3) 使用 ServletContext 里定義的 addListener() 方法
請注意,ServletContextListener 不是Servlet API里唯一的監(jiān)聽器。這里還有一些其他的監(jiān)聽器,比如
javax.servlet.ServletRequestListenerjavax.servlet.ServletRequestAttrbiteListenerjavax.servlet.ServletContextListenerjavax.servlet.ServletContextAttributeListenerjavax.servlet.HttpSessionListenerjavax.servlet.HttpSessionAttributeListener
根據(jù)你要監(jiān)聽的事件選擇他們來實現(xiàn)你的監(jiān)聽器類。比如,每當創(chuàng)建或銷毀一個用戶session時,HttpSessionListener 就會發(fā)出通知。
傳遞Servlet初始化參數(shù)現(xiàn)在的大多數(shù)應(yīng)用都需要設(shè)置一些在應(yīng)用/控制器(controller)啟動時可以傳遞的配置參數(shù)(configuration parameters)。Servlet同樣可以接受初始化參數(shù),并在處理第一個請求前來使用它們來構(gòu)建配置參數(shù)。
顯然,你也可以在Servlet里硬編碼配置值。但是這樣做的話,在Servlet發(fā)生改動時你需要再次重新編譯整個應(yīng)用。沒有人喜歡這樣做。
<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>
設(shè)置后,你就可以在代碼里調(diào)用 getServletConfig.getInitializationParameter() 并傳遞參數(shù)名給該方法來使用參數(shù)。就像下面展示的代碼一樣:
String value = getServletConfig().getInitParameter('name');為特定的URL請求添加Servlet過濾器
Web過濾器在給定的URL被訪問時對請求進行預處理并調(diào)用相應(yīng)的功能是很有用的。相 比于直接調(diào)用給定URL請求的Servlet,包含相同URL模式的過濾器(filter)會在Servlet調(diào)用前被調(diào)用。這在很多情況下是很有用的。 或許最大的用處就是執(zhí)行日志,驗證或者其他不需要與用戶交互的后臺服務(wù)。
過濾器必須要實現(xiàn) javax.servlet.Filter 接口。這個接口包含了init(),descriptor()和doFilter()這些方法。init()和destroy()方法會被容器調(diào)用。 doFilter()方法用來在過濾器類里實現(xiàn)邏輯任務(wù)。如果你想把過濾器組成過濾鏈(chain filter)或者存在多匹配給定URL模式的個過濾器,它們就會根據(jù)web.xml里的配置順序被調(diào)用。
為了在web.xml里配置過濾器,需要使用<filter>和<filter-mapping> XML元素以及相關(guān)的子元素標簽。
<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注解。
使用Servlet下載二進制文件幾乎所有的web應(yīng)用都必須有下載文件的功能。為了下載一個文件,Servlet必須提供一個和下載文件類型匹配的響應(yīng)類型。同樣,必須在響應(yīng)頭里指出該響應(yīng)包含附件。就像下面的代碼。
String mimeType = context.getMimeType( fileToDownload );response.setContentType( mimeType != null ? mimeType : 'text/plain' );response.setHeader( 'Content-Disposition', 'attachment; filename='' + fileToDownload + ''' );
通過調(diào)用 ServletContext.getResourceAsStream() 方法并傳遞文件路徑給該方法,你可以獲取要下載的文件(文件保存在文件系統(tǒng))的引用。這個方法會返回一個輸入流(InputStream)對 象,我們可以用這個對象來讀取文件內(nèi)容。當讀取文件時,我們創(chuàng)建一個字節(jié)緩存區(qū)(byte buffer)從文件里獲取數(shù)據(jù)塊。最后的工作就是讀取文件內(nèi)容并且把它們復制到輸出流。我們使用while循環(huán)來完成文件的讀取,這個循環(huán)直到讀取了文 件的所有內(nèi)容才會跳出循環(huán)。我們使用循環(huán)來讀進數(shù)據(jù)塊并把它寫進輸出流。把所有數(shù)據(jù)寫進輸出流后,ServletOutputStream 對象的flush方法就會被調(diào)用并且清空內(nèi)容和釋放資源。
看這段簡單的代碼:
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(); }使用RequestDispatcher.forward()轉(zhuǎn)發(fā)請求到另一個Servlet
有時候,你的應(yīng)用需要把一個Servlet要處理的請求轉(zhuǎn)讓給另外的Servlet來處理并完成任務(wù)。而且,轉(zhuǎn)讓請求時不能重定向客戶端的URL。即瀏覽器地址欄上的URL不會改變。
在 ServletContext 里已經(jīng)內(nèi)置了實現(xiàn)上面需求的方法。所以,當你獲取了 ServletContext 的引用,你就可以簡單地調(diào)用getRequestDispatcher() 方法去獲取用來轉(zhuǎn)發(fā)請求的 RequestDispatcher 對象。當調(diào)用 getRequestDispatcher() 方法時,需要傳遞包含servlet名的字符串,這個Servlet就是你用來處理轉(zhuǎn)讓請求的Servlet。獲取 RequestDispatcher 對象后,通過傳遞 HttpServletRequest 和HttpServletResponse 對象給它來調(diào)用轉(zhuǎn)發(fā)方法。轉(zhuǎn)發(fā)方法負責對請求進行轉(zhuǎn)發(fā)。
RequestDispatcher rd = servletContext.getRequestDispatcher('/NextServlet');rd.forward(request, response);使用HttpServletResponse.sendRedirect()重定向請求到另一個Servlet
盡管有時候,你不想在Servlet發(fā)送重定向時通知用戶,就像我們在上面那段看到的一樣。但是在某些情況下,我們確實想要通知用戶。當應(yīng)用內(nèi)的特定URL被訪問時,你想把瀏覽器的URL重定向到另外一個。
要實現(xiàn)這種功能,你需要調(diào)用 HttpServletResponse 對象的sendRedirect()方法。
httpServletResponse.sendRedirect('/anotherURL');
這個簡單的重定向,與servlet鏈(servlet chaining)相反,不需要傳遞目標地址的HttpRequest對象。
使用Servlet讀寫Cookie很多應(yīng)用都想在客戶端機器里保存用戶當前的瀏覽歷史。目的是當用戶再次使用應(yīng)用時,他能夠從上次離開的地方開始瀏覽。為了實現(xiàn)這個需求,通常使用cookies。你可以把它看作是保存在客戶端機器里的鍵值對基本數(shù)據(jù)。當使用瀏覽器打開應(yīng)用時,應(yīng)用可以對這些數(shù)據(jù)進行讀寫。
為了創(chuàng)建cookie,需要實例化一個新的 javax.servlet.http.Cookie 對象并且為它分配名稱和值。實例化cookie后,可以設(shè)置屬性來配置cookie。在這個例子里,我們使用 setMaxAge() 和 setHttpOnly() 方法來設(shè)置cookie的生命周期和防范客戶端腳本。
從Servlet3.0 API開始,已經(jīng)可以把cookie標記為HTTP only了。這使cookie可以防范客戶端腳本的攻擊,使cookie更加安全。
Cookie cookie = new Cookie('sessionId','123456789');cookie.setHttpOnly(true);cookie.setMaxAge(-30);response.addCookie(cookie);
這里的response是傳遞給doXXX()方法的 HttpServletResponse 實例。
要讀取服務(wù)端的cookie信息,使用下面代碼:
Cookie[] cookies = request.getCookies();for(Cookie cookie : cookies){ //cookie.getName(); //cookie.getValue()}
這就是這篇教程里關(guān)于Servlet技術(shù)的全部內(nèi)容了。歡迎評論和回饋。
學習快樂!!
相關(guān)文章:
