1. 程式人生 > >servlet3.1規範翻譯:第8章 註解和可插拔性

servlet3.1規範翻譯:第8章 註解和可插拔性

第8章 註解和可插拔性

本章討論在web應用中使用的Servlet 3.0規範定義的註解和啟用框架和庫的可插拔性增強。

8.1 註解和可插拔性

在web應用中,使用註解的類僅當它們位於WEB-INF/classes目錄中,或它們被打包到位於應用的WEB-INF/lib中的jar檔案中時它們的註解才將被處理。

Web應用部署描述符的web-app元素包含一個新的“metadata-complete”屬性。“metadata-complete”屬性定義了web描述符是否是完整的,或是否應該在部署時檢查jar包中的類檔案和web fragments。如果“metadata-complete”設定為“true”,部署工具必須忽略存在於應用的類檔案中的所有servlet註解和web fragments。如果metadata-complete屬性沒有指定或設定為“false”,部署工具必須檢查應用的類檔案的註解,並掃描web fragments。

相容Servlet3.0的web容器必須支援下面的註解。

8.1.1 @WebServlet

該註解用於在Web應用中定義Servlet元件。該註解在一個類上指定幷包含關於宣告的Servlet的元資料。必須指定註解的urlPatterns或value屬性。所有其他屬性是可選的預設設定(請參考javadoc獲取更多細節)。當註解上唯一屬性是url模式時推薦使用value,當使用了其他屬性時使用urlPatterns屬性。在同一註解上同時使用value 和 urlPatterns屬性是非法的。如果沒有指定Servlet名字則預設是全限定類名。被註解的sevlet必須指定至少一個url模式進行部署。如果同一個Servlet類以不同的名字宣告在部署描述符中,必須例項化一個新的Servlet例項。如果同一個Servlet類使用定義在4-35頁的4.4.1節 “程式設計式新增和配置Servlet” 的程式設計式API新增到ServletContext,使用@WebServlet註解宣告的值必須被忽略,必須建立一個指定名字的Servlet的新的例項。

@WebServlet註解的類必須繼承javax.servlet.http.HttpServlet類。

下面是如何使用該註解的一個示例。

程式碼示例8-1  @WebServlet 註解示例

@WebServlet(”/foo”)
public class CalculatorServlet extends HttpServlet{
    //...
}

下面是如何使用該註解指定更多的屬性的一個示例。

程式碼示例 8-2  @WebServlet 註解示例使用其它指定的註解屬性

@WebServlet(name=”MyServlet”, urlPatterns={"/foo", "/bar"})
public class SampleUsingAnnotationAttributes extends HttpServlet{
	public void doGet(HttpServletRequest req, HttpServletResponse res) {
    	}
}

8.1.2 @WebFilter

該註解用於在Web應用中定義Filter。該註解在一個類上指定且包含關於宣告的過濾器的元資料。如果沒有指定Filter名字則預設是全限定類名。註解的urlPatterns屬性, servletNames 屬性 或 value 屬性必須被指定。所有其他屬性是可選的預設設定(請參考javadoc獲取更多細節)。當註解上唯一屬性是url模式時推薦使用value,當有使用其他屬性時使用urlPatterns屬性。在同一註解上同時使用value 和 urlPatterns屬性是非法的。

@ WebFilter註解的類必須實現javax.servlet.Filter。

下面是如何使用該註解的一個示例。

程式碼示例 8-3  @WebFilter 註解示例

@WebFilter(“/foo”)
public class MyFilter implements Filter {
	public void doFilter(HttpServletRequest req, HttpServletResponse res)  {
		...
	}
}

8.1.3 @WebInitParam

該註解用於指定必須傳遞到Servlet或Filter的任何初始化引數。它是WebServlet和WebFilter註解的一個屬性。

8.1.4 @WebListener

WebListener註解用於註解獲得特定web應用上下文中的各種操作事件的監聽器。@WebListener註解的類必須實現以下介面:

■javax.servlet.ServletContextListener

■javax.servlet.ServletContextAttributeListener

■javax.servlet.ServletRequestListener

■javax.servlet.ServletRequestAttributeListener

■javax.servlet.http.HttpSessionListener

■javax.servlet.http.HttpSessionAttributeListener

示例:

@WebListener
public class MyListener implements ServletContextListener{
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext sc = sce.getServletContext();
        sc.addServlet("myServlet", "Sample servlet",  "foo.bar.MyServlet", null, -1);
        sc.addServletMapping("myServlet", new String[] { "/urlpattern/*" });
    }
}

8.1.5 @MultipartConfig

該註解,當指定在Servlet上時,表示請求期望是mime/multipart型別。相應servlet的HttpServletRequest物件必須使用getParts和getPart方法遍歷各個mime附件以獲取mime附件。javax.servlet.annotation.MultipartConfig的location屬性和<multipart-config>的<location>元素被解析為一個絕對路徑且預設為javax.servlet.context.tempdir。如果指定了相對地址,它將是相對於tempdir位置。絕對路徑與相對地址的測試必須使用java.io.File.isAbsolute。

