2011年8月7日 星期日

《深入淺出 Servlets & JSP》筆記(未完)

Ch1 簡介與架構

一.HTML與HTTP

1.HTML:告訴瀏覽器如何將內容展示給使用者

2.HTTP:客戶端與伺服器進行溝通的協定

3.伺服器藉由HTTP將HTML送給客戶端


二.HTTP通訊協定

1.HTTP依賴TCP/IP來取得完整的請求與回應
﹡TCP負責將檔案完整地從一個網路節點傳送到另一個網路節點,即使檔案在傳輸過程中被分割成幾個部份,TCP還是會確保送達目的地的檔案能夠保持元有的完整性
﹡IP封包從一台主機順利傳送/繞送至另一台目標主機的底層協定

2.HTTP交談(conversation)的結構是一序列簡單的請求/回應;瀏覽器發出請求,伺服器產生回應
﹡請求串流的關鍵元素:
(1)HTTP方法(要被執行的動作(action))
(2)要存取的頁面(URL)
(3)表單參數(類似方法的引數)
﹡回應串流的關鍵元素:
(1)狀態碼(status code,代表請求是否成功)
(2)內容形式(content-type,如文字、圖片、HTML等等)
(3)內容(實際的HTML、圖片等等)

3.HTTP回應能夠包含HTML,並且將標頭(header)資訊增加到回應(回應來自伺服器)裡的任何內容之前,HTML瀏覽器則利用標頭資訊來協助處理HTML頁面。你可以將HTML內容想像成貼附在HTTP回應裡的資料
﹡標頭資訊告訴瀏覽器使用的是什麼協定、請求是否成功、以及主體裡頭包含什麼類型的內容


三.HTTP方法

1.GET:要求伺服器取出資源,再將它傳回給客戶端。重點是,GET是用來從伺服器取得資源

2.POST:向伺服器請求資源,同時將表單資料傳送給伺服器。它會將表單資料包含在請求的主體(body)中

3.GET所能夠傳送的字元數量相當有限(取決於伺服器)。假如使用者在搜尋欄位中輸入一長串資料,GET可能就無法有效運作

4.藉由GET所傳送的資料會附加在URL後面,出現在瀏覽器的URL欄位中。因此,最好不要用GET來傳送向密碼之類的敏感資料

5.GET請求可以被設成書籤,POST則無法這麼做

6.POST請求被設計來「讓瀏覽器對伺服器提出複雜的請求」。例如,當使用者填寫完一個大表單時,應用程式可能會想要將該表單上的所有資料增加到資料庫中。要被送回伺服器的資料就是所謂的「訊息主體」(message body)或「酬載」(payload),而且其資料量可以相當大

7.POST包含主體,這就是與GET主要的差異。GET與POST皆可傳送參數,然而,使用GET時,參數資料受到當相當的限制,只能夠塞進請求列(request line)

8.GET是用來取得資訊的,純粹擷取資料,但重點是-你不是要用它來改變伺服器的狀態(或內容)。POST則是要用來傳遞資料給伺服器處理,這可能只是用來判斷要回傳什麼資料的簡單查詢參數,就跟GET一樣,不過當你想到POST時,就要想到:更新-利用POST訊息主體的資料來改變伺服器的狀態(或內容)


四.靜態網頁

1.Web伺服器善於提供靜態網頁

2.靜態網頁存在於目錄結構中,伺服器會把它找出來,並且原封不動地回傳給客戶端,每個客戶端看到的都是相同的內容


五.當Web伺服器不敷所需時

1.當你需要即時(just-in-time)網頁(在請求進來之前尚未存在,由應用程式根據請求而動態建立的網頁),或者必須能夠在伺服器上寫入/儲存資料(這裡指的是把資料寫入檔案或資料庫),單靠Web伺服器是無法完成的

2.即時網頁在請求進來之前尚未存在,它是輔助應用程式根據請求而重新產生的HTML網頁

3.請求進來,輔助應用程式「撰寫」HTML,Web伺服器會將它取回,並且回傳給客戶端

4.Web伺服器應用程式只能夠提供靜態網頁,然而,Web伺服器能夠與之相互溝通的輔助應用程式,則可以即時建立動態網頁

5.當Web伺服器看見要交給輔助應用程式的請求時,Web伺服器將假設收到的參數是要給輔助應用程式使用的,因此,Web伺服器會把參數移交給輔助應用程式,讓它去處理資料,並且產生要回傳給客戶端的回應



Ch2 高階架構

一.何謂Container?

1.Servlet本身並沒有main(),它們是由另一個稱為Container(容器)的Java應用程式所控制

2.當你的Web伺服器應用程式收到一個針對Servlet的請求時(相對於簡單的靜態HTML網頁),伺服器並不是將請求直接交由Servlet處理,而是把它交給該Servlet所屬的Container,Container再把HTTP請求物件以及HTTP回應物件交給Servlet。另外,負責呼叫Servlet方法(如doPost()或doGet())的也是Container


