https://github.com/javahongxi
本文轉載自kaitao.hongxi.org
註解和可插拔性
本章討論Servlet 3.0規範定義的註解和使web應用內使用的框架和庫能夠可插拔的增強。
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。
以下註解必須被Servlet 3.0兼容的容器支持。
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。
下面是如何使用該註解的一個示例。
@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節的“註解和資源注入”將繼續工作在這些新註解上下文中。
默認情況下,所有應用將有index.htm(l)和index.jsp在welcome-file-list列表中。該描述符可以用來覆蓋這些默認設置。
當使用註解時,從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描述符指定。
爲了給開發人員更好的可插拔性和更少的配置,在這個版本(Servlet 3.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應用的一個邏輯分區,以這樣一種方式,在應用中使用的框架可以定義所有制品(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。
在部署期間,容器負責掃描上面指定的位置和發現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>
<name>MyFragment2</name>
..
</web-fragment>
<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>內,runtime必須確保任何web-fragment未明確指定在<absolute-ordering>部分的以處理的順序包括在這一點上。
■ 如果web-fragment.xml 文件沒有<ordering>或web.xml 沒有<absolute-ordering>元素,則artifact被假定沒有任何順序依賴。
■ 如果runtime發現循環引用,必須記錄提供有用信息的消息,應用必須部署失敗。此外, 用戶採取的一系列動作可能是在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,通過組合出現在註解和描述符中的metadata導出有效的metadata。合併的規則具體如下:
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>元素可以以不同的名字添加。
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. web fragment的頂級<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 and persistence-unit-ref) 如下規則適用:
i. 如果任意資源引用元素出現在web fragment,主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, and @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. url-pattern,當以給定servlet名字指定在描述符中時,將覆蓋註解指定的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>
</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容器之上的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 值的集合調用一次。這將允許initializer基於應用中可用的資源決定是否需要初始化Servlet/Filter。
在任何Listener的事件被觸發之前,當應用正在啓動時,ServletContainerInitializer的onStartup方法將被調用。
ServletContainerInitializer’s的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 服務被排除於一個absolute ordering必須被忽略,這些服務被發現的順序必須遵照應用的類裝載委託模型。
8.3 JSP容器可插拔性
ServletContainerInitializer和編程式註冊特性可以在Servlet和JSP容器之間提供一個清晰的職責分離,通過由Servlet容器只負責解析web.xml和web-fragment.xml資源,而解析標籤庫描述符(TLD)資源委託給JSP容器。
在此之前,web容器必須掃描TLD資源尋找任何Listener聲明。使用Servlet3.0後,該職責可以委託給JSP容器。JSP容器是內嵌到一個Servlet3.0兼容的Servlet容器中,可以提供它自己的ServletContainerInitializer實現,搜索傳遞到它的onStartup方法的ServletContext參數尋找任何TLD資源,掃描這些資源尋找Listener聲明,並向ServletContext註冊相關的Listener。
另外,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名字的排序(可能排除如果fragment JAR包已經被排除在absolute-ordering),或者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 |