8.1.6 其他註解/慣例

除了這些註解,定義在第15-183頁15.5節的“註解和資源注入”中的所有註釋將繼續工作在這些新註解上下文中。

預設情況下,所有應用的welcome-file-list列表中都將有index.htm(l)和index.jsp。該描述符可以用來覆蓋這些預設設定。

當使用註解時,沒有指定從WEB-INF/classes或WEB-INF/lib目錄下的各種框架jar包/類中載入監聽器、Servlet的順序。如果順序是很重要的,那麼請看web.xml模組部分和後面的web.xml和web-fragment.xml順序部分。順序僅能在部署描述符中指定。

8.2 可插拔性

8.2.1 web.xml模組

使用上述定義的註解,使得使用web.xml可選。然而,對於覆蓋預設值或使用註解設定的值,需要使用部署描述符。如前所述,如果web.xml 描述符中的metadata-complete元素設定為true,將不會處理在class檔案和繫結在jar包中的web-fragments中的註解。這意味著,應用的所有元資料通過web.xml描述符指定。

為了給開發人員更好的可插拔性和更少的配置,在這個版本(Servlet3.0)的規範中,我們引入了web模組部署描述符片段(web fragment)的概念。web fragment是web.xml的部分或全部,可以在一個類庫或框架 jar包的META-INF目錄指定和包括。在WEB-INF/lib目錄中的普通的老的jar檔案即使沒有web-fragment.xml也被認為是一個fragment,它們之中指定的所有註解都將按照定義在8.2.3節的規則處理,容器將會取出並按照如下定義的規則進行配置。

web fragment是web應用的一個邏輯分割槽,以這樣一種方式,web應用中使用的框架可以定義所有自定義配置(artifact)而無需要求開發人員在web.xml中編輯或新增資訊。它幾乎包含web.xml描述符中使用的所有相同的元素。不過描述符的頂級元素必須是web-fragment且對應的描述符檔案必須命名為web-fragment.xml,相關元素的順序在web-fragment.xml 和 web.xml也是不同的,請參考定義在第14章的部署描述符一章中對應的web-fragment模式(schema)。

如果框架打包成jar檔案,且有部署描述符形式的元資料資訊,那麼web-fragment.xml描述符必須在該jar包的META-INF/目錄中。

如果框架想使用META-INF/web-fragment.xml,以這樣一種方式,它擴充了web應用的web.xml,框架必須被繫結到Web應用的WEB-INF/lib目錄中。為了使框架中的任何其他型別的資源(例如,類檔案)對web應用可用,把框架放置在web應用的classloader委託鏈的任意位置即可。換句話說,只有繫結到web應用的WEB-INF/lib目錄中的JAR檔案,需要掃描其web-fragment.xml,而那些在類裝載委託鏈之上的JAR檔案不需要掃描其web-fragment.xml。

在部署期間,容器負責掃描上面指定的位置和發現web-fragment.xml並處理它們。存在於當前單個web.xml的名字唯一性的要求,也同樣適用於一組web.xml和所有可應用的web-fragment.xml檔案。

如下是庫或框架可以包括什麼的例子。

<web-fragment>
  <servlet>
    <servlet-name>welcome</servlet-name>
    <servlet-class>
      WelcomeServlet
    </servlet-class>
  </servlet>
  <listener>
    <listener-class>   
      RequestListener
   </listener-class>
  </listener>
</web-fragment>

以上的web-fragment.xml將被包括在框架的jar檔案的META-INF/目錄中。web-fragment.xml配置和應該應用的註解的順序是未定義的。如果順序對於某一應用是很重要的方面,請參考下面如何實現所需的順序定義的規則。

8.2.2 web.xml和web-fragment.xml順序

由於規範允許應用配置資源由多個配置檔案組成(web.xml 和 web-fragment.xml),必須解決從應用的多個不同位置發現和載入配置檔案的順序問題。本節詳述了配置資源的作者如何宣告他們自定義配置(artifact)的順序要求。

web-fragment.xml可以有一個javaee:java-identifierType型別的頂級<name>元素,且在一個web-fragment.xml中僅能有一個<name>元素。如果存在一個<name>元素,它必須考慮artifact的順序(除非出現重複名異常,如上文所述)。

在下面兩種情況下必須考慮允許應用程式配置資源表達它們的優先順序。

1. 絕對順序:在web.xml中的<absolute-ordering>元素。在一個web.xml中僅能有一個<absolute-ordering>元素。

a. 在這種情況下,下面第二種情況處理的優先順序必須被忽略。

b. web.xml和WEB-INF/classes 必須在absolute-ordering元素中的所有web-fragment之前處理。