二.Container能帶給你什麼?

1.溝通支援:Container提供一種簡單的機制,讓Servlet跟你的Web伺服器交談。Container知道伺服器跟它之間的通訊協定,因此,Servlet不必擔心Web伺服器與你的Web應用程式之間要使用哪些API進行溝通。你只需要擔心那些放在Servlet裡頭的商業邏輯即可

2.生命週期管理:Container控制著Servlet的生與死。它負責載入類別、實例化、及初始化Servlet、呼叫Servlet方法、並且適時地讓Servlet實例能夠被垃圾桶回收機制清除掉

3.多執行緒支援:Container收到每一個針對Servlet的請求時,會自動建立一個Java執行緒來處理它,當Servlet為此請求執行完service()方法時,該執行緒就會結束(亦即死亡)

4.宣告式的權限管理:透過Container,你可以使用XML格式的部署描述檔來組態(或修改)權限管理機制,而不必將權限管理寫死在Servlet(或任何其他)類別中
﹡部屬描述檔(DD)提供一種宣告式的機制,讓你客製化Web應用程式,而無需碰觸任何原始碼
﹡DD的好處:
(1)減少碰觸未經測試的原始碼
(2)就算沒有原始碼,也能夠微調Web應用程式的功能
(3)調整Web應用程式,搭配不同資源(如資料庫),無需重新編譯或測試任何程式碼
(4)讓你比較容易維護動態的安全防護(如存取控制清單(ACL)以及權限管理角色等)
(5)讓非程式人員能夠調整及部署你的Web應用程式

5.支援JSP頁面


三.MVC設計模式

1.Model-View-Controller(MVC)將商業邏輯從Servlet中搬出來,再把它放進「Model」-可重複利用的簡單Java類別(POJ)。Model(模型)是商業資料與操作該資料之方法(或規格)的結合

2.MODEL(模型):包含真實的商業邏輯與狀態,換言之,MODEL知道如何取得及更新狀態規則。它也是系統中唯一會跟資料庫交談的部份

3.VIEW(視圖):負責處理表現層。View從Controller那取得Model的狀態(然而並不直接;Controller將Model的資料放在View可以找到的某個地方),另外,View也是「取得要送給Controller之使用者輸入」的元件

4.CONTROLLER(控制器):從HTTP請求中取得使用者輸入,並且判斷它對Model的意義為何。告訴Model更新它自己,並且準備好新的Model狀態供View(如JSP)存取



Ch3 請求與回應

一.Servlet的生命週期

1.只有一種主要狀態-已初始化(initialized)。假如Servlet不在「已初始化」狀態,那麼,它不是正在初始化(執行它的建構式或init()方法),就是正在被銷毀中(執行它的destroy()方法),否則,就是它根本不存在

2.在Servlet生命週期中,init()方法只會被呼叫一次,並且必須在Container能夠呼叫service()之前完成

3.Container會呼叫Servlet的destroy方法,讓它在被殺掉之前有機會進行一些清理工作。就像init(),destroy()方法也只會被呼叫一次

4.Servlet真正的工作是在處理請求,那正是Servlet的生命意義所在


二.三個重要的生命週期方法

1.init()
(1)何時被呼叫:Container會在Servlet實例被建立之後,能夠服務任何客戶端請求之前,呼叫這個Servlet實例的init()方法
(2)目的:讓你有機會在處理客戶端請求之前,初始化Servlet
(3)會被覆寫嗎:有可能。如果你擁有一些進行初始化工作的程式碼(像是取得資料庫連接或者向別的物件註冊 ),就可以覆寫Servlet裡的init()方法

2.service()
(1)何時被呼叫:當第一個客戶端請求進來時,Container啟動一個新執行緒或者從執行緒緩衝區中配置一個執行緒,再透過它呼叫Servlet的service()方法
(2)目的:service()方法根據請求的HTTP方法(POST、GET等),呼叫對應的Servlet方法(doPost()、doGet()等)
(3)會被覆寫嗎:不可能。你不應去覆寫service()方法,而是應該覆寫doPost()和/或doGet()方法,讓HTTPServlet類別所實作的service()方法負責呼叫正確的Servlet方法

3.doPost和/或doGet()
(1)何時被呼叫:service()方法根據請求的HTTP方法(POST、GET等),呼叫對應的Servlet方法(doPost()、doGet()等)
(2)目的:這是你程式碼的起點,你希望Web應用程式處理哪些事情,全都由這些方法來負責
(3)會被覆寫嗎:至少覆寫一個(doPost()或doGet())。不管覆寫何者,都是在告訴Container你要支援什麼HTTP方法。舉例來說,假如你沒有覆寫doPost()方法,就是讓Container知道此Servlet不支援HTTP POST請求


