科技新知

Servlet 3.0技術文件

作者/毛運韡   [發表日期:2016/4/5]

前言

在JAVA EE Web開發中,JSP 與 Servlet可以只使用單獨一項技術來解決動態網頁呈現的需求,但最好的方式是取兩者的長處,JSP 是網頁設計人員導向的,而 Servlet 是程式設計人員導向的,釐清它們之間的職責可以讓兩個不同專長的團隊彼此合作,並降低相互間的牽制作用。本篇將針對Servlet來探討。

Web基本結構組態

一個Web應用程式基本上會由以下項目所組成:

  • 靜態資源
  • Servlet/JSP
  • 自定義類別
  • 工具類別
  • 部署描述檔web.xml、設定資訊Annotation

Web應用程式存在一個特殊的/WEB-INF目錄,此目錄中存在的 資源項目不會被列入應用程式根目錄中可直接存取的項目,也就是說,客戶端(例如瀏覽器)不可以直接請求/WEB-INF中的資源(直接在網址上指明存取 /WEB-INF)。/WEB-INF中的資源項目有著一定的名稱與結構。

  • /WEB-INF/web.xml 是部署描述檔
  • /WEB-INF/classes 用來放置應用程式所用到的自定義類別(.class),必須包括套件Package結構
  • /WEB-INF/lib 用來放置應用程式所用到的JAR檔

Web應用程式所用到的JAR檔案,當中可以放置Servlet、JSP、自定義類別、工具類別、部署描述檔等,應用程式的類別載入器可以從JAR中載入對應的資源。可以在JAR檔案的/META-INF/resources目 錄中放置靜態資源或JSP等,例如若在/META-INF/resources中放個index.html,若請求的URL中包括 /xxx/index.html,但實際上/ xxx/根目錄底下不存在index.html,則JAR中的/META- INF/resources/index.html會被使用。

如果你要到用到某個類別,則Web應用程式會到/WEB-INF/classes中試著載入類別,若無,再試著從/WEB-INF/lib的JAR檔案中尋找類別檔案(若還沒有找到,則會到容器實作本身存放類別或JAR的目錄中尋找,但位置視實作廠商而有所不同)。

客戶端不可以直接請求/WEB-INF中的資源,但你可以透過程式面的控管,讓程式來取得/WEB-INF中的資源,像是使用ServletContext的getResource() 與getResourceAsStream(),或是透過RequestDispatcher請求調派。

如果對Web應用程式的URL最後是以/結尾,而且確實存在該目錄,則Web容器必須傳回該目錄下的歡迎頁面, 你可以在部署描述檔web.xml中包括以下的定義,指出可用的歡迎頁面名稱為何,Web容器會依序看看是否有對應的檔案存在,如果有則傳回給客戶端:

如果找不到以上的檔案,則會嘗試至JAR的/META-INF/resources中尋找已置放的資源頁面。如果URL最後是以/結尾,但不存在該目錄,則會使用預設Servlet(如果有定義的話,參考URL 模式中的說明)。

Servlet 3.0

Servlet 3.0提供了新的方法(Annotation標註)來管理應用程式的組態設定,在Servlet 3.0中,要為Web應用程式進行Servlet、過濾器、傾聽器的組態設定有幾種方式:

1.透過@WebServlet、@WebFilter、@WebListener等標註的設定

2.透過web.xml的設定,如果web.xml中所設定的Servlet、過濾器等類別中已包括標註,則以web.xml中的設定為主

3.在容器初始化時,透過ServletContext的addServlet()、addFilter()、addListener()等方法來設定,這可以實作在ServletContextListener的contextInitialized()中,或者是ServletContainerInitializer的onStartup()中實作

在一個JAR檔案中如果有個META-INF/services/javax.servlet.ServletContainerInitializer檔案,該檔案是個文件檔案,當中所撰寫的類別名稱若實作了ServletContainerInitializer介面,則在容器啟動時,將會載入這些類別並執行其onStartup()方法。