c. <absolute-ordering>的所有直接子元素<name>必須解釋為表示那些命名為web-fragment的絕對順序,它可能存在,也可能不存在,必須被處理。

d. <absolute-ordering>可以包含零個或一個<others />元素。下面描述此元素必需的功能。如果<absolute-ordering>元素沒有包含<others/>元素,沒有在<name />明確提到的web-fragment必須被忽略。不會掃描被排除的jar包中的註解Servlet、Filter或Listener。然而,如果是被排除的jar包中的servlet、filter或listener,其列在web.xml或非排除的web-fragment.xml中,那麼將使用它的註解除非另外使用metadata-complete排除。

在排除的jar包的TLD 檔案中發現的ServletContextListener不能使用程式設計式API配置Filter和Servlet,任何試圖這樣做將導致IllegalStateException。如果發現的ServletContainerInitializer是從一個被排除的jar包中裝載的,它將被忽略。不掃描被排除的jar包中的任何ServletContainerInitializer處理的類。

e. 重複名字異常:如果,當遍歷<absolute-ordering>子元素時,遇到多個子元素具有相同<name>元素,只需考慮首次出現的。

2. 相對順序:在web-fragment.xml中的<ordering>元素,一個web-fragment.xml只能有一個<ordering>元素。

a. web-fragment.xml可以有一個<ordering>元素。如果是這樣,該元素必須包含零個或一個<before> 元素和零個或一個<after>元素。這些元素的含義在下面進行說明。

b. web.xml和WEB-INF/classes 必須在ordering元素中的所有web-fragment之前處理。

c. 重複命名異常:如果,當遍歷web-fragments時,遇到多個成員具有相同<name>元素,應用必須記錄包含有助於解決這個問題的錯誤提示資訊,且部署必須失敗。例如,一種解決該問題的辦法是使用者使用絕對順序,在這種情況下將忽略相對順序。

d. 思考這個簡短但具說明性的例子。3個作為應用一部分的web-fragment - MyFragment1、 MyFragment2 和 MyFragment3,也包括一個web.xml。

web-fragment.xml
<web-fragment>
  <name>MyFragment1</name>
  <ordering><after><name>MyFragment2</name></after></ordering>
  ...
</web-fragment>
web-fragment.xml
<web-fragment>
  <name>MyFragment2</name>
  ..
</web-fragment>
web-fragment.xml
<web-fragment>
  <name>MyFragment3</name>
  <ordering><before><others/></before></ordering>
  ..
</web-fragment>
web.xml
<web-app>
  ...
</web-app>

在該示例中,處理順序將是:

web.xml

MyFragment3

MyFragment2

MyFragment1

前面的示例說明了一些,但不是全部原則,以下是全部原則。

■ <before> 意味著文件必須被安排在指定在巢狀<name>元素的name匹配的文件之前。

■ <after> 意味著文件必須被安排在指定在巢狀<name>元素的name匹配的文件之後。

■ 在<before> 或 <after>可以包括特殊的<others/>元素零次或一次,或直接包括在<absolute-ordering>元素中零次或一次。必須按下面的方式處理<others/>元素。

■ 如果<before>元素包含一個巢狀的<others/>,該文件將被移動到有序的文件列表開頭。如果有多個文件指定<before><others/>,則它們都將在有序的文件列表開頭,但未指定該組文件的順序。

■ 如果<after>元素包含一個巢狀的<others/>,該文件將被移動有序的文件列表末尾。如果有多個文件指定<after><others/>,則它們都將在有序的文件列表末尾,但未指定該組文件的順序。

■ 在一個的<before>或<after>元素內,如果存在一個<others/> 元素,但在它的父元素內<name>元素不是唯一的,父元素內的其他元素必須按照順序處理。

■ 如果<others/>直接出現在<absolute-ordering>內,在執行時階段必須確保沒有明確指定在<absolute-ordering>部分的所有web-fragment包含在處理順序中。

■ 如果web-fragment.xml 檔案沒有<ordering>或web.xml 沒有<absolute-ordering>元素,則artifact被假定沒有任何順序依賴。

■如果執行時發現迴圈引用,必須記錄有用的提示資訊,且應用程式必須部署失敗。此外,使用者可能採取的一種措施是在web.xml使用絕對路徑。

■ 之前的示例可以被擴充套件以說明當web.xml包含順序部分的情況。

web.xml
<web-app>
<absolute-ordering>
    <name>MyFragment3</name>
    <name>MyFragment2</name>
</absolute-ordering>
  ...
</web-app>

在該示例中,各種元素的順序將是:

web.xml

MyFragment3

MyFragment2

下面包括了一些額外的示例場景。所有這些適用於相對順序而不是絕對順序。

Document A:

<after>
  <others/>
    <name>
      C
    </name>
</after>

Document B:

<before>
    <others/>
</before>

Document C:

<after>
    <others/>