三.Servlet執行緒

1.Container會呼叫Servlet的init()方法,但若是沒有覆寫init()方法,從GenericServlet繼承來的init()方法就會被執行。接著,當請求進來時,Container會啟動或配置執行緒來呼叫service()方法,此方法通常未被覆寫,因此,從HttpServlet繼承而來的service()方法便會執行,然後,HttpServlet的service()方法便會呼叫你所覆寫的doPost()或doGet()方法。因此,你的doPost()或doGet()方法每次都會執行在分離的執行緒中

2.service()方法總是在它自己的堆疊(Stack)中被呼叫,亦即再獨立的執行緒中

3.一個請求配置一個執行緒。每一個客戶端的請求皆由獨立的執行緒來處理,Container會配置一組新的請求與回應物件

4.特定Servlet只會有一個實例,但可以利用不同的執行緒來處理每一個請求


四.Servlet初始化

1.Servlet必須在完成載入與初始化後,才能開始服務它的第一個客戶端請求

2.init()總是在第一次呼叫service()之前就已經完成

3.Servlet從「不存在」變成「已初始化」(表示準備好服務客戶的請求),是從建構式開始的,然而,建構式只能夠產生物件,而不是Servlet。要成為真正的Servlet,該物件必須被賦予「Servlet特性」(Servletness)

4.你可能擁有一些要為Servlet進行初始化的程式碼,像是取得Web應用程式的組態資訊,或者查詢指向Web應用程式其他部份的參考等,假如你在Servlet生命週期中過早執行初始化的程式碼,便會導致失敗,因此,切勿再Servlet的建構式中進行任何處理。一切等到init()方法再說


五.ServletConfig與ServletContext

1.ServletConfig物件:
(1)每個Servlet都有一個ServletConfig物件
(2)利用它將部署期間的資訊傳給Servlet(例如,資料庫或Enterprise Bean的查詢名稱),也就是那些你不想要寫死在Servlet裡的資訊(Servlet初始參數)
(3)利用它存取ServletContext
(4)在部署描述檔(DD)中設定參數

2.ServletContext物件:
(1)每個Web應用程式只有一個ServletContext
(2)利用它存取Web應用程式的參數(也是被設定在部署描述檔中)
(3)利用它作為應用程式的公佈欄,你可以將應用程式其他部份能夠存取的訊息(稱為屬性,attribute)張貼在這裡
(4)利用它取得伺服器資訊,包括Container的名稱與版本,以及支援的API版本


六.請求與回應

1.Container負責實作HttpServletRequest與HttpServletResponse介面,而API並未包含這些實作類別,因為它們會留給Container供應商(vendor)去實作。因此你不需要擔心實作的問題,你只需要注意實作類別使否提供HttpServletRequest與HttpServletResponse介面所指定的完整類功能。換言之,你所要知道的一切是:Container提供給你的請求物件上有哪些方法可以呼叫。至於實作這些方法的實際類別並不重要,因為你只會透過介面的型別來參照請求與回應物件

2.介面也可以有自己的繼承樹(inheritance tree)。當介面繼承另一個介面時,就表示任何想要實作此類別的介面,必須實作定義在這兩個介面(此介面與其父介面)中的所有方法


七.HTTP方法

1.GET:透過請求URL向伺服器要求資源或檔案

2.POST:要求伺服器接受請求所隨附的主體(body)資訊,並將它交給請求URL所指定的元件。POST就像加強版的GET,除了GET原有的功能外,還能處理請求所隨附的額外資訊

3.HEAD:只要求GET原本會回傳的標頭部份,所以,它就像GET,只是在回應中不附帶主體部份。這會提供你有關請求URL的相關資訊,但不取回實際的內容

4.TRACE:要求關於請求訊息的回執(loopback),讓客戶端能夠看到伺服器究竟接收到什麼樣的請求訊息,可作為測試或偵測之用

5.PUT:表明要將附帶的主體資訊「放」到請求URL上

6.DELETE:表明要「刪除」請求URL上的資源或檔案

7.OPTIONS:要求請求URL上的資源列出可回應的HTTP方法

8.CONNECT:表明要將請求連接轉換成透通的TCP/IP通道


八.等冪請求

1.HTTP GET只針對資料擷取,而不應去改變伺服器上的任何資料,所以,GET是等冪的(idempotent),可以被執行多次,而不會產生不良的影響

2.POST不是等冪-POST主體所提交的資料可能註定就是一筆不可逆的交易,因此,務必小心處理你的doPost()功能,以處理客戶端勿送兩次相同請求的狀況


九.表單與HTTP

1.假如你的HTML表單使用的是GET,你就必須在你的Servlet類別裡準備doGet()。表單的預設HTTP方法為GET


十.表單參數

1.單一參數也可以具有多個值,那表示你必須使用getParameterValues()方法,它會傳回字串陣列,而不是使用getParameter()方法,那只會傳回字串


