Servlet是獨立於平台和協議的服務器端的java應用程序,處理請求的信息並將其發送到客戶端。
Servlet的客戶端可以提出請求並動態獲得響應。
Servlet動態生成web頁面,擔當浏覽器或其他客戶端發出的請求與HTTP服務器上的數據庫或應用程序之間的中間層。
對於所有客戶端請求,都只需要創建一次Servlet實例,因此,節省了大量內存。
Part 1 Servlet運行原理
web服務器收到一個http請求後,判斷請求內容,若是靜態頁面數據,自行處理,若為動態數據,交給Servlet容器,Servlet容器找到相應Servlet實例處理;處理結果交給web服務器,再轉交給客戶端。
針對同一個Servlet,Servlet容器會在第一次收到HTTP請求時建立一個Servlet實例,然後啟動一個線程,第二次收到http請求後,Servlet容器無需創建相同Servlet ,僅開啟第二個線程來處理請求。
----->多線程的方式有效提高執行效率,降低服務器負擔。
Part 2 Servlet 優勢
Servlet具有優良的跨平台性;
可移植性良好:java語言編寫,Servlet API標准完善,企業編寫的Servlet程序可輕松移植到其他服務器中;
執行效率高:Servlet請求到來時激活Servlet,處理完成後等待新請求;新請求產生新線程,而不是進程;
使用方便:可輕松處理HTML表單數據,並讀取和設置HTTP頭,處理cookie,跟蹤會話;
Part 3 基礎知識
HttpServlet作為一個抽象類來創建用戶自己的Http Servlet. Http Servlet類擴展了GenericServlet類。HttpServlet類的子類必須至少重寫doGet()和doPost()方法其中之一。HttpServlet類提供doGet()方法處理GET請求,doPost()處理POST請求:
doGet() :通過GenericServlet 類的service()方法來調用此方法;
doPost(): 通過GenericServlet 類的service()方法來調用此方法;
Part 4 Servlet生命周期
Servlet生命周期由Servlet容器控制,該容器創建Servlet的實例。Servlet的生命周期是指Servlet實例在創建後,響應客戶端請求直至銷毀的全過程;
其創建取決於Servlet的首次調用。
Servlet生命周期定義了它如何被加載,初始化,以及它如何接受請求,響應請求,提供服務;
代碼中,Servlet生命周期由接口javax.servlet.Servlet定義,所有的Servlet必須直接或間接的實現該接口,這樣才能在Servlet容器中運行。
Servlet接口定義了生命周期三種方法:
1 init():創建實例後進行初始化。實現ServletConfig接口的對象作為參數進行傳遞。在初始化過程中,Servlet容器使用ServletConfig接口信息(如Servlet的初始化參數的名稱,初始化參數的值,以及Servlet實例名稱等)傳遞給Servlet。
public void init(ServletConfig config)throws ServletException
2 service():響應客戶端發出的請求。service()方法接受ServletRequest接口和Servletresponse接口的對象來處理請求和發送響應;
public void service(ServleRequest request,ServletResponse response) throws ServletException,IOException
3 destroy():如果不再有需要請求的對象,則釋放Servlet對象;
public void destroy()
Servlet生命周期各階段:
1.實例化:Servlet容器創建Servlet類的實例對象;
2.初始化:容器調用Servlet的init()方法,通常會申請資源以便後續使用;
3.服務:由容器使用以響應客戶對Servlet的請求;
4.破壞:在釋放Servlet實例前調用,通常會釋放資源;
5.不可用:釋放內存中的容器;
[要實例化一個Servlet,容器必須先找到Servlet類,加載servlet類並創建Servlet對象,然後通過調用Servlet的init()方法來初始化Servlet。ServletConfig接口對象作為參數傳給init()方法,該接口對象為Servlet提供對ServletContext接口的訪問。Servlet容器使用ServletContext接口與Servlet容器進行通信;
如果初始化Servlet失敗,則拋出UnavailableException或ServletException異常,並再次嘗試對Servlet進行實例化和初始化。然後將ServletRequest和ServletResponse接口作為參數傳遞給service方法,該方法將處理請求並返回響應。
如果響應請求時發生異常,則容器通過調用destroy()方法卸載實例]
Part 5 Servlet API
在javax.servlet和javax.servlet.http包中的各類和接口如下:
(1)ServletInputStream類
從java.io.InputStream類擴展而來的抽象類,該類創建的對象用於讀取客戶端請求中的二進制數據,而該類的readLine()方法用於每次讀取一行數據;
該方法將從給定偏移處開始的每字節讀取到數組中,直到該方法遇到換行符或者讀取完一定量的字節數量,該方法返回一個整數來指定實際讀取的字節數,到達流尾時返回-1
public int readLine(byte b[],int offset,int length)throws java.io.IOException
//b為用於存儲讀取的數據的字節數組;offset指定方法開始讀取字符的起始位置;length讀取的最大字節數
(2)ServletOutputStream類
該類創建的對象用於將二進制數據從服務器發送到客戶端。具體實現的方法如下:
+ print():將字符串寫入客戶端。如果發生任何輸入或輸出異常,則方法print()會引發IOException異常,print()方法接受參數,如char,float,double,int,long,String。
public void print(String str)throws java.io.IOException
//str為發送到客戶端的字符串
+ println():將字符串寫入客戶端,緊跟後面輸出回車。如果發生任何I/O異常,則會引發IOException
(3)ServletRequest接口
使用ServletRequest接口創建對象,用於使客戶端請求信息對Servlet可用。創建的對象作為參數傳遞至Servlet的service()。
該類實現方法如下:
+ getInputStream():返回客戶端請求中的二進制數據,並將其保存在getInputStream對象中
public ServletInputStream getInputStream()throws IOException
+ getParameter():用於獲取請求消息一起發送的附加信息-----請求參數
public String getParameter(String str)
+ getContentLength():返回客戶端發送的請求的實際長度,如果長度未知,則返回-1
public int getContentLength()
+ getServerName():返回請求發至的服務器名稱
public String getServerName()
(4)ServletResponse接口
使用該接口創建的對象用於向客戶端提供響應。創建的對象作為參數傳遞至Servlet的service()方法中。該接口實現的方法如下:
+ getOutpouStream():返回一個ServletOutputStream對象,它被用來發送對客戶端的響應
public ServletOutputStream getOutputStream()throws IOException
+ getWriter():返回將字符文本發送到客戶端的PrintWriter對象
public PrintWriter getWriter()throws IOException
+ setContentLength():允許用戶設置將作為響應放的數據的長度
public void setContentLength(int length)
+ getBufferSize():檢索實際的以響應客戶端的緩存區大小。若沒有使用緩沖區則返回0
public int getBufferSize()
+ setBufferSize():設置將發送到客戶端的數據的緩沖區的大小
public void setBufferSize(int size)
(5)HttpServletRequest接口
容器在調用Servlet的doGet()或doPost()方法時,會創建一個HttpServletRequest接口的實例和一個HttpServletResponse接口的實例,作為參數傳遞給doGet和doPost()方法。該接口代表客戶請求,它提供了多種獲取請求數據的方法,具體繼承層次如圖:
(6)HttpServletResponse接口
該接口代表返回給客戶端的響應;具體繼承層次如圖:
(7)ServletConfig接口
在初始化過程中,Servlet容器使用ServletConfig接口的對象作為參數來傳遞Servlet的配置信息。方法如下:
+ getServletName():用於獲取Servlet實例名稱
public String getServletName()
+ getInitParameter():檢索初始化參數的值,如果參數不存在,則getInitParameter()方法返回null
public String getInitParameter(String name)
//name為初始化參數的名稱字符串
+ getServletContext():返回Servlet用來與其容器交互的ServletContext對象
public ServletContext getServletContext()
(8)ServletContext接口
該接口定義了一組方法,Servlet使用這些方法與容器進行交互並獲取信息(如讀寫文件等)
+ getContext():返回允許Servlet訪問服務器上下文的ServletContext類對象
public ServletContext getServletContext(String uripath) //uripath是Web容器上的另外一個Web程序的上下文路徑名稱字符串
+ getMimeType():返回文件的MIME類型。MIME定義了一種協議,允許用戶通過Internet交換非ASCII消息。不同的MIME類型分為"text/html"和"image/gif"
public String getMimeType(String file)
//file是文件名稱
+ getResource():返回與路徑名相對應的資源的URL
public java.net.URL getResource(String path) throws MalFormedURLexception
//path是資源對應的路徑名稱字符串
(9)獲取請求中的數據
在Servlet類的請求處理方法中(如doGet(),doPost()方法),要想獲得客戶端請求中提交的數據,需要使用HttpServletRequest提供的以下方法:
public String getParameter(String name) //獲取指定名稱的參數值 public String[] getParameterValues(String name) //獲取指定名稱參數的所有值數組。它適用於一個參數名對應多個值的情況,如頁面表單中的復選框,多選列表提交的值 public java.util.Enumeration getParameterNames() //返回一個包含請求消息中的所有參數名的Enumeration對象。通過遍歷這個Enumeration對象,就可以獲取請求消息中所有的參數名 public java.util.Map getParameterMap() //返回一個保留了請求消息中所有參數名和值的Map對象。Map對象的key是字符串類型的參數名,value是這個參數所對應的Object類型的值數組。
[提示:在此說明如何處理客戶端提交給服務器的數據的亂碼問題
若客戶端以POST方式提交請求,請求消息主體中的參數數據是按HTML頁面中指定的編碼方式進行編碼的,在Servlet類的請求處理方法中需要先調用 HttpServletRequest接口的setCharacterEncoding(String enc)方法對請求消息主體中的數據按參數指定的編碼方式進行編碼,然後才能使用上述介紹的方法正 確獲得參數值
若客戶端使用GET方法請求,上述方法無效,此時,最好的解決方案是在URL中不使用中文等非ASCII字符]
(9)重定向和請求分配
1.重定向:HttpServletRequest接口提供的sendRedirect()方法用於生成302響應碼和Location響應頭從而通知客戶端去重新訪問Location響應頭中指定的URL
public void sendRedirect(String location)throws IOException
//其中location參數指定了重定向的URL,它可以使用絕對或相對URL,Servlet會進行轉化
2.請求分派:RequestDispatcher接口,(分派器),定義了下面兩個方法
public void forward(ServletRequest request,ServletResponse response)throws ServletException,IOException; //forward()用於將請求轉發到RequestDispatcher實例封裝的資源 public void include(ServletRequest request,ServletResponse response)throws ServletException,IOException; //include()用於將RequestDispatcher實例封裝的資源作為當前響應內容的一部分包含進來
RequestDispatcher dispatcher=request.getRequestDispatcher("servlet1"); //參數為當前servlet名稱 dispatcher.forward(request,response);
[重定向與請求分派的區別:
請求分派只能將請求轉發到同Web應用中的其他組件;而重定向也可以發送到其他Web;
請求分派過程結束後,浏覽器內網址不改變;重定向完成後浏覽器內原網址變為重定向目標網址;
]
(11)利用請求域屬性傳遞對象數據
HttpServletRequest接口中提供了幾個方法來操作請求實例中的存儲對象
public void setAttribute(String name,Object obj) //將對象存儲進HttpServletRequest實例中 public Object getAttribute(String name) //檢索存儲在HttpServletRequest實例中的對象 public Enumeration getAttributeNames() //返回HttpServletRequest實例中所有屬性名的Enumeration對象 public void removeAtribute(String name) //從指定HttpServletRequest實例中刪除指定名稱的屬性
這種存儲在HttpServletRequest中的對象稱之為請求域屬性,屬於同一請求過程的多個處理模塊之間可以通過請求域屬性來傳遞對象數據;
//Servlet1.java package test import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.*; public class Servlet1 extends HttpServlet{ public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{ this.doPost(request,response); } public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{ String str="Job done"; request.setAttribute("string",str); RequestDispatcher dispatcher=request.getRequestDispatcher("Servlet2"); dispatcher.forward(request,response); } }
//Servlet2.java package test import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.*; public class Servlet2 extends HttpServlet{ public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{ this.doPost(request,response); } public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{ response.setContentType("text/html;charset=utf-8"); PrintWriter out=response.getWriter(); out.println("<html>"); out.println("<head><title>test</title><head>"); out.println("<body>"); //獲取名為"string"的請求域屬性的值 String str=(String)request.getAttribute("string"); out.println(str); out.println("</body>"); out.println("</html>"); } }
Part 6 Servlet的線程安全問題
Servlet默認是多線程模式執行的,當有多個用戶同時並發請求一個Servlet時,容器將啟動多個線程調用相應的請求處理方法,此時,請求處理方法中的局部變量是安全的,但對於成員變量和共享數據是不安全的,因此這多個線程有可能同時都操作這些數據,這是需要同步處理。所以,編寫代碼時需要非常細致的考慮多線程的安全性問題。多數人編寫Servlet時不注意多線程問題,導致少量用戶訪問時沒有問題,但並發量大則出現莫名其妙的問題。
解決:
1.使用synchronized:使用synchonized關鍵字同步操作成員變量和共享數據的代碼,就可以防止出現線程安全性問題,但這也意味著線程需要排隊處理。因此,在使用同步語句時要盡可能縮小同步代碼范圍,不能直接在請求處理方法(如doGet(),doPost()方法)使用同步,這樣會嚴重影響效率。
2.盡量少使用成員變量和共享數據:對於集合,使用Vector代替非線程安全的ArrayList,使用Hashtable代替HashMap;不能在Servlet內創建自己的線程,導致復雜化。
Part 7 Servlet 過濾器
過濾器技術(Filter)是Servlet2.3以上版本新增的功能,2.5對其進一步增強。
Filter是一個程序,它先於相關的Servlet或JSP頁面運行在服務器上。過濾器可附加到多個或一個Servlet或JSP上,並且可以檢查進入這些資源的請求消息。
Filter在Request到達Servlet之前預處理,也可以在離開Servlet時處理Response;其實本質就是一個Servlet Chaining.
一個Filter必須實現javax.servlet.Filter接口並定義三個方法:
public void init(FilterConfig config) //Filter實例化後進行初始化的回調方法 public void doFilter(ServletRequest req,ServletResponse res,FilterChain chain) //處理過濾業務的方法 public void destroy() //Filter釋放時回調的方法
每一個Filter從doFilter()方法中得到當前的request和response。在該方法內,可進行任何針對request和response的操作。Filter調用chain.doFilter()方法把控制權交給下一個Filter。
實例:Filter處理中文亂碼問題:
import java.io.IOException; import javax.servlet.*; public class CharacterEncodingFilter implements Filter{ private FilterConfig config; public void destroy(){ } public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws IOException,ServletException{ String encoding=config.getInitParameter("encoding"); if(null!=encoding&&!"".equals(encoding)){ request.setCharacterEncoding(encoding); } chain.doFilter(request,response); //將修改過的請求和響應傳遞給下一個過濾器或者Servlet } //Filter初始化時的回調方法 //FilterConfig接口實例中封裝了這個Filter的初始化參數 public void init(FilterConfig config){ this.config=config; } }
在web.xml中注冊
...... <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>(路徑).characterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> .........
此處/*是過濾所有頁面請求,如果需要,可以指定頁面 或文件夾下的所有頁面。
過濾鏈:為web添加按順序執行的多個Filter,進入時執行順序A-B,離開時執行順序B-A
排列方法是在web.xml中,按順序注冊即可;
如下示例:(在上一示例基礎上添加一個可以自動壓縮響應輸出流的Filter,提高傳輸效率)
...... <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>(路徑).characterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>Compression Filter</filter-name> <filter-class>compressionFilters.CompressionFilter</filter-class> <!--設置緩沖區大小--> <init-param> <param-name>compressionThreshold</param-name> <param-value>512</param-value> </init-param> <!--調試級別--> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> </filter> <filter-mapping> <filter-name>Compression Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> .........
即可實現Filter Chain。
Part 8 Servlet 監聽器
監聽器可使應用對某些事件做出反應:
Servlet2.3以上版本提供了一下幾個接口:
+ ServletContextListener:應用上下文生命周期監聽器,用於監聽Web應用的啟動和銷毀事件;
+ ServletContextAttributeListener:應用上下文屬性事件監聽器,用於監聽Web應用上下文中的屬性改變事件;
+ ServletRequestListener:請求生命周期監聽器,監聽請求的創建和銷毀;
+ ServletRequestAttributeListener:請求屬性事件監聽器,用於監聽請求中的屬性改變事件;
+ HttpSessionListener:會話生命周期監聽器,用於監聽會話的創建和銷毀;
+ HttpSessionActivationListener:會話激活和鈍化事件監聽器;
+ HttpSessionAtributeListener:會話屬性事件監聽器;
+ HttpSessionBindingListener:會話值綁定事件監聽器;
(正在更新)