</after>

Document D: 沒有指定順序

Document E: 沒有指定順序

Document F: 

<before>
  <others/>
  <name>
    B
  </name>
</before>

產生的解析順序:

web.xml, F, B, D, E, C, A。

Document<no id>:

<after>
  <others/>
</after>
<before>
  <name>
  C
  </name>
</before>

 Document B:

<before>
  <others/>
</before>

Document C:沒有指定順序

Document D:  

<after>
  <others/>
</after>

Document E:

<before>
  <others/>
</before>

Document F:沒有指定順序

產生的解析順序可能是下列之一:

■ B, E, F, <no id>, C, D

■ B, E, F, <no id>, D, C

■ E, B, F, <no id>, C, D

■ E, B, F, <no id>, D, C

■ E, B, F, D, <no id>, C

■ B, E, F, D, <no id>, C

Document A:

<after>
  <name>
  B
  </name>
</after>

Document B:沒有指定順序

Document C:

<before>
  <others/>
</before>

Document D: 沒有指定順序

產生的解析順序: C, B, D, A。解析的順序也可能是: C, D, B, A 或 C, B, A, D

8.2.3 從web.xml、web-fragment.xml和註解裝配描述符

如果對於一個應用來說Listener、Servlet和Filter的呼叫順序是很重要的,那麼必須使用部署描述符。同樣,如果有必要,可以使用上面定義的順序元素。如上所述,當使用註解定義Listener、Servlet和Filter,它們呼叫的順序是未指定的。下面是用於裝配應用程式的最終部署描述符的一組規則:

1. 如果有關的Listener、Servlet和Filter的順序必須指定,那麼必須指定在web-fragment.xml 或 web.xml。

2. 順序將依據它們定義在描述符中的順序,和依賴於web.xml中的absolute-ordering 元素或web-fragment.xml 中的ordering元素,如果存在。

a. 匹配請求的過濾器鏈的順序是它們在web.xml中宣告的順序。

b. Servlet在請求處理時延遲例項化或在部署時立即例項化。在後一種情況,以它們的load-on-startup元素表示的順序例項化。

c. 在此規範釋出這前,上下文Listener以隨機順序呼叫。在Servlet3.0,Listener以它們在web.xml中宣告的順序呼叫,如下所示:

i. javax.servlet.ServletContextListener實現的contextInitialized方法以宣告時順序呼叫,contextDestroyed 以相反順序呼叫。

ii. javax.servlet.ServletRequestListener實現的requestInitialized以宣告時順序呼叫,requestDestroyed方法以相反順序呼叫。

iii. javax.servlet.http.HttpSessionListener實現的sessionCreated方法以宣告時順序呼叫,sessionDestroyed 方法以相反順序呼叫。

iv. 未指定其他所有Listener介面的呼叫順序。

3. 如果使用web.xml中引入的enabled元素禁用了servlet,那麼為該servlet指定的url-pattern將不可用。

4. 當解析web.xml、web-fragment.xml 和註解之間發生衝突時web應用的web.xml具有最高優先順序。

5. 如果沒有在描述符中指定metadata-complete或在部署描述符中設定為false,通過組合出現在註解和描述符中的元資料匯出有效的元資料。合併的規則具體如下:

a. 在web fragment中的配置設定用於擴充那些已指定在主web.xml的配置設定,使用這種方式就好像它們指定在同一個web.xml。

b. 新增到主web.xml的web fragment中的配置設定的順序由8-70頁的8.2.2節“web.xml和web-fragment.xml順序”指定。

c. 當主web.xml的metadata-complete 屬性設定為true,被認為是完整的且在部署時不會掃描註解和fragment。如果有absolute-ordering和ordering元素將被忽略。當fragment上設定為true時,metadata-complete屬性僅適用於在特定的jar包中掃描註解。

d. 除非metadata-complete 設定為true,否則web fragment被合併到主web.xml。合併發生在相關fragment的註解處理之後。

e. 當使用web fragment擴充web.xml時以下被認為配置衝突:

i. 多個<init-param>元素使用相同的<param-name>但不同的<param-value>

ii. 多個<mime-mapping>元素使用相同的<extension>但不同的<mime-type>

f. 上面的配置衝突按照如下方式解析:

i. 在主web.xml和web fragment之間的配置衝突被解析為在web.xml的配置具有高優先順序。

ii. 在兩個web fragment之間的配置衝突,衝突的中心元素沒有出現在主web.xml,將導致一個錯誤。必須記錄一個有用的訊息,且應用必須部署失敗。

g. 上面的衝突被解析後,將應用下面這些額外的規則:

i. 可以在多個web-frament中宣告任意多次元素並生成到web.xml。比如,<context-param>元素可以以不同的<param-name>新增。

ii. 如果指定在web.xml中的覆蓋了指定在web-fragment中的同名的值,則可以宣告任意多次元素。