十一.HttpServletRequest物件

1.客戶端的平台與瀏覽器資訊:String client = request.getHeader(“User-Agent”)

2.與請求相關連的Cookie:Cookie[] cookies = request.getCookies()

3.與客戶端相關連的Session(期程):HttpSession = request.getSession()

4.請求的HTTP方法:String theMethod = request.getMethod()

5.請求的輸入串流:InputStream input = request.getInputStream()

6.大多數的時候,你會想要利用request.getInputStream()方法,來取得主體中的參數值,但那些參數值的資料量有可能會很大。你也可能建立專門處理「由電腦驅動之請求」的Servlet,在當中,請求的主體會包含需要被處理的文字或二進制內容。在諸如此類的情況下,你可以使用getReader()或getInputStream()方法,所得到的串流只包含HTTP請求主體,而不包含請求標頭

7.getRemotePort()方法表示「取得客戶端的連接埠」,也就是說,「客戶端請求是從客戶端的哪個連接埠送出的?」

8.getServerPort()方法表示「該請求原本要被送往哪個連接埠?」,而getLocalPort()方法則表示「該請求最後到達哪個連接埠?」,兩者並不相同,因為雖然請求總是被送往單一連接埠(伺服器正在偵聽的位置),但伺服器會為每個執行緒找到一個不同的本地連接埠,讓Web應用程式可以同時服務多個客戶端


十二.內容型式

1.內容型式是HTTP回應中必要的HTTP標頭

2.setContentType()方法的整體觀念:將內容寫入回應輸出串之後,就不能夠再利用這個方法改變內容型式,也就是說,你不能先設定某種內容型式,寫入一些內容,接著再改變成另一種內容型式,再寫入不同型式的內容。記得,瀏覽器一次只能夠處理一種內容型式的回應

3.最佳的實務方法是:在呼叫提供輸出串流的方法(getWrite()或getOutputStream())之前,先呼叫setContentType()方法。這樣才能確保不會讓內容型式與輸出串流產生衝突

4.Servlet只是在讀檔案的二進制資料,接著將這些二進制資料寫入輸出串流。在我們讀取那些位元組時,Container並不清楚我們在做什麼,它只知道我們正在讀取某一種型式的資料,並將一些資料寫到回應中


十三.PrintWrite與ServletOutputStream

1.PrintWrite:針對字元的輸出串流型態。專門將文字資料寫到字元串流。寫入時必須使用println()
﹡PrintWrite writer = response.getWriter();
writer.println(“x”);

2.OutputStream:針對位元組的輸出串流型態。寫入任何非字元資料。寫入時必須使用write()
﹡ServletOutputStream out = response.getOutputStream();
out.write(x);

3.getWriter()方法讓你進行字元I/O,以便將HTML(或其他字元資料  )寫入串流

4.你可以利用回應物件來設定標頭、設定錯誤訊息、以及增加Cookie


十四.設定回應標頭

1.setIntHeader():以整數值取代取代既有標頭之值的簡便方法,或者在回應中增加新的標頭與值

2.若回應中還沒有這個標頭(方法的第一個參數),setHeader()與addHeader()皆可在回應中增加全新的標頭與值。兩者的差異在於「標頭已存在」的情況下:setHeader()會覆寫既有的值;addHeader()則會添加額外的值


十五.Servlet重導

1.Servlet重導需要瀏覽器來完成。重導(redirect)讓Servlet完全撇清關係。在Servlet判定它無法處理這項工作時,Servlet直接呼叫sendRedirect()方法

2.在sendRedirect()也可以使用相對URL

3.你不可以在寫出回應之後再呼叫sendRedirect()

4.sendRedirect()接受的是String,而不是URL型別的物件


十六.請求分派

1.重導是讓客戶端去處理;請求分派(request dispatch)則是由伺服器上的其他元件來處理。謹記:重導=客戶端;請求分派=伺服器

2.重導就像客戶(瀏覽器)打電話給其他人代為處理一樣。使用者會在瀏覽器上看到新的URL

3.請求分派就像請同事去服務客戶,最後由那位同事負責回應客戶,但只要有人處理,客戶並不在乎由誰負責。使用者並不知道這實際上是由JSP負責處理的,因為瀏覽器上的URL並未變動



Ch4 屬性與偵聽器

一.初始參數

1.在Container將Servlet初始化時,會為這個Servlet準備一個獨一無二的ServletConfig物件

2.Container從DD中「讀取」這個Servlet的初始參數,並且把這些參數交給ServletConfig物件,然後在執行Servlet的init()方法時,再把ServletConfig傳給這個Servlet


二.Servlet初始參數

1.Servlet初始參數只會被讀取一次-在Container初始化Servlet時

