什麼是屬性?
在Servlet&JSP的那些事兒(五)中,我們瞭解了ServletContext監聽者如何在擷取上下文初始化參數後建立一個對象,以及如何將對象作為一個屬性儲存區到ServletContext,以便web應用的其他部分能得到該對象。屬性就是一個對象,設定(或稱之為綁定)到另外3個servlet
API對象中-SservletContext、HttpServletRequest(或HttpServletResponse)、HttpSession。可以把它簡單的認為是一個映射執行個體對象中的名/值對(名是一個String,值是一個Object)。在實際中,我們不關心它如何?,只關心屬性的範圍。通俗的講,就是看它能活多久。
屬性和參數的區別
屬性不是參數,它們之間的區別如表1。
|
屬性 |
參數 |
類型 |
應用/上下文 請求 會話(註:沒有特定於servlet的屬性,只需要使用執行個體變數。) |
應用/上下文初始化參數 請求參數 servlet初始化參數(註:沒有會話參數的說法。) |
設定方法 |
setAttribute(String name, Object value) |
不能設定應用和servlet初始化參數,他們都在web.xml中設定。 |
傳回型別 |
Object |
String |
擷取方法 |
getAttribute(String name)(註:不要忘了強制轉換,因為傳回型別是Object) |
getInitParameter(String name) |
表1 屬性和參數的區別
三個範圍:上下文、請求和會話
內容屬性,web應用中的每一部分都能訪問。會話屬性,只有能訪問特定HttpSession的部分才能訪問。而請求屬性,只有能訪問特定ServletRequest的部分才能訪問。具體的範圍見表2。
|
可訪問性 (誰能看到) |
範圍 (能存活多久) |
適用性 |
Context(上下文) (不是安全執行緒的) |
web應用的所有部分,包括servlet,jsp,servlet-contextlistener等。 |
ServletContext的生命週期,這意味著所部屬應用的生命期。如果伺服器或應用關閉,上下文則撤銷,其屬性也相應撤銷。 |
你希望整個應用共用的資源,包括資料庫連接、JNDJ尋找名、email地址等。 |
HttpSession(會話) (不是安全執行緒的) |
訪問這個特定會話的所有servlet或jsp。注意,會話從一個客戶請求擴充到可能跨同一個客戶的多個請求,這些請求可能到達多個servlet。 |
會話的生命期。會話可以通過編程撤銷,也可能只是因為逾時而撤銷。 |
與客戶會話有關的資源和資料,而不只是與一個請求相關的資源。它要與客戶完成一個持續的會話。購物車是一個典型例子。 |
Request(請求) (是安全執行緒的) |
應用中能直接存取請求對象的所有部分。基本上說,這意味著接收所轉寄請求的jsp和servlet(使用RequestDispatcher),另外還有與請求相關的監聽者。 |
請求的生命週期。這說明會持續到servlet的service()方法結束。也即,線程/棧處理這個請求的生命週期。 |
將模型資訊從控制器傳遞到視圖,或者傳遞特定於客戶請求的任何資料。 |
表2 屬性範圍
上下文範圍不是安全的
因為應用中的每一部分都能訪問內容屬性,而這意味著可能有多個servlet。多個servlet則說明你可能有多個線程,因為請求時並發處理的,每個請求在一個單獨的線程中處理。例如如下語句:
getServletContext().setAttribute("boo","12");getServletContext().setAttribute("foo","24");out.println(getServletContext().getAttribute("boo"));out.println(getServletContext().getAttribute("foo"));
在一個複雜項目中啟動並執行時候,第一次啟動並執行時候,可能會輸出預期結果12 24,第二次輸出可能會是12,,36了。可能的原因是:servlet A設定了內容屬性"boo"的值為"12","foo"的值為"24"。之後線程B,即servlet B成為活動線程(此時線程A回到可運行但未啟動並執行狀態),並設定內容屬性"foo"的值為"36"(值"24"丟了)。這時線程A又重新成為活動線程,它取得"foo"的值,結果就輸出了36。
如何讓內容屬性做到安全執行緒?
既然內容屬性不是安全執行緒的,那麼我們該如何改進呢?有人說使用同步服務的方法。也即對doGet方法做如下修改:
public synchronized void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {response.setContentType("text/html;charset=utf-8"); PrintWriter out=response.getWriter();getServletContext().setAttribute("boo","12");getServletContext().setAttribute("foo","24");out.println(getServletContext().getAttribute("boo"));out.println(getServletContext().getAttribute("foo"));}
即給doGet方法添加synchronized關鍵字。但此方法意味著servlet中一次只能運行一個線程,但是並不能阻止其他servlet或jsp訪問這個屬性。同步服務方法會防止同一個servlet中的其他線程訪問內容屬性,但是不能阻止另外一個servlet的訪問。所以,不是需要對servlet加鎖,而是需要對上下文加鎖,也即,應該做如下修改:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {response.setContentType("text/html;charset=utf-8"); PrintWriter out=response.getWriter();synchronized(getServletContext()) {getServletContext().setAttribute("boo","12");getServletContext().setAttribute("foo","24");out.println(getServletContext().getAttribute("boo"));out.println(getServletContext().getAttribute("foo"));}}
保護內容屬性的一般做法是對內容物件本身同步,如果訪問內容相關的每一個人都必須先得到內容物件的鎖,就能保證一次只有一個線程可以得到或設定內容屬性。但是,只有當處理這些內容屬性的其他代碼也對ServletContext同步時,這種做法才起作用。如果一段代碼沒有請求鎖,那麼這段代碼還是能自由訪問內容屬性。
會話屬性是安全的嗎?
貌似還沒有介紹會話呢。會話會在下一篇討論。其實會話就是一個對象,用於維護與一個使用者的工作階段狀態。對於同一個使用者的多個請求,會話會跨這些請求持久儲存。先來看看只有一個使用者的情況,如果只有一個使用者,而且一個客戶一次只有一個請求,這就說明會話是安全執行緒的嗎?會有一個使用者一次有多個請求的情況發生嗎?元芳,你怎麼看?
使用者有可能開啟一個新的瀏覽器視窗,在這種情況下,容器還是為一個使用者使用同樣的會話,儘管它來自另一個瀏覽器執行個體。所以,會話屬性不是安全執行緒的。那麼如何保護這些會話屬性不受多線程的破壞呢?呵呵,只需要像內容屬性一樣,對所有訪問這些會話屬性的代碼都進行同步即可。那麼,該對誰同步呢?必須對HttpSession同步!代碼如下
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {response.setContentType("text/html;charset=utf-8"); PrintWriter out=response.getWriter();HttpSession session = request.getSession();synchronized(session) {session .setAttribute("boo","12");session .setAttribute("foo","24");out.println(getServletContext().getAttribute("boo"));out.println(getServletContext().getAttribute("foo"));}}
為了防止同步產生大量的開銷,所以,一定要在最短的時間內完成同步目標,要讓同步塊儘可能小。
轉載請註明出處:http://blog.csdn.net/iAm333