iii. 如果是最少出現零次且最多出現一次的元素存在於web fragment,且沒有在主web.xml中,則主web.xml繼承web fragment的設定。如果元素出現在主web.xml和web fragment,則主web.xml的配置設定具有高優先順序。例如,如果在主web.xml和web fragment中都聲明瞭相同的servlet,且宣告在web fragment中的servlet指定了<load-on-startup>元素,且沒在主web.xml指定,則web fragment的<load-on-startup>元素將被使用併合併到web.xml。

iv. 如果是最少出現零次且最多出現一次的元素指定在兩個web fragment中,且沒有出現在主web.xml,則認為是錯誤的。例如,如果兩個web fragment聲明瞭相同的Servlet,但具有不同的<load-on-startup>元素,且相同的Servlet也宣告在主web.xml中,但沒有<load-on-startup>,則必須報告一個錯誤。

v.<welcome-file>宣告是可新增的。

vi. 具有相同<servlet-name>的<servlet-mapping>元素可以新增到多個web-fragment。在web.xml中指定的<servlet-mapping>覆蓋在web-fragment中指定的同名的<servlet-name>的<servlet-mapping>。

vii. 具有相同<filter-name>的<filter-mapping>元素可以新增到多個web-fragment。在web.xml中指定的<filter-mapping>覆蓋在web-fragment中指定的同名的<filter-name>的<filter-mapping>。

viii. 具有相同<listener-class>的多個<listener>元素被當作一個<listener>宣告。

ix. 合併產生的web.xml被認為是<distributable>,僅當所有它的web fragment也被標記為<distributable>。

x. webfragment的頂級<icon>和它的孩子元素,<display-name>,和<description>元素被忽略。

xi. jsp-property-group是可新增的。當繫結jar檔案META-INF/resources目錄中的靜態資源時,推薦jsp-config元素使用url-pattern,反對使用extension對映。此外,如果存在一個fragment的JSP資源,則應該在一個與fragment同名的子目錄中。這有助於防止一個web-fragment的jsp-property-group受到來自應用的主docroot中的JSP的影響和受到來自一個fragment的META-INF/resources的JSP的影響。

h. 對於所有資源引用元素 (env-entry, ejb-ref,ejb-local-ref, service-ref, resource-ref, resource-env-ref, message-destination-ref,persistence-context-ref和persistence-unit-ref) 適用如下規則:

i. 如果任意資源引用元素出現在web fragment中,而主web.xml中沒有,那麼主web.xml繼承web fragment的值。如果該元素同時出現在主web.xml和web fragment,使用相同的名字,web.xml具有高優先順序。所有fragment的子元素除下面指定的injection-target外其他都被合併到主web.xml中。例如,如果主web.xml和web fragment都使用相同的<resource-ref-name>宣告一個<resource-ref>,將使用web.xml中的<resource-ref>且不會合並fragment中的任何子元素除下面宣告的<injection-target>外。

ii. 如果資源引用元素指定在兩個fragment中,而沒有指定在主web.xml中,且資源引用元素的所有屬性和子元素都是一樣的,資源引用將被合併到主web.xml。如果使用相同名字在兩個fragment中指定資源引用元素,而沒有在web.xml中指定,且屬性和子元素是不一樣的,那麼被認為是錯誤的。必須報告錯誤且應用必須部署失敗。例如,如果兩個web fragment使用相同的<resource-ref-name>聲明瞭<resource-ref>但型別一個指定為javax.sql.DataSource,而另一個指定為一個JavaMail資源,這是錯誤的且應用必須部署失敗。

iii. 對於在fragment中使用相同名稱的<injection-target> 的資源引用元素將被合併到主web.xml。

i. 除了上面定義的web-fragment.xml的合併規則之外,下面的規則適用於使用資源引用註解(@Resource,@Resources, @EJB, @EJBs, @WebServiceRef, @WebServiceRefs, @PersistenceContext,@PersistenceContexts,@PersistenceUnit, 和@PersistenceUnits)。

如果資源引用註解應用到類上,這等價於定義一個資源,但是這不等價於定義一個injection-target。在這種情況下上述規則適用於injection-target元素。

如果在欄位上使用資源引用註解,這等價於在web.xml定義injection-target元素。但是如果在描述符中沒有injection-target元素,那麼fragment中的injection-target仍將被合併到上面定義的web.xml中。

如果從另一方面來說,在主web.xml中有一個injection-target並同時有一個相同資源名的資源引用註解,那麼這被認為是對資源引用註解的覆蓋。在這種情況下,由於在描述符中指定了一個injection-target,上述定義的規則將適用並覆蓋資源引用註解的值。

j. 如果在兩個fragment中指定了data-source元素,而沒有出現在主web.xml中,且data-source元素的所有屬性和子元素都是一樣的,data-source將被合併到主web.xml中。如果在兩個fragment中指定同名的data-source元素,而沒有出現在主web.xml中且兩個fragment的屬性和子元素不是一樣的,這被認為是錯誤的。在這種情況下,必須報告一個錯誤且引用必須部署失敗。