2.在Container產生Servlet時,它會讀取DD,並且為ServletConfig建立一些名/值對(name/value pair),之後,Container就不會再去讀取DD中的那些初始參數。一旦參數在ServletConfig中就定位後,它們就不會再被讀取,直到你重新部署Servlet


三.Servlet初始參數與Context初始參數

1.Context初始參數是整個Web應用程式都能存取的,而不是針對單一Servlet。這表示應用程式裡的任何Servlet與JSP都自動能夠存取到Context初始參數,這樣的話,就不必費心在DD中為每個Servlet作設定,而且,當參數值有變時,也只需要在一個地方作修改

2.一個Servlet會有一個ServletConfig;整個Web應用程式只會有一個ServletContext

3.整個整個Web應用程式只會有一個ServletContext,Web應用程式會共用它,然而,Web應用程式裡的每個Servlet都有自己的ServletConfig。在Web應用程式被部署時,Container會產生ServletContext,並且讓它可以被Web應用程式裡的每個Servlet與JSP存取到

4.ServletContext初始參數是用<context-param>來設定的(在<servlet>元素之外);而ServletConfig初始參數則是利用DD中個別<servlet>宣告裡的<init-param>來設定的

5.Web應用程式初始化:
﹡Container讀取DD並且為每個<context-param>建立字串型態的名值對
﹡Container建立ServletContext的新實例
﹡Container將指向Context初始參數的每個名值對的參考交給ServletContext
﹡Web應用程式裡所有部署的每個Servlet與JSP都能存取到同一個ServletContext

6.Context參數最常見的用途,就是存放資料庫的查詢名稱(lookup name),你會想要讓應用程式的各個部份都能夠存取到正確的名稱,而且當它有變動時,也只需要修改一個地方

7.請將初始參數視為部署時期的常數。你可以在執行期間取得它們,但不能設定它們


四.ServletContextListener

1.ServletContextListener這個類別能夠偵聽ServletContext生命週期中的兩個重要事件-初始化(或建立)與銷毀。這使我們能夠:
(1)從Context初始化時得到通知(應用程式正在被部署)
<1>從ServletContext取得Context參數
<2>使用初始參數中的查詢名稱來建立資料庫連結
<3>將資料庫連接儲存成屬性,讓Web應用程式的各個部份都能夠存取到它
(2)在Context被銷毀時得到通知(應用程式被移除或關閉)
<1>關閉資料庫連接

2.要偵聽ServletContext事件,必須先撰寫實作ServletContextListener介面的偵聽器類別,將它放到WEB-INF/classes目錄中,並且透過在部署描述檔中設定<listener>元素,讓Container能夠找到這個偵聽器


五.8個偵聽器

1.使用情節:你想知道Web應用程式Context中的屬性是否被新增、移除、或取代
偵聽器介面:ServletContextAttributeListener
事件類型:ServletContextAttributeEvent

2.使用情節:你想知道目前線上有多少個使用者,換句話說,你想找出正在活動的Session
偵聽器介面:HttpSessionListener
事件類型:HttpSessionEvent

3.使用情節:每當有請求進來時,你都想要知道,以便加以記錄
偵聽器介面:ServletRequestListener
事件類型:ServletRequestEvent

4.使用情節:你想知道Request屬性何時被新增、移除、或取代
偵聽器介面:ServletRequestAttributeListener
事件類型:ServletRequestAttributeEvent

5.使用情節:你有一個屬性類別(其值會被設定成某個屬性的值),當此類別的物件被繫結到Session或者從Session中移除時,你想讓它們收到通知
偵聽器介面:HttpSessionBindingListener
事件類型:HttpSessionBindingEvent

6.使用情節:你想知道Session屬性何時被新增、移除、或取代
偵聽器介面:HttpSessionAttributeListener
事件類型:HttpSessionBindingEvent

7.使用情節:你想知道Context是否被建立或銷毀
偵聽器介面:ServletContextListener
事件類型:ServletContextEvent

8.使用情節:你有一個屬性類別,當此類別之物件所繫結的Session被移到另一個JVM,或者從另一個JVM移過來時,你想讓它們收到通知
偵聽器介面:HttpSessionActivationListener
事件類型:HttpSessionEvent


六.屬性vs.參數

1.屬性
類型:Application/Context;Request;Session
設定的方法:setAttribute(String name,Object value)
回傳型別:Object
取值的方法:ServletContext.getAttribute(String name);HttpSession.getAttribute(String name);ServletRequest.getAttribute(String name)
﹡別忘了做強制轉型,因為回傳型別是Object

2.參數
類型:Application/Context初始參數;請求參數;Servlet初始參數
設定的方法:你不能在程式碼中設定Context和Servlet初始參數
回傳型別:String
取值的方法:ServletContext.getInitParameter(String name);ServletConfig.getInitParameter(String name);ServletRequest.getInitParameter(String name)


七.3種屬性作用域