在Servlet 3.0中,可以使用標註來設定Servlet的相關資訊,實際上,Web容器並不僅讀取/WEB-INF/classes中的Servlet標註訊息,如果一個JAR檔案中有使用標註的Servlet,Web容器也可以讀取標註資訊、載入類別並註冊為Servlet進行服務。但是如果要決定Servlet、過濾器、傾聽器的載入順序,則一定要在web.xml中設定,載入的順序就是其在web.xml中宣告的順序(Servlet的話,就是load-on-startup值一樣時的情況)。實際上,在Servlet 3.0中支援對Servlet、過濾器、傾聽器抽換性,可以藉由在JAR中包括符合要求的文件,就可以達到置換JAR檔案即抽換Servlet、過濾器、傾聽器的目的,實際上,在Servlet 3.0中,一個JAR檔案確實可以當作一個Web應用程式的部份模組,就如上面所說明的,事實上不僅是Servlet,傾聽器、過濾器等,也可以在撰寫、 定義標註完畢後,封裝在JAR檔案中,視需要放置至Web應用程式的/WEB-INF/lib之中,以增減Web應用程式的功能性。

如果你將web.xml中的metadata-complete屬性設定為true(預設是false),則表示web.xml中已完成Web應用程式的相關定義,部署時將不會掃描標註與web-fragment.xml中的定義,如果有與也會被忽略。例如:

如果web-fragment.xml中指定的類別可以在web應用程式的/WEB-INF/classes中找到,就會使用該類別,要注意的是,如果該類別本身有標註,而web-fragment.xml又有定義該類別為Servlet,則此時會有兩個Servlet實例。如果將的metadata-complete屬性設定為true(預設是false),則就只會處理自己JAR檔案中的標註資訊。

AsyncContext

每個請求來到Web容器,Web容器會為其分配一條執行緒來專門負責該請求,直到回應完成前,該執行緒都不會被釋放回容器。執行緒會耗用系統資源,若有些請求需要長時間處理(例如長時間運算、等待某個資源),就會長時間佔用執行緒,若這類的請求很多,許多執行緒都被長時間佔用,對於系統就會是個效能負擔,甚至造成應用程式的效能瓶頸。基本上一些需長時間處理的請求,若可以讓這類請求先釋放容器分配給該請求的執行緒,讓容器可以有機會將執行緒資源分配給其它的請求,可以減輕系統負擔。原先釋放了容器所分配執行緒的請求,其回應將被延後,直到處理完成(例如長時間運算完成、所需資源已獲得)再行對客戶端的回應。

在Servlet 3.0中,在ServletRequest上提供了startAsync()方法:

這兩個方法都會傳回AsyncContext介面的實作物件,前者會直接利用原有的請求與回應物件來建立AsyncContext,後者可以讓你傳入自己建立的請求、回應包裹物件。在呼叫了startAsync()方法取得AsyncContext物件之後,這次的回應會被延後,並釋放容器所分配的執行緒。可以透過AsyncContext的getRequest()、getResponse()方法取得請求、回應物件,此次對客戶端的回應將暫緩至呼叫AsyncContext的complete()方法或dispatch()為止,前者表示回應完成,後者表示將回應調派給指定的URL。

若要能呼叫ServletRequest的startAsync使用AsyncContext,你的Servlet必須能支援非同步處理,如果使用@WebServlet來標註,則可以設定其asyncSupported為true。例如:

如果使用web.xml設定過濾器,則可以設定標籤為true:

對於進來的請求,Servlet會取得其AsyncContext,並釋放容器所分配的執行緒,回應被延後,對於這些被延後回應的請求,將其排入一個執行緒池(Thread pool),執行緒池的執行緒數量是固定的,讓這些必須長時間處理的請求,在這些有限數量的執行緒中完成,而不用每次請求都佔用容器所分配的執行緒。

參考資料

1.語言技術:Servlet/JSP
2.Servlet教學