下面是一些示例,展示了在不同情況下的結果。

程式碼示例8-4

web.xml – 沒有 resource-ref 定義

Fragment 1

web-fragment.xml
<resource-ref>
    <resource-ref-name="foo">
    ...
    <injection-target>
       <injection-target-class>
            com.foo.Bar.class
       </injection-target-class>
        <injection-target-name>
                baz
        </injection-target-name>
   </injection-target>
</resource-ref>

有效的metadata將是

<resource-ref>
    <resource-ref-name="foo">
        ....
         <injection-target>
       <injection-target-class>
            com.foo.Bar.class
       </injection-target-class>
        <injection-target-name>
                baz
        </injection-target-name>
   </injection-target>
</resource-ref>

程式碼示例8-5

web.xml
<resource-ref>
    <resource-ref-name="foo">
    ...
</resource-ref>

Fragment 1

web-fragment.xml
<resource-ref>
    <resource-ref-name="foo">
    ...
    <injection-target>
       <injection-target-class>
            com.foo.Bar.class
       </injection-target-class>
        <injection-target-name>
                baz
        </injection-target-name>
   </injection-target>
</resource-ref>
Fragment 2
web-fragment.xml
<resource-ref>
    <resource-ref-name="foo">
    ...
    <injection-target>
       <injection-target-class>
            com.foo.Bar2.class
       </injection-target-class>
        <injection-target-name>
                baz2
        </injection-target-name>
   </injection-target>
</resource-ref>

有效的metadata將是

<resource-ref>
    <resource-ref-name="foo">
        ....
    <injection-target>
       <injection-target-class>
            com.foo.Bar.class
       </injection-target-class>
        <injection-target-name>
                baz
        </injection-target-name>
   </injection-target>
         <injection-target>
       <injection-target-class>
            com.foo.Bar2.class
       </injection-target-class>
        <injection-target-name>
                baz2
        </injection-target-name>
   </injection-target>
</resource-ref>

程式碼示例8-6

web.xml
<resource-ref>
    <resource-ref-name="foo">
       <injection-target>
       <injection-target-class>
            com.foo.Bar3.class
       </injection-target-class>
        <injection-target-name>
                baz3
        </injection-target-name>
    ...
</resource-ref>

Fragment 1

web-fragment.xml
<resource-ref>
    <resource-ref-name="foo">
    ...
    <injection-target>
       <injection-target-class>
            com.foo.Bar.class
       </injection-target-class>
        <injection-target-name>
                baz
        </injection-target-name>
   </injection-target>
</resource-ref>

Fragment 2

web-fragment.xml
<resource-ref>
    <resource-ref-name="foo">
    ...
    <injection-target>
       <injection-target-class>
            com.foo.Bar2.class
       </injection-target-class>
        <injection-target-name>
                baz2
        </injection-target-name>
   </injection-target>
</resource-ref>

有效的metadata將是

<resource-ref>
    <resource-ref-name="foo">
       <injection-target>
       <injection-target-class>
            com.foo.Bar3.class
       </injection-target-class>
        <injection-target-name>
                baz3
        </injection-target-name>
        <injection-target-class>
            com.foo.Bar.class
        </injection-target-class>
        <injection-target-name>
            baz
        </injection-target-name>
        <injection-target-class>
            com.foo.Bar2.class
        </injection-target-class>
        <injection-target-name>
            baz2
        </injection-target-name>
        </injection-target>
        ...
</resource-ref>

Fragment1和2的<injection-target>將被合併到主web.xml

k. 如果主web.xml沒有指定任何<post-construct>元素,而web-fragment中指定了<post-construct> ,那麼fragment中的<post-construct>將被合併到主web.xml。不過如果在主web.xml中至少指定一個<post-construct>元素,那麼fragment中的<post-construct>將不被合併。由web.xml的作者負責確保<post-construct>列表是完整的。

l. 如果主web.xml沒有指定任何<pre-destroy>元素,且web-fragment中也指定了<pre-destroy>,那麼fragment中的<pre-destroy>元素將被合併到主web.xml。不過如果在主web.xml中至少指定一個<pre-destroy>元素,那麼fragment中的<pre-destroy>將不被合併。由web.xml的作者負責確保<pre-destroy>列表是完整的。

m. 在處理完web-fragment.xml之後,在處理下一個fragment之前相應fragment的註解被處理以完成有效的metadata。以下規則用於處理註解:

n. 通過註解指定的metadata,尚未存在於描述符中,將被用來擴充有效的描述符。

i. 指定在主web.xml或web fragment中的配置比通過註解指定的配置具有更高優先順序。