1.Context屬性:
可存取性:Web應用程式的任何部份
作用域:ServletContex的生命週期,那表示已部署之Web應用程式的存活期間,假如伺服器或應用程式結束,Context就會被銷毀(它的屬性也是)
適當用途:你想要讓整個應用程式共享某些資源
﹡非執行緒安全

2.Session屬性:
可存取性:能夠存取特定Session的任何Servlet與JSP。記住,Session的範圍跨越來自相同客戶端的多個請求,並且可以針對不同的Servlet
作用域:Session生命週期。Session可以透過程式被銷毀,或者只是單純地發生逾期(time-out)
適當用途:與此客戶端之Session關聯的資料與資源,而不是只是單一的請求。有些工作需要與客戶端持續對話,如購物車
﹡非執行緒安全

3.Request屬性:
可存取性:應用程式的任何部份都能夠直接存取Request物件,一般是指利用RequestDispatcher將請求轉交給它的Servlet或JSP還有與Request相關連的偵聽器
作用域:Request生命週期,那表示直到Servlet的service()方法完成,也就是說,處理此請求之執行緒(堆疊)的存活期間
適當用途:將Model資訊從Controller傳給View...,或者其他特定於單一客戶端請求的資料
﹡執行緒安全


八.Context屬性與執行緒安全

1.保護Context屬性的典型做法,就是直接鎖定(lock/synchronize)Context物件(ServletContext)本身

2.若每一個存取Context的程式碼,都必須先取得Context的「鎖」,這樣的話,就能保證同時間下只有一個執行緒能夠存取Context屬性。不過還有一個但書:只有在操作相同Context屬性的所有其他程式碼皆有鎖定Context物件時才有用。如果某段程式碼沒有做鎖定的動作,那麼該段程式碼還是可以自由地存取Context屬性


九.Session屬性與執行緒安全

1.保護Session屬性的做法,就是「鎖定HttpSession物件」的同步化區塊(synchronized block)

2.不要去鎖定「未存取受保護資料的程式碼」,同步化區塊愈小愈好

3.取得鎖定,進入同步化區塊,存取需要的屬性,接著儘快離開,那樣的話,鎖定就能夠儘快被釋放,好讓其他執行緒能夠執行這段程式碼

4.SingleThreadModel(或稱STM)被設計來保護實例變數,用來確保Servlet一次只處理一個請求。這個介面沒有任何方法,但若Servlet實作此介面,即可保證不會有兩個執行緒同時執行Servlet的服務方法


十.Request屬性是執行緒安全

1.只有Request屬性與區域變數(包含方法參數)是執行緒安全

2.實例變數並非執行緒安全

3.假如你需要執行緒安全的狀態,就不要使用實例變數,因為Servlet的所有執行緒都能夠「染指」它的實例變數

4.當你想要將請求的一部分或全部交由應用程式的其他元件來接管時,Request屬性就蠻適用的


十一.RequestDispatcher

1.RequestDispatcher只有兩個方法:forward()和include()。這兩個方法都接受請求和回應物件作為參數(接管請求的其他元件需要這兩個物件以便完成工作)。使用forward()方式時,你的意思是在說:「就這樣了,我不會再對請求與回應做任何處理」;使用include()方式時,你的意思是在說:「我想要請求某人幫忙處理請求和/或回應,不過等到它們完成處理後,我想要自己對請求與回應做最後的處理(然而,我也可能決定再繼續使用include()或forward())。實際的工作上幾乎不會用到include()

2.取得RequestDispatcher的方式有兩種:透過Request或者Context。無論使用哪一種,你都必須指明要將請求轉交給哪個Web元件。也就是說,哪個Servlet或JSP將接管請求的處理



Ch5 Session管理

一.對話

1.HttpSession物件可以針對特定客戶端保存跨多個請求的對話狀態。換句話說,它會針對特定客戶端將整個Session保存在記憶體中。因此,我們可以利用它來存放從客戶端所有請求中取得的一切資訊


二.識別客戶端

1.HTTP通訊協定使用的是不具狀態的連結(stateless connection)。客戶端的瀏覽器連結到伺服器,送出請求,取得回應,然後關閉連結。換句話說,連線只存在於一次的請求/回應

2.由於HTTP的連結無法持續存在,Container就無法辨別產生第二個請求的客戶端與先前發出請求的客戶端是否為同一個。對Container來說,每一個請求皆來自新的客戶端


三.交換資訊

1.Container必須將Session ID作為回應的一部分交給客戶端,而客戶端必須將Session ID作為請求的一部分送回伺服器端。最常見的方法是透過Cookie交換這項資訊

2.在回應中送出Session Cookie與從請求中取得Session ID,都是使用同樣指令:HttpSession session = request.getSession(); 所有與Cookie有關的工作都會在背後自動運作

3.假如你的目標就是要建立Session,而它的新舊確實會影響後續的處理邏輯,那麼就使用沒有參數的getSession()方法,接著再利用HttpSession的isNew()方法判斷Session是新的還是舊的

