本章聊一聊ServletContext 3.0規(guī)范中定義的注解以及在web應(yīng)用中使用的框架和庫的可插拔性的提升。
一、 注解和可插拔性
在一個web應(yīng)用中,如果使用注解的類位于WEB-INF/classes目錄下,或者如果它們被打成jar包并被放到WEB-INF/lib目錄中,使用注解的類將自己處理它們的注解。
web應(yīng)用部署描述符在web-app元素上有一個新的屬性metadata-complete。屬性metadata-complete定義了web描述符是否完整,或者在部署的時候jar文件中類文件是否應(yīng)該被掃描注解和web段。如果屬性metadata-complete被設(shè)置為true,部署工具必須忽略出現(xiàn)在應(yīng)用的類文件中和web分段中的任何servlet注解。如果屬性metadata-complete沒有明確設(shè)置或者被設(shè)置為false,部署工具必須檢查應(yīng)用中類文件的注解和掃描web片段。
下列是必須被兼容Servlet 3.0的容器支持的注解。
-
@WebServlet
這個注解用來定義web應(yīng)用中的一個Servlet注解。這個注解應(yīng)用在類上,且包含聲明的Servlet的元數(shù)據(jù)。注解上的屬性urlPatterns和value必須配置。其它屬性可以不用配置,直接使用默認值就好。推薦做法是,當(dāng)注解上的屬性僅僅是url pattern時使用value,當(dāng)注解上還有其它屬性使用時使用urlPattern。不能在一個注解上同時使用value和urlPatterns。如果沒有指定Servlet的名字,默認的名字是全路徑類名。被注解的servlet必須指明至少一個url Pattern。如果相同的servlet類在部署描述符中聲明了兩份,但是名字不一樣,那么servlet的新實例必須被實例化。如果相同的servlet類通過編程API被添加到ServletContext,那么通過@WebServlet注解聲明的值必須被忽略,并且要為有具體名字的servlet必須要創(chuàng)建一個新實例。
用@WebServlet注解的類必須繼承javax.servlet.http.HttpServlet。
以下是這個注解如何被使用的例子。
CODE EXAMPLE 1-1 @WebServlet 注解例子
@WebServlet("/foo")
public class CalculatorServlet extends HttpServlet {
// ......
}
以下是注解如何與更多的屬性一起工作的例子。
CODE EXAMPLE 1-2 @WebServlet 使用其它屬性的例子
@WebServlet(name="MyServlet", urlPattern={"/foo", "/bar"})
public class SampleUsingAnnotationAttributes extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) {
//....
}
}
-
@WebFilter
這個注解用來定義web應(yīng)用中的Filter。這個注解應(yīng)用在類上,并且包含有關(guān)filter的元數(shù)據(jù)。如果沒有指明Filter的名字,那么默認的名字將是全路徑類名。注解的屬性URLPattern,servltName,或者value必須被配置。所有其它屬性可選,并且有默認值可用。推薦的做法是,當(dāng)注解上僅有url pattern屬性的時候使用value,當(dāng)還有其它屬性被使用時使用urlPatterns。不能再同一個注解上同時使用value和urlPatterns。
用@WebFilter注解的類必須繼承javax.servlet.Filter。
以下是這個注解如何使用的例子。
CODE EXAMPLE 1-3 @WebFilter注解例子
@WebFilter("/foo")
public class MyFilter implements Filter {
public void doFilter(HttpServletRequest req, HttpServletResponse res) {
//......
}
} -
@WebInitParam
這個注解用來指明任何必須傳遞給Servlet或者Filter的參數(shù)。它是WebServlet和WebFilter注解的一個屬性。 -
@WebListener
注解WebListener用來注解一個監(jiān)聽者,以便獲取web應(yīng)用上下文中各種操作的事件。用@WebListener注解的類必須實現(xiàn)下列接口的一個:
- 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/*" });
}
-
@MultipartConfig
當(dāng)應(yīng)用在一個Servlet上,這個注解表明它期望的請求是mime/multipart類型。與servlet對應(yīng)的HttpServletRequest對象必須要能夠通過方法getParts和getPart來遍歷不同的mime附件。javax.serlvet.annotation.MultipartConfig的location屬性和<multipart-config>的<location>元素會被解析為絕對路徑,并且默認值是javax.servlet.context.tempdir。如果指定了一個相對地址,它會相對于tempdir這個位置。絕對路徑和相對路徑的測試必須通過java.io.File.isAbsolute 來完成。 -
其它注解和約定
除了以上注解,章節(jié)注解和資源注入中定義的注解將會繼續(xù)在這些新注解的上下文中正常工作。
通常情況下,所有應(yīng)用在welcome-file-list中有index.htm(1) 和 index.jsp。這個描述符可以用來重寫這些默認設(shè)置。
當(dāng)使用注解時,Listeners和Servlets從各種框架jars文件、WEB-INF/classes或者WEB-INF/lib中的class文件中被加載的順序沒有明確指明。如果順序很重要,那么看看web.xml的模塊和web.xml及web-fragment.xml的順序。順序僅能在部署描述符中被指定。
二、可插拔性
-
web.xml的模塊化
使用上述定義的注解可以不再依賴web.xml文件。但是為了重寫默認值或者注解設(shè)置的值,就需要使用部署描述符。就像之前的例子,如果metadata-complete元素在web.xml描述符中設(shè)置為true,那么類文件和jars里面web-fragments文件中的注解將不會被處理。這個暗示了應(yīng)用中所有的元數(shù)據(jù)都通過web.xml描述符來指明。
為了給開發(fā)者提供更好的可插拔性和更少的配置,servlet 3.0規(guī)范引入了web模塊化部署描述符片段(web fragment),一個web片段可以是一個web.xml的一部分或者全部,它可以被包含在一個庫中或者框架jar文件里面META-INF目錄中。WEB-INF/lib目錄里面沒有web-fragment.xml的普通舊jar也被認為是一個片段。在它里面的所有注解將根據(jù)下面第三小節(jié)定義的規(guī)則被處理。容器將選擇和使用像如下定義的規(guī)則來做為配置。
一個web片段以這樣一種方式來作為web應(yīng)用的一個邏輯部分--正在web應(yīng)用里面被使用的框架能夠定義所有構(gòu)件,而不需要讓開發(fā)者來編輯或者添加信息到web.xml文件中。它幾乎能夠包含所有web.xml文件中使用的元素。然而描述符的頂級元素必須是web-fragment, 并且對應(yīng)的描述符文件必須被命名為web-fragment.xml。相關(guān)元素的順序在web-fragment.xml和web.xml之間也不一樣。
如果一個框架被打包為一個jar文件,且元數(shù)據(jù)是部署描述符的形式,那么web-fragment.xml描述符必須要在jar文件的META-INF/目錄里。
如果一個框架想要像填充web應(yīng)用的web.xml文件的方式來填充META-INF/web-fragment.xml,框架必須被綁定在web應(yīng)用的WEB-INF/lib目錄里。為了web應(yīng)用能使用框架其它類型的資源(如類文件),使用web應(yīng)用的類加載器委托鏈就能夠?qū)崿F(xiàn)。換句話說,僅有位于web應(yīng)用的WEB-INF/lib目錄下,而不是在類加載委托鏈上層目錄的JAR文件需要為web-fragment.xml而被掃描。
容器在部署期間需要負責(zé)掃描上述指定的位置,并且發(fā)現(xiàn)web-fragment.xml,然后處理它們。單個web.xml中名字唯一性的要求同樣適用于一個web.xml和所有web-fragment.xml文件的組合。
一個庫或者框架能包含內(nèi)容如下:
<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的配置和注解的順序并未定義。如果對于web應(yīng)用這個順序很重要,請參考下面定義的如何獲取特定順序的規(guī)則。
-
web.xml和web-fragment.xml的順序
由于規(guī)范允許應(yīng)用配置資源由多個配置文件(web.xml和web-fragment.xml)組成,它們會從一個應(yīng)用中幾個不同的位置被發(fā)現(xiàn)和加載,那么順序的問題必須被重視。本章詳述了怎樣聲明構(gòu)件的順序。
一個web-fragment.xml可以有一個javaee:java-identifierType類型的頂級元素<name>。web-fragment.xml 中僅能有一個<name>元素。如果一個<name>元素出現(xiàn),必須考慮到構(gòu)件順序。
要讓應(yīng)用配置資源表達他們的順序,必須考慮兩個場景:- 絕對順序:web.xml 中的一個<absolute-ordering>元素。一個web.xml中僅能有一個<absolute-ordering>元素。
- 需要被下面第二條case處理的有序引用必須被忽略。
- web.xml文件必須在absolute-ordering 元素列表中的任意一個web-fragment之前被處理。
- <absolute-ordering>的任意直接子元素<name>必須被解析,以便明確那些有名字的web-fragments按照絕對順序被處理。
-
<absolute-ordering>元素可以包含0個或者1一個<others />元素。這個元素對應(yīng)的行為如下。如果<absolute-ordering> 元素不包含一個<others />元素,任何沒有在<name />元素中具體提到的web-fragment必須被忽略。外部的jars不會被掃描帶注解的servlets,filters,或者listeners。然而,如果來自外部jar的servlet,filter,或者listener被列在web.xml或者一個非外部的web-fragment中,那么除非被metadata-complete 排除在外,它的注解將會被掃描。
在外部jars的TLD文件中發(fā)現(xiàn)的ServletContextListeners不能使用編程方式配置filters和servlets。任何試圖如此的行為都會拋出IllegalStateException。如果發(fā)現(xiàn)的ServletContainerInitializer 是從外部jar中加載的,它將會被忽略。外部的jars不會為任何ServletContainerInitializer 處理的類而被掃描。 - 重復(fù)名字異常:當(dāng)轉(zhuǎn)換<absolute-ordering>的子元素時,如果多個子元素有相同的<name>元素,僅有遇到第一個出現(xiàn)的會被處理。
- 相對順序:web-fragment.xml里面的<ordering>元素。一個web-fragment.xml 文件中僅有一個<ordering>。
* 一個*web-fragment.xml*可以有一個*<ordering>*元素。如果這樣,這個元素必須包含0個或者一個*<before>*元素以及0個或者一個*after*元素。這些元素的意義在下面解釋。
* 重復(fù)名字異常:當(dāng)轉(zhuǎn)換web-fragments時,如果有相同*<name>*元素的多個成員,應(yīng)用必須記錄一個錯誤信息來幫助修復(fù)這個問題,而且應(yīng)用必須報錯并停止部署。比如,對于使用者,修復(fù)這個問題的一種方式是使用絕對順序,這種情況下相對路徑將會失效。
* 考慮如下簡潔但是有說明性的例子。3個web-fragments-MyFragment1,MyFragment2和MyFragment3是包含*web.xml*應(yīng)用的一部分。
web-fragment.xml
<web-fragment>
<name>MyFragment1</name>
<ordering><after><name>MyFragments</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>
上面這個例子中,處理的順序如下:
1. web.xml
1. MyFragment3
1. MyFragment2
1. MyFragment1
前面的例子說明了下列原則中的一些而不是全部。
* <before>意味著文檔在匹配嵌套<name>元素里名字的文件前必須有序。
* <after>意味著文檔在匹配嵌套<name>元素里名字的文件后必須有序。
* 有一個特殊的元素<other/>,它可以被包含在<before>或者<after>里面0次或者1次;或直接被包含<absolute-ordering>元素中0次或者1次。<others/>元素必須按如下處理:
* 如果<before>元素包含一個嵌套的<others/>,這個文檔將會被移動到有序文檔列表的開頭。如果有以<before><others/>開頭的多個文檔,它們將會放在有序文檔列表的開頭,但是文檔組內(nèi)的順序并未指定。
* 如果<after>元素包含一個嵌套<others/>,文檔將被移動到有序文檔列表的末尾。如果有多個需要<after><others/>的文檔它們將會被移動到有序文檔的末尾,但是這些文檔組內(nèi)部的順序并未指定。
* 在第一個<before>或者<after>元素中,如果出現(xiàn)了<others/>元素,但是在它的父級中并不是只有<name>,父級元素中的其它元素必須認為是按順序處理。
* 如果<others/>元素直接出現(xiàn)在<absolute-ordering>元素中,運行時必須確保<absolute-ordering>塊中任何未明確命名的web-fragments在那個點按照處理順序被包含進來。
* 如果一個web-fragment.xml文件沒有一個<ordering>或者web.xml沒有<absolute-ordering>元素,那么構(gòu)建就被認為沒有任何順序依賴。
* 如果運行時發(fā)現(xiàn)循環(huán)引用,必須記錄一條消息,并且應(yīng)用必須立即報錯,停止部署。用戶可以采用一個方式是在web.xml中使用絕對順序。
* 當(dāng)web.xml包含一個有序部分,之前的例子能夠被擴展來說明這個case:
web.xml
<web-app>
<absolute-ordering>
<name>MyFragment3</name>
<name>MyFragment2</name>
</absolute-ordering>
...
</web-app>
這個例子中,不同元素的順序?qū)?br>
* web.xml
* MyFragment3
* MyFragment2
一些額外的示例場景描述如下。所有這些例子都應(yīng)用于相對順序和非絕對順序:
Document A
<after>
<others/>
<name>C</name>
</after>
Document B
<before>
<others/>
</before>
Document C
<after>
<others/>
</after>
Document D: no ordering
Document E: no ordering
Document F:
<before>
<others/>
<name>name</name>
</before>
結(jié)果解析順序:
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 D:
<after>
<others/>
</after>
Document E:
<before>
<others/>
</before>
Document D: no ordering
結(jié)果解析可能是下列中的一種:
* 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
* E, B, F, D, <no id>, D
Document A:
<after>
<name>B</name>
</after>
Document B: no ordering
Document C:
<before>
<others/>
</before>
Document D: no ordering: no ordering
結(jié)果解析:C, B, D, A. 解析順序也可能是:C, D, B, A或者C, B, A, D.
-
組裝來自web.xml,web-fragment.xml和annotations的描述符
如果對于一個應(yīng)用來說,listener,servlets和filters被調(diào)用的順序很重要,那么必須使用部署描述符。而且必要的時候,上述有序元素可以被使用。如上述,當(dāng)使用注解來定義listeners,servlets和filters,它們被調(diào)用的順序就未知了。下面是對于一個應(yīng)用,組裝最后部署描述符的規(guī)則集合: - 如果listeners,servlets,filters相關(guān)聯(lián),那么它們之間的順序必須在web.xml或者web-fragment.xml中指明。
- 順序?qū)⒒谒鼈冊诿枋龇斜欢x,web.xml中的absolute-ordering元素或者web-fragment.xml中的ordering元素的順序。
* 匹配請求的filters按照它們在web.xml中聲明的順序組織成鏈。
* servlets會在請求處理時通過懶加載初始化或者在部署階段直接初始化。在后面一個case中,它們按照*load-on-startup*元素中指明的順序初始化。
* 在這個規(guī)范發(fā)行之前,上下文listeners按隨機順序被調(diào)用。在Servlet 3.0中,listeners按照它們在web.xml中聲明的順序被調(diào)用:
* *javax.servlet.ServletContextListener*的實現(xiàn)在它們的*contextInitialized*按照它們被聲明的順序被調(diào)用,而在*contextDestroyed* 方法里按照相反的順序被調(diào)用。
* *javax.servlet.ServletRequestListener*的實現(xiàn)在它們的*requestInitialized*按照它們被聲明的順序被調(diào)用,而在*requestDestroyed* 方法里按照相反的順序被調(diào)用。
* *javax.servlet.http.HttpSessionListener*的實現(xiàn)在它們的*sessionCreated*按照它們被聲明的順序被調(diào)用,而在*sessionDestroyed* 方法里按照相反的順序被調(diào)用。
* 任何其它listener接口的調(diào)用順序都不能確定。
- 如果一個servlet在web.xml中使用enabled元素來disabled掉,那么這個servlet在為servlet指定的url-pattern上不會生效。
- 當(dāng)解決web.xml, web-fragment.xml和注解之間的沖突時,web應(yīng)用的web.xml有最高優(yōu)先級。
- 如果metadata-complete在描述符中未指定,或者在部署描述符中被設(shè)置為false,應(yīng)用的有效元數(shù)據(jù)來自注解和描述符的并集。合并的規(guī)則如下:
* web fragments中的配置設(shè)置用來增強主web.xml中設(shè)置,就好像它們在同一個web.xml中配置的一樣。
* web fragments中的配置設(shè)置被添加到主web.xml的順序如上述章節(jié)描述一樣-*web.xml和web-fragment.xml的順序*。
* 在web.xml中當(dāng)*metadata-complete*屬性被設(shè)置為true時,它就被認為是完整的,并且注解和fragment的掃描將不會在部署時期出現(xiàn)。如果*absolute-ordering*和*ordering*元素出現(xiàn),那么它們將會被忽略。如果在fragment中設(shè)置為true,*metadata-complete*屬性僅應(yīng)用在那個特殊jar的注解掃描上。
* 除非*metadata-complete*被設(shè)置為true,那么web fragment被合并進主web.xml。這個合并發(fā)生在對應(yīng)fragment上注解處理之后。
* 當(dāng)用web fragments增強web.xml時,下列將被認為是配置沖突:
* 多個*init-param*元素,有相同的*param-name*,但是有不同的*param-value*。
* 多個*mime-mapping*元素,有相同的*extension*,但是不同的*mime-type*。
* 上述配置沖突這樣來解決:
* web.xml和web fragment之間有配置沖突,那么web.xml中的配置生效。
* 在兩個web fragments之間的沖突,而且沖突元素并不出現(xiàn)在web.xml中,將會導(dǎo)致一個錯誤。這時應(yīng)該記錄錯誤信息,而且應(yīng)用必須失敗,然后停止部署。
* 在上述沖突解決之后,這些額外的規(guī)則會被應(yīng)用:
* 那些可以被聲明任意多次的元素最后會從各個web-fragment匯總累加到web.xml。比如,*<context-param>*元素有不同的*<param-name>*累加。
* 那些可以被聲明多次的元素,如果它們在web.xml有,那么web.xml中的值會覆蓋web-fragments中的的同名元素。
* 如果一個最多會出現(xiàn)一次的元素出現(xiàn)在一個web fragment中,而沒有出現(xiàn)在主web.xml中,那么主web.xml會繼承web fragment中的設(shè)置。如果元素在主web.xml和一個web fragment中同時出現(xiàn),web.xml中的配置優(yōu)先級最高。比如,如果主web.xml和一個web fragment同時聲明了相同的servlet,并且web fragment中的servlet聲明指明了一個*<load-on-startup>*元素,但是web.xml中沒有對應(yīng)的元素,那么來自web fragment的*<load-on-startup>*元素將會在合并的web.xml中被使用。
* 如果最多能出現(xiàn)一次的元素在不同的web fragment中都出現(xiàn)了,而沒有出現(xiàn)在主web.xml,這將會被認為是個錯誤。比如,如果兩個web fragment聲明了同樣的servlet,但是有不同的*<load-on-startup>*元素;并且同樣的servlet在主web.xml也被聲明了,但是沒有*<load-on-startup>*元素,那么必須拋出一個錯誤。
* *<welcome-file>*聲明是累加的。
* 有相同*<servlet-name>*的*<servlet-mapping>*元素在web-fragments之間是累加的。web.xml中的*<servlet-name>*元素會重寫web-fragment中相同*<servlet-name>*的元素。
* 有相同*<listener-class>*的多個*<listener>*元素被認為是單個*<listener>*聲明。
* 僅當(dāng)所有的web fragments被標(biāo)記為*<distributable>*,合并后的web.xml被認為是<distributable>。
* 一個web fragment的頂層*<icon>*及它的子元素,*<display-name>*,*<description>*元素將會被忽略。
* jsp-property-group是累加的。當(dāng)綁定一個jar文件的*META-INF/resources*目錄中的靜態(tài)資源時,推薦*jsp-config*元素使用**url-pattern**,而不是擴展映射。如果一個fragment存在更多JSP資源,那么應(yīng)該放在一個子目錄中。這有助于防止一個web fragment的*jsp-property-group*元素影響到應(yīng)用的主文檔中的JSPs,且防止影響到一個fragment的*META-INF/resources*目錄中的JSPs。
* 下列規(guī)則應(yīng)用于所有資源引用元素(env-entry, ejb-ref, ejb-local-ref, service-ref, resource-ref, resource-env-ref, message-destination-ref, persistence-context-ref和persistence-unit-ref):
* 如果任何資源引用元素出現(xiàn)在一個web fragment,并且不出現(xiàn)在web.xml中,那么主web.xml繼承web fragment中的值。如果元素同時出現(xiàn)在web.xml和web fragment中,且有相同的名字,web.xml有最高優(yōu)先級。除了下面將描述的*injection-target*元素,沒有fragment的子元素會合并到主web.xml。比如,如果web.xml和web fragments都聲明了有相同*<resouce-ref-name>*的*<resource-ref>*元素,來的web.xml的*<resource-ref>*將會被使用,但是除了下述*<injection-target>*,沒有其它子元素會從fragment合并進來。
* 如果一個資源引用元素在兩個fragments中配置,同時并沒有在web.xml中配置,并且資源引用元素的的所有屬性和子元素都一樣,那么資源引用將會被合并進主web.xml。如果資源引用元素在兩個fragment中有相同的名字,同時并不出現(xiàn)在主web.xml中,并且在兩個fragment中屬性和子元素都不相同,將會被認為是一個錯誤。錯誤必須被報告出來且應(yīng)用必須失敗并停止部署。比如,如果兩個fragment聲明了有相同*<resource-ref-name>*的*<resource-ref>*元素,但是其中一個的類型是*javax.sql.DataSource*,而另外一個中的類型是一個*JavaMail* 資源,它會是個錯誤且應(yīng)用應(yīng)該報錯并停止部署。
* 對于資源引用,來自fragment且有同名*<injection-target>*元素的元素將會被合并進主web.xml。
* 除了上述針對web-fragment.xml的合并規(guī)則,當(dāng)使用資源引用注解(*@Resource,@Resources,@EJB,@EJBs,@WebServiceRef,@WebServiceRefs,@PersistenceContext, @PersistenceContexts, @PersistenceUnit和@PersistenceUnits*)時,下列規(guī)則將會被應(yīng)用。
如果一個資源引用被應(yīng)用在一個類上,它等價于定義了一個資源,然而,它并不等價于定義一個injection-target。上述規(guī)則應(yīng)用于這個場景中的inject-target元素中。
如果一個資源引用被應(yīng)用于一個域上,等價于在web.xml中定義了一個injection-target元素。然而,如果在描述符中沒有injection-target元素,那么來自fragments的injection-target 元素將會被合并進web.xml。
如果web.xml中有一個injection-target,以及有一個有相同資源名字的資源引用注解,那么它被認為是對資源引用注解的重寫。這種情況由于有一個injection-target在描述符中,除了為資源引用注解重寫值,上述定義的規(guī)則將會應(yīng)用。
* 如果一個data-source元素在兩個fragment中被指定,同時不出現(xiàn)在主web.xml中,并且data-source元素的所有屬性和子元素都一樣,data-source元素將會被合并進主web.xml。如果data-source元素在兩個fragment中有相同的名字,同時不出現(xiàn)在web.xml并且在兩個fragment中屬性和子元素不一樣,那么將會被認為是一個錯誤。這種情況下,一個錯誤必須被報告出來且應(yīng)用必須立即失敗并停止部署。
下面是一些展示不同情況下結(jié)果的例子。
CODE EXAMPLE 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
</injecttion-target-name>
</injection-target>
</resource-ref>
有效的元數(shù)據(jù)將會是:
<resource-ref>
<resource-ref-name="foo">
...
<injection-target>
<inject-target-class>
com.foo.Bar.class
</inject-target-class>
<injection-target-name>
baz
</injection-target-name>
</inject-target>
<resource-ref>
CODE EXAMPLE 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>
有效的元數(shù)據(jù)將是:
<resource-ref>
<resource-ref-name="foo">
...
<inject-target>
<injection-target-class>
com.foo.Bar.class
</injection-target-class>
<injection-target-name>
baz
</injection-target-name>
</inject-target>
<injection-target>
<injection-target-class>
com.foo.Bar2.class
</injection-target-class>
<injection-target-name>
baz2
</injection-target-name>
</injection-target>
</resource-ref>
CODE EXAMPLE 8-6
<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>
有效的元數(shù)據(jù)如下:
<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>
..
<resource-ref>
來自fragment 1和2的<injection-target>將會被合并到主web.xml。
* 如果主web.xml沒有配置任何<post-construct>元素,并且web-fragment沒有指定<post-construct>,那么來自fragment的<post-construct>元素將被合并到主web.xml。然而,在主web.xml中至少指定了一個<post-construct>元素,那么來自fragment的<post-construct>元素將不會被合并。web.xml的作者負責(zé)保證<post-construct>列表完成。
* 如果主web.xml沒有指定任何<pre-destroy>元素,并且web-fragment指定了<pre-destroy>,那么來自fragment的<pre-destroy>元素將會被合并進主web.xml。然而,如果在主web.xml至少有一個<pre-destroy>,那么來自fragment的<pre-destroy>將不會被合并。web.xml的作者負責(zé)保證<pre-destroy>列表完成。
* 在處理web-fragment.xml之后,在處理下一個fragment之前,來自對應(yīng)fragment的注解將會被處理來為fragment完成有效元數(shù)據(jù)。下列規(guī)則被用來處理注解:
* 任何通過并不出現(xiàn)在描述符中的注解指定的元數(shù)據(jù)將會被用來擴展有效描述符。
3. 主web.xml或者一個web fragment中的配置比注解的優(yōu)先級更高。
3. 對于通過@WebServlet注解定義的一個servlet,為了通過描述符重寫值,描述符中servlet的名字必須匹配通過注解(如果沒有通過注解指定,那么顯示指定或者默認名字)指定的servlet的名字。
3. 如果初始化參數(shù)的名字匹配通過注解指定的名字,那么通過注解為servlets何filter定義的初始化參數(shù)將會在描述符中的被重寫。初始化參數(shù)在注解和描述符之間會累加。
3. 當(dāng)在描述符中為給定的servlet名字指定了url-patterns,它將會重寫通過注解配置的url patterns。
3. 對于通過@WebFilter注解定義的一個filter,為了重寫通過描述符的值,描述符中filter的名字必須匹配通過注解(如果沒有通過注解指定一個,那么顯示指定或者默認值)指定的filter的名字。
3. 當(dāng)在描述符中為一個指定的filter配置一個url-patterns時候,它會重寫通過注解指定的url patterns。
3. 當(dāng)在描述符中為一個指定的filter名字指定了一個DispatcherTypes,它會重寫通過注解指定的DispatcherTypes。
3. 下面的例子說明了一些上述規(guī)則-
通過一個注解聲明的servlet并且與描述符中對應(yīng)的web.xml一起打包。
@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用以下內(nèi)容代替:
<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>
- 共享庫/運行時插件
除了支持fragments和注解的使用,其中一個要求是我們不僅能插進綁定在WEB-INF/lib中的一些東西,而且插件共享框架副本-包括能夠插進web容器,如構(gòu)建在web容器之上的JAX-WS,JAX-RS和JSF。ServletContainerInitializer 允許處理此種情況。
容器會在容器或者應(yīng)用啟動時刻,通過jar服務(wù)API查找ServletContainerInitializer 的一個實例。提供ServletContainerInitializer的實現(xiàn)的框架必須在jar文件的META-INF/services目錄下捆綁叫做javax.servlet.ServletContainerInitializer的文件,作為每個jar服務(wù)API,它指向ServletContainerInitializer的實現(xiàn)類。
除了ServletContainerInitializer,我們也有一個注解-HandlesTypes。在ServletContainerInitializer的實現(xiàn)上,HandlesTypes注解用來表達對那些可以有在HandlersTypes的值里指定的注解(類型,方法或者域級注解)的類感興趣,或者如果它在類的超類的任意位置擴展/實現(xiàn)了那些類。容器使用HandlersTypes注解來決定何時調(diào)用初始化的onStartup方法。當(dāng)檢測一個應(yīng)用的類來看它們是否匹配通過ServletContainerInitializer的HandlersTypes注解指定的任意標(biāo)準(zhǔn)時,如果缺少一個或者多個應(yīng)用的可選JAR文件,容器可以在類加載問題中執(zhí)行。由于容器不是在一個點決定這些加載失敗的類的類型是否會阻礙應(yīng)用正常工作,容器必須忽略它們,同時提供一個配置選項來記錄這些錯誤日志。
如果ServletContainerInitializer的一個實現(xiàn)沒有@HandlersTypes注解,或者如果沒有匹配任意指定的HandlersType,那么它會為每一個應(yīng)用被調(diào)用一次,且集合的值為null。這允許初始化來決定基于應(yīng)用中的可用資源來判斷它是否需要初始化一個servlet或者filter。
當(dāng)在任意listener的事件調(diào)用之前一個應(yīng)用起來時,ServletContainerInitializer的onStartup方法將被調(diào)用。
ServletContainerInitializer的onStartup方法獲得一個類的集合,這些類或者繼承/實現(xiàn)初始化程序感興趣的類,或者通過@HandlesTypes注解配置了。
下面是一個具體的例子來說明這如何工作。
我們拿JAX-WS服務(wù)運行時來說。
JAX-WS運行時的實現(xiàn)通常不捆綁在每個war文件中。這個實現(xiàn)會捆綁一個ServletContainerInitializer的實現(xiàn),并且容器將會使用服務(wù)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 specific code here to initialize the runtime
// and setup the mapping etc.
ServletRegistration reg = ctx.addServlet("JAXWSServlet","com.sun.webservice.JAXWSServlet");
reg.addServletMapping("/foo");
}
框架jar文件也能被捆綁在war文件的WEB-INF/lib目錄下。如果ServletContainerInitializer在一個JAR文件中被捆綁到一個應(yīng)用的WEB-INF/lib的目錄下,它的onStartup方法在應(yīng)用啟動階段僅被調(diào)用一次。另一方面,如果ServletContainerInitializer在JAR文件中被捆綁到WEB-INF/lib目錄下,但是仍然沒有被運行時服務(wù)提供者查找機制發(fā)現(xiàn),它的onStartup方法將在每次應(yīng)用開始時都被調(diào)用。
ServletContainerInitializer接口的實現(xiàn)將會被運行時服務(wù)查找機制,或者與它等價的容器具體機制發(fā)現(xiàn)。無論哪種情況下,來自被從絕對順序中排除的web fragment JAR文件的ServletContainerInitializer服務(wù)必須被忽略,并且這些服務(wù)被發(fā)現(xiàn)的順序必須遵循應(yīng)用的類加載委托模型。
JSP容器可插入性
通過讓Servlet容器僅負責(zé)解析web.xml和web-fragment.xml資源,且把Tag Library Descriptor(TLD)委托給JSP容器,ServletContainerInitializer和項目注冊的特點在Servlet和JSP容器之間提供了清晰的職責(zé)隔離。
之前,一個web容器必須為任何listener聲明掃描TLD資源。在Servlet 3.0中,這個職責(zé)可以委托給JSP容器。嵌入到兼容Servlet 3.0的Servlet容器的JSP容器可以提供它自己的ServletContainerInitializer 實現(xiàn),可以搜索為任何TLD資源傳遞給onStartup的ServletContext,可以為listener聲明掃描那些資源,以及用ServletContext注冊對應(yīng)的listeners。
除此之外,在Servlet 3.0之前,JSP容器通常會為了任何jsp-config相關(guān)的配置去掃描一個應(yīng)用的部署描述符。在Servlet 3.0,Servlet容器必須通過ServletContext.getJspConfigDescriptor方法準(zhǔn)備好來自應(yīng)用的web.xml和web-fragment.xml部署描述符的任何jsp-config 相關(guān)的配置。
任何在TLD中被發(fā)現(xiàn)以及通過編程方式注冊的ServletContextListener在它們提供的功能中會受限制。任何嘗試在它們之上去調(diào)用在Servlet 3.0中添加的ServletContext API方法會導(dǎo)致一個UnsupportedOperationException。
此外,兼容Servlet 3.0的Servlet容器必須提供一個有javax.servlet.context.orderedLibs 名字的ServletContext 屬性,它的值(java.util.List<java.lang.String>類型)包含一個被ServletContext代表的應(yīng)用WEB-INF/lib目錄里JAR文件的名字列表,它會按照它們的web fragment名字(如果fragmentJAR文件被排除在absolute-ordering就會被排除)排序,或者如果應(yīng)用沒有指明任何絕對或相對順序就為null。
處理注解和fragments
Web應(yīng)用可能同時包含注解和web.xml/web-fragment.xml部署描述符。如果沒有部署描述符或者有一個但是沒有把metadata-complete設(shè)置為true,那么web.xml,web-fragment.xml和注解必須被處理。下列表格描述了是否需要處理注解和web.xml fragments
TABLE 8-1 注解和web fragment處理要求
| Deployment descriptor | metadata-complete | process annotations and web fragments |
|---|---|---|
| web.xml 2.5 | yes | no |
| web.xml 2.5 | no | yes |
| web.xml 3.0 | yes | no |
| web.xml 3.0 | no | yes |
翻譯自 Java Servlet Specification
Version 3.0 Rev a
Author:Rajiv Mordani
Date: December 2010