ii. 使用@WebServlet 註解定義的Servlet,要使用描述符覆蓋其值,描述符中的servlet名字必須與使用註解指定的servlet名字匹配(如果沒有通過註解指定名字,那麼使用明確指定的或預設的名字)。

iii. 使用註解定義的Servlet和Filter初始化引數,如果描述符中的初始化引數的名字完全匹配指定在註解中的名字,則將描述符中的覆蓋。可在註解和描述符之間新增初始化引數。

iv. 以給定servlet名字指定在描述符中的url-pattern,將覆蓋註解指定的url pattern。

v. 使用@WebFilter 註解定義的Filter,要使用描述符覆蓋其值,描述符中的Filter名字必須與使用註解指定的Filter名字匹配(如果沒有通過註解指定名字,那麼使用明確指定的或預設的名字)。

vi. Filter應用的url-pattern,當以給定Filter名字指定在描述符中時,將覆蓋註解指定的url pattern。

vii. Filter應用的DispatcherType,當以給定Filter名字指定在描述符中時,將覆蓋註解指定的DispatcherType。

viii. 下面的例子演示了上面的一些規則:

使用註解宣告的Servlet和在打包到相應的web.xml描述符中的Servlet:

@WebServlet(urlPatterns=”/MyPattern”, initParams={@WebInitParam(name="ccc", value="333")})
public class com.acme.Foo extends HttpServlet {
  ...
}
web.xml
<servlet>
   <servlet-class>com.acme.Foo</servlet-class>
   <servlet-name>Foo</servlet-name>
   <init-param>
     <param-name>aaa</param-name>
     <param-value>111</param-value>
   </init-param>
</servlet>
<servlet>
   <servlet-class>com.acme.Foo</servlet-class>
   <servlet-name>Fum</servlet-name>
   <init-param>
     <param-name>bbb</param-name>
     <param-value>222</param-value>
   </init-param>