4.當你想要得到Session時,無論新建的或是既存的都可以,那麼沒有參數的getSession()就是一個方便的方法。當你知道你不想要新的Session時,或者到執行期間才能夠決定要不要建立新的Session時,接受布林值的方法就很有用(如getSession(false))


四.URL重寫

1.URL重寫是當客戶端不接受Cookie時的備案。它將位在Cookie裡的Session ID,放到進入此應用程式裡的每一個URL尾端

2.URL重寫只會在Cookie失效,以及你有呼叫回應物件的encodeURL()方法時,才會啟動

3.當Container看見getSession()呼叫,而且沒有從客戶端請求中取得Session ID,就知道它必須為這個客戶端產生新的Session。在這個時間點,Container並不清楚Cookie是否能夠運作,所以,在第一次將回應傳給客戶端時,Cookie與URL重寫兩種方式都會做

4.如果Container從客戶端請求中找不到Session ID,Container根本就不會知道這是來自客戶端的再一次請求。Container沒有辦法知道它在上一次已經嘗試過傳送Cookie給這個客戶端,而且沒有成功。記住,唯一能讓Container辨別出它之前已經見過這個客戶端的方法,就是檢查客戶端請求中有沒有Session。所以,當Container看到你在呼叫request.getSession(),並發現必須為這個客戶端產生新的Session,Container就會同時使用”Set-Cookie”標頭,以及將Session ID附加在URL後面的方式

5.在某些使用情節中,你可能需要將請求重導到不同的URL,但你還是想要維持這個Session,在此情況下,你可以使用此URL編碼:response.encodeRedirectURL(“/test.do”)

6.使用URL重寫的唯一方式,就是Session所涉及的全部網頁都必須是動態產生的,因為Session ID在執行期間才會存在,很明顯地,你無法在程式碼中把Session寫死。所以,如果你的應用程式需要仰仗Session才能夠有效運作,你就必須使用URL重寫作為備用策略;因為你需要URL重寫,你就必須在回應的HTML裡動態地產生URL,那表示你必須在執行期間處理HTML

7.URL重寫是自動進行的,但只會發生在你做過編碼的URL上。你必須針對所有URL執行回應物件所提供的兩個方法之一:encodeURL()或encodeRedirectURL(),接著Container就會負責完成其他工作

8.在靜態網頁中沒有辦法自動完成URL重寫,所以,假如應用程式必須倚賴Session才能有效運作,你就必須使用動態產生的網頁


五.HttpSession的方法

1.getCreationTime:
工作:傳回Session第一次被建立的時間
用途:計算Session到目前為止的存活時間

2.getLastAccessedTime:
工作:回傳Container最後一次收到與此Session ID相關連之請求的時間
用途:找出客戶最後一次存取此Session的時間。你可以利用它來判斷客戶端是否已經離開很長一段時間,或是直接利用invalidate()方法,讓這個Session無效

3.setMaxInactiveInterval():
工作:指定你希望針對此Session的不同請求之間的最長時間間隔(以秒為單位)
用途:如果在指定時間內沒有收到針對此Session的請求,就讓這個Session被銷毀

4.getMaxInactiveInterval():
工作:取得你希望針對此Session的不同請求之間的最長時間間隔(以秒為單位)
用途:找出這個Session可以多久不活動(inactive)但還是活著(alive)。你可以利用它來判斷一個不活動的客戶端在Session被結束之前還有多少存活時間

5.invalidate():
工作:結束這個Session。包括解除目前存放在此Session裡的所有屬性
用途:當你發現客戶端已經不活動,或者你知道Session已經結束時,可以利用這個方法來殺掉Session


六.Custom Cookie

1.你可以使用Cookie在伺服器與客戶端之間交換字串型態的名值對。

2.伺服器將Cookie傳給客戶端,而客戶端在後續的請求中將它送回伺服器

3.當客戶端的瀏覽器關閉時,Session Cookie便會消失,但是透過自訂Cookie,你可以讓Cookie在瀏覽器關閉後仍繼續存在於客戶端

4.建立新的Cookie:Cookie cookie = new Cookie(“username”, name);

5.設定Cookie會存活在客戶端多久:cookie.setMaxAge(60); //以秒為單位
﹡若值設定為「-1」,則表示在瀏覽器關閉後就讓Cookie隨之消失

6.將Cookie傳送給客戶端:response.addCookie(cookie);

7.從客戶端請求取得Cookie:你只能夠以陣列的方式取得Cookie,接著你必須以迴圈繞行陣列,找出你要的那一個Cookie

8.當你將標頭增加到回應時,你傳遞的是字串型態的名值對;當你將Cookie增加到回應時,你傳遞的是Cookie物件。你在Cookie的建構式中設定Cookie的名稱與值


七.Session遷移

1.每次相同客戶端產生請求時,該請求最後會到達相同Servlet的不同實例

2.只有HttpSession物件(及其屬性)會從一個VM遷移到另一個VM

3.每個VM都有一個ServletContext,每個VM的每個Servlet實例都有一個ServletConfig,但是,應用程式裡的每個Session ID只會對應到一個HttpSession物件,不管這個應用程式分散在幾個VM上執行

4.除了HttpSession物件之外,所有東西都會被複製到另個VM上

5.Session在任意時刻下只會存活在一個地方。同一個Session ID絕對不會同時出現在兩個VM中

6.雖然Web應用程式的其他部份被複製到每個節點/VM上,但Session卻是以一個物件的型式遊走於不同VM之間

7.Container必須遷移可序列化的屬性,但是,Container不一定得使用序列化機制作為遷移HttpSession物件的手段。你只要確認你的屬性類別是可序列化的,那你就不用再去擔心它;但若它們不是可序列化的,那就讓你的屬性類別去實作HttpSessionActivationListener介面,並且利用sessionDidActivate()與sessionWillPassivate()這兩個回呼方法來處理這個問題



Ch6 使用JSP

一.JSP的角色

1.Container將你在JSP中所撰寫的內容轉譯(translate)成Servlet類別的原始碼檔案(.java),再將它編譯(compile)成Serlvet類別(.class),自此之後,它就完全成為Servlet,而且執行的方式就跟你自己撰寫與編譯的Servlet一模一樣。也就是說,Container同樣會載入這個Servlet類別,完成實例化與初始化的過程,針對每一個請求產生一個獨立的執行緒,並且呼叫Servlet的service()方法


二.page指令的import屬性

1.所謂指令(directive)就是讓你能夠在JSP頁面「轉譯」期間,將特殊指示傳達給Container的一種機制


三.JSP宣告

1.JSP宣告(declaration)是為了要在轉譯出來的Servlet類別中宣告成員,成員包含變數與方法。換句話說,任何在<%!與%>標籤中的內容,都會被增加到類別之中、服務方法之外的地方。這也表示,你可以用它來宣告靜態或非靜態的變數與方法


四.轉譯出來的Servlet

1.Container對你的JSP做了什麼:
(1)先查看JSP指令,取得轉譯期間所需要的資訊
(2)建立HttpServlet
(3)若有內含import屬性的page指令,就會將這些import陳述式加到類別檔案的頂端,就接在pagkage陳述式之後
(4)若有JSP宣告,便會將它們寫到類別檔案中,通常就在類別宣告之後,服務方法之前
(5)建立服務方法。這個服務方法的實際名稱是_jspService(),它會被Servlet超類別所覆寫的service()方法呼叫,並接收HttpServletRequest與HttpServletResponse作為參數。在建立這個方法的過程中,Container會宣告及初始化所有的隱含物件(implicit object)
(6)將JSP頁面中所出現的簡單HTML、Scriptlet、和運算式結合起來,搬到服務方法中,並且為所有的東西格式化,再將它們送給PrintWriter產生回應輸出


五.有效及無效的運算式

1.你可以在JSP中加入兩種不同的註解:
(1)<!--HTML註解-->:Container直接將這種註解送給客戶端,客戶端瀏覽器會直接將它解譯為註解
(2)<%--JSP註解-->

2.如果你想讓註解作為HTML回應的一部分送給客戶端,就使用HTML註解


六.使用pageContext來取得及設定屬性

1.設定Page作用域的屬性:
<% pageContext.setAttribute(“foo”, one); %>

2.取得Page作用域的屬性:
<%= pageContext.getAttribute(“foo”) %>

3.使用pageContext設定Session作用域的屬性:
<% pageContext.setAttribute(“foo”, two, PageContext.SESSION_SCOPE); %>

4.使用pageContext取得Session作用域的屬性:
<%= pageContext.getAttribute(“foo”, PageContext.SESSION_SCOPE) %>
(等同於:<%= session.getAttribute(“foo”) %>)

5.使用pageContext取得Application作用域的屬性:
<%= pageContext.getAttribute(“mail”, PageContext.APPLICATION_SCOPE) %>
(在JSP中,等同於:<%= application.getAttribute(“mail”) %>)

6.當你不知道屬性所在的作用域時,可利用pageContext來尋找屬性:
<%=pageContext.findAttribute(“foo”) %>


七.三個指令

1.page指令:用來定義頁面特定(page-specific)的特性

2.taglib指令:用來定義可供JSP使用的標籤程式庫

3.include指令:定義在轉譯期間要被增加到當前頁面上的文字或程式碼,讓你能夠建立一些可重複利用的區塊,接著這些區塊就可以被增加到每個頁面上,無需在每JSP中重複撰寫那些程式碼



Ch7 不含Script的JSP

沒有留言:

張貼留言