</servlet>
<servlet-mapping>
   <servlet-name>Foo</servlet-name>
   <url-pattern>/foo/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
   <servlet-name>Fum</servlet-name>
   <url-pattern>/fum/*</url-pattern>
</servlet-mapping>

因為使用註解宣告的Servlet名字與web.xml中宣告的servlet名字不匹配,在web.xml中除了其他的宣告外,還添加了一個註解指定的新的servlet宣告,相當於:

<servlet>
   <servlet-class>com.acme.Foo</servlet-class>
   <servlet-name>com.acme.Foo</servlet-name>
   <init-param>
     <param-name>ccc</param-name>
     <param-value>333</param-name>
   </init-param>
</servlet>
如果上面的web.xml被替換為如下:
 <servlet>
   <servlet-class>com.acme.Foo</servlet-class>
   <servlet-name>com.acme.Foo</servlet-name>
   <init-param>
     <param-name>aaa</param-name>
     <param-value>111</param-value>
   </init-param>
 </servlet>
 <servlet-mapping>
   <servlet-name>com.acme.Foo</servlet-name>
   <url-pattern>/foo/*</url-pattern>
 </servlet-mapping>

那麼有效的描述符將等價於:

 <servlet>
   <servlet-class>com.acme.Foo</servlet-class>
   <servlet-name>com.acme.Foo</servlet-name>
   <init-param>
     <param-name>aaa</param-name>
     <param-value>111</param-value>
   </init-param>
   <init-param>
     <param-name>ccc</param-name>
     <param-value>333</param-value>
   </init-param>
 </servlet>
 <servlet-mapping>
   <servlet-name>com.acme.Foo</servlet-name>
   <url-pattern>/foo/*</url-pattern>
 </servlet-mapping>

8.2.4 共享庫 / 執行時可插拔性

除了支援fragment和使用註解的外,另一個要求是我們不僅能plug-in 繫結在WEB-INF/lib下的資源,也能夠plugin框架共享副本—包括能plug-in到web容器的東西,如建立在web容器之上的JAX-WS、JAX-RS和JSF。ServletContainerInitializer允許處理這樣的使用情況,如下所述。

在容器/應用啟動時,由容器通過jar services API查詢一個ServletContainerInitializer例項。框架提供的ServletContainerInitializer實現必須繫結在jar包的META-INF/services目錄中的一個叫做javax.servlet.ServletContainerInitializer的檔案中,根據每個jar services API,指定ServletContainerInitializer的實現類。

除ServletContainerInitializer外,我們還有一個註解—HandlesTypes。在ServletContainerInitializer 實現上的HandlesTypes註解用於表示感興趣的一些類,這些類可能在HandlesTypes的value中指定了註解(型別、方法或欄位級別的註解),或者是這些類繼承/實現了該類的超類中的某個類。容器使用HandlesTypes註解來決定什麼時候呼叫initializer的onStartup方法。當檢測一個應用的類看它們是否匹配ServletContainerInitializer的HandlesTypes註解指定的所有條件時,如果應用的一個或多個可選的JAR包缺失,容器可能遇到類裝載問題。由於容器不能決定這些類裝載失敗的型別是否會阻止應用正常工作,它必須忽略它們,同時也提供一個記錄這些問題的配置選項。

如果ServletContainerInitializer實現沒有@HandlesTypes註解,或如果沒有匹配任何指定的HandlesType,那麼它會為每個應用使用null作為Set的值呼叫一次。這將允許initializer來決定基於應用中可用的資源是否需要初始化一個Servlet/Filter。

在任何Listener的事件被觸發之前,當應用正在啟動時,ServletContainerInitializer的onStartup方法將被呼叫。

ServletContainerInitializer的onStartup得到一個類的Set,其或者繼承/實現initializer表示感興趣的類,或者它使用@HandlesTypes註解指定的任意類來註解。

下面一個具體的例子展示了這是如何工作的。

讓我們學習JAX-WS web service 執行時。

JAX-WS執行時實現通常不是繫結到每個war包。其實現將繫結一個ServletContainerInitializer的實現(如下所示)且容器將使用services API來查詢(繫結在jar包中的META-INF/services目錄中的一個叫做javax.servlet.ServletContainerInitializer的檔案,它將指出如下所示的JAXWSServletContainerInitializer)。

@HandlesTypes(WebService.class)
JAXWSServletContainerInitializer implements ServletContainerInitializer  {
    public void onStartup(Set<Class<?>> c, ServletContext ctx)  throws ServletException {
        // 這裡使用JAX-WS 特定的程式碼來初始化執行庫和設定mapping等。
        ServletRegistration reg = ctx.addServlet("JAXWSServlet", "com.sun.webservice.JAXWSServlet");
        reg.addServletMapping("/foo");
    }
}

框架的jar包也可能被繫結到war檔案中的WEB-INF/lib目錄。如果ServletContainerInitializer被繫結到應用的WEB-INF/lib目錄內的一個JAR包中,它的onStartup方法在繫結到的應用啟動期間僅被呼叫一次。換句話說,如果ServletContainerInitialzer被繫結到WEB-INF/lib目錄外的一個JAR包中,但仍能被執行時的服務提供商查詢機制發現,每次啟動應用時都會呼叫它的onStartup方法。

ServletContainerInitializer介面的實現將被執行時的服務查詢機制或語義上與它等價的容器特定機制發現。無論是哪種情況,必須忽略從絕對順序中排除的web fragment JAR包中的ServletContainerInitializer服務,這些服務被發現的順序必須遵照應用的類裝載委託模型。

8.3 JSP容器可插拔性

ServletContainerInitializer和程式設計註冊功能使在Servlet和JSP容器之間提供一個清晰的職責分離成為可能,通過由Servlet容器只負責解析web.xml和web-fragment.xml資源,而把解析標籤庫描述符(TLD)資源委託給JSP容器。

在此之前,web容器必須掃描TLD資源的所有監聽器宣告。Servlet3.0以後,該職責可以委託給JSP容器。JSP容器是內嵌到一個相容Servlet3.0的Servlet容器中,可以提供它自己的ServletContainerInitializer實現,搜尋傳遞到它的onStartup方法的ServletContext引數的所有TLD資源,掃描這些資源的監聽器宣告,並向ServletContext註冊相應的監聽器。

另外,Servlet3.0之前,JSP容器通常必須掃描應用的部署描述符中所有與jsp-config相關的配置。Servlet3.0以後,Servlet容器必須可通過ServletContext.getJspConfigDescriptor方法獲取到應用程式的web.xml和web-fragment.xml部署描述符中的所有與jsp-config有關的配置。

在TLD中發現的和程式設計註冊的所有ServletContextListener提供的功能有限。任何試圖呼叫一個在Servlet3.0中新增的ServletContext API方法將導致一個UnsupportedOperationException。

另外,相容Servlet3.0的Servlet容器必須提供一個名字為javax.servlet.context.orderedLibs的ServletContext屬性,它的值(java.util.List<java.lang.String>型別)包含了由ServletContext所代表的應用的WEB-INF/lib目錄中的JAR檔案的名字列表,按照它們的web fragment名字的排序(可能排除已經從absolute-ordering中排除的fragment JAR檔案),或者如果應用沒有指定任何絕對或相對順序則為null。

8.4 處理註解和fragment

Web應用可同時包括註解和web.xml/web-fragment.xml部署描述符。如果沒有部署描述符,或有一個但其metadata-complete沒有設定為true,如果應用程式中使用了web.xml、web-fragment和註解則必須處理。下表描述了是否處理註解和web.xml的fragment。

表 8-1  註解和web fragment處理要求

部署描述符

metadata-complete

處理註解和web fragment

web.xml 2.5

yes

no

web.xml 2.5

no

yes

web.xml 3.0

yes

no

web.xml 3.0

no

yes