本章聊一聊ServletContext 3.0规范中定义的注解以及在web应用中使用的框架和库的可插拔性的提升。
一、 注解和可插拔性
在一个web应用中,如果使用注解的类位于WEB-INF/classes目录下,或者如果它们被打成jar包并被放到WEB-INF/lib目录中,使用注解的类将自己处理它们的注解。
web应用部署描述符在web-app元素上有一个新的属性metadata-complete。属性metadata-complete定义了web描述符是否完整,或者在部署的时候jar文件中类文件是否应该被扫描注解和web段。如果属性metadata-complete被设置为true,部署工具必须忽略出现在应用的类文件中和web分段中的任何servlet注解。如果属性metadata-complete没有明确设置或者被设置为false,部署工具必须检查应用中类文件的注解和扫描web片段。
下列是必须被兼容Servlet 3.0的容器支持的注解。
-
@WebServlet
这个注解用来定义web应用中的一个Servlet注解。这个注解应用在类上,且包含声明的Servlet的元数据。注解上的属性urlPatterns和value必须配置。其它属性可以不用配置,直接使用默认值就好。推荐做法是,当注解上的属性仅仅是url pattern时使用value,当注解上还有其它属性使用时使用urlPattern。不能在一个注解上同时使用value和urlPatterns。如果没有指定Servlet的名字,默认的名字是全路径类名。被注解的servlet必须指明至少一个url Pattern。如果相同的servlet类在部署描述符中声明了两份,但是名字不一样,那么servlet的新实例必须被实例化。如果相同的servlet类通过编程API被添加到ServletContext,那么通过@WebServlet注解声明的值必须被忽略,并且要为有具体名字的servlet必须要创建一个新实例。
用@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应用中的Filter。这个注解应用在类上,并且包含有关filter的元数据。如果没有指明Filter的名字,那么默认的名字将是全路径类名。注解的属性URLPattern,servltName,或者value必须被配置。所有其它属性可选,并且有默认值可用。推荐的做法是,当注解上仅有url pattern属性的时候使用value,当还有其它属性被使用时使用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的参数。它是WebServlet和WebFilter注解的一个属性。 -
@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/*" });
}
-
@MultipartConfig
当应用在一个Servlet上,这个注解表明它期望的请求是mime/multipart类型。与servlet对应的HttpServletRequest对象必须要能够通过方法getParts和getPart来遍历不同的mime附件。javax.serlvet.annotation.MultipartConfig的location属性和<multipart-config>的<location>元素会被解析为绝对路径,并且默认值是javax.servlet.context.tempdir。如果指定了一个相对地址,它会相对于tempdir这个位置。绝对路径和相对路径的测试必须通过java.io.File.isAbsolute 来完成。 -
其它注解和约定
除了以上注解,章节注解和资源注入中定义的注解将会继续在这些新注解的上下文中正常工作。
通常情况下,所有应用在welcome-file-list中有index.htm(1) 和 index.jsp。这个描述符可以用来重写这些默认设置。
当使用注解时,Listeners和Servlets从各种框架jars文件、WEB-INF/classes或者WEB-INF/lib中的class文件中被加载的顺序没有明确指明。如果顺序很重要,那么看看web.xml的模块和web.xml及web-fragment.xml的顺序。顺序仅能在部署描述符中被指定。
二、可插拔性
-
web.xml的模块化
使用上述定义的注解可以不再依赖web.xml文件。但是为了重写默认值或者注解设置的值,就需要使用部署描述符。就像之前的例子,如果metadata-complete元素在web.xml描述符中设置为true,那么类文件和jars里面web-fragments文件中的注解将不会被处理。这个暗示了应用中所有的元数据都通过web.xml描述符来指明。
为了给开发者提供更好的可插拔性和更少的配置,servlet 3.0规范引入了web模块化部署描述符片段(web fragment),一个web片段可以是一个web.xml的一部分或者全部,它可以被包含在一个库中或者框架jar文件里面META-INF目录中。WEB-INF/lib目录里面没有web-fragment.xml的普通旧jar也被认为是一个片段。在它里面的所有注解将根据下面第三小节定义的规则被处理。容器将选择和使用像如下定义的规则来做为配置。
一个web片段以这样一种方式来作为web应用的一个逻辑部分--正在web应用里面被使用的框架能够定义所有构件,而不需要让开发者来编辑或者添加信息到web.xml文件中。它几乎能够包含所有web.xml文件中使用的元素。然而描述符的顶级元素必须是web-fragment, 并且对应的描述符文件必须被命名为web-fragment.xml。相关元素的顺序在web-fragment.xml和web.xml之间也不一样。
如果一个框架被打包为一个jar文件,且元数据是部署描述符的形式,那么web-fragment.xml描述符必须要在jar文件的META-INF/目录里。
如果一个框架想要像填充web应用的web.xml文件的方式来填充META-INF/web-fragment.xml,框架必须被绑定在web应用的WEB-INF/lib目录里。为了web应用能使用框架其它类型的资源(如类文件),使用web应用的类加载器委托链就能够实现。换句话说,仅有位于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的配置和注解的顺序并未定义。如果对于web应用这个顺序很重要,请参考下面定义的如何获取特定顺序的规则。
-
web.xml和web-fragment.xml的顺序
由于规范允许应用配置资源由多个配置文件(web.xml和web-fragment.xml)组成,它们会从一个应用中几个不同的位置被发现和加载,那么顺序的问题必须被重视。本章详述了怎样声明构件的顺序。
一个web-fragment.xml可以有一个javaee:java-identifierType类型的顶级元素<name>。web-fragment.xml 中仅能有一个<name>元素。如果一个<name>元素出现,必须考虑到构件顺序。
要让应用配置资源表达他们的顺序,必须考虑两个场景:- 绝对顺序: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 />元素。这个元素对应的行为如下。如果<absolute-ordering> 元素不包含一个<others />元素,任何没有在<name />元素中具体提到的web-fragment必须被忽略。外部的jars不会被扫描带注解的servlets,filters,或者listeners。然而,如果来自外部jar的servlet,filter,或者listener被列在web.xml或者一个非外部的web-fragment中,那么除非被metadata-complete 排除在外,它的注解将会被扫描。
在外部jars的TLD文件中发现的ServletContextListeners不能使用编程方式配置filters和servlets。任何试图如此的行为都会抛出IllegalStateException。如果发现的ServletContainerInitializer 是从外部jar中加载的,它将会被忽略。外部的jars不会为任何ServletContainerInitializer 处理的类而被扫描。 - 重复名字异常:当转换<absolute-ordering>的子元素时,如果多个子元素有相同的<name>元素,仅有遇到第一个出现的会被处理。
- 相对顺序:web-fragment.xml里面的<ordering>元素。一个web-fragment.xml 文件中仅有一个<ordering>。
* 一个*web-fragment.xml*可以有一个*<ordering>*元素。如果这样,这个元素必须包含0个或者一个*<before>*元素以及0个或者一个*after*元素。这些元素的意义在下面解释。
* 重复名字异常:当转换web-fragments时,如果有相同*<name>*元素的多个成员,应用必须记录一个错误信息来帮助修复这个问题,而且应用必须报错并停止部署。比如,对于使用者,修复这个问题的一种方式是使用绝对顺序,这种情况下相对路径将会失效。
* 考虑如下简洁但是有说明性的例子。3个web-fragments-MyFragment1,MyFragment2和MyFragment3是包含*web.xml*应用的一部分。
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/>开头的多个文档,它们将会放在有序文档列表的开头,但是文档组内的顺序并未指定。
* 如果<after>元素包含一个嵌套<others/>,文档将被移动到有序文档列表的末尾。如果有多个需要<after><others/>的文档它们将会被移动到有序文档的末尾,但是这些文档组内部的顺序并未指定。
* 在第一个<before>或者<after>元素中,如果出现了<others/>元素,但是在它的父级中并不是只有<name>,父级元素中的其它元素必须认为是按顺序处理。
* 如果<others/>元素直接出现在<absolute-ordering>元素中,运行时必须确保<absolute-ordering>块中任何未明确命名的web-fragments在那个点按照处理顺序被包含进来。
* 如果一个web-fragment.xml文件没有一个<ordering>或者web.xml没有<absolute-ordering>元素,那么构建就被认为没有任何顺序依赖。
* 如果运行时发现循环引用,必须记录一条消息,并且应用必须立即报错,停止部署。用户可以采用一个方式是在web.xml中使用绝对顺序。
* 当web.xml包含一个有序部分,之前的例子能够被扩展来说明这个case:
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: no ordering
Document E: no ordering
Document F:
<before>
<others/>
<name>name</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 D:
<after>
<others/>
</after>
Document E:
<before>
<others/>
</before>
Document D: no ordering
结果解析可能是下列中的一种:
* 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
结果解析:C, B, D, A. 解析顺序也可能是:C, D, B, A或者C, B, A, D.
-
组装来自web.xml,web-fragment.xml和annotations的描述符
如果对于一个应用来说,listener,servlets和filters被调用的顺序很重要,那么必须使用部署描述符。而且必要的时候,上述有序元素可以被使用。如上述,当使用注解来定义listeners,servlets和filters,它们被调用的顺序就未知了。下面是对于一个应用,组装最后部署描述符的规则集合: - 如果listeners,servlets,filters相关联,那么它们之间的顺序必须在web.xml或者web-fragment.xml中指明。
- 顺序将基于它们在描述符中被定义,web.xml中的absolute-ordering元素或者web-fragment.xml中的ordering元素的顺序。
* 匹配请求的filters按照它们在web.xml中声明的顺序组织成链。
* servlets会在请求处理时通过懒加载初始化或者在部署阶段直接初始化。在后面一个case中,它们按照*load-on-startup*元素中指明的顺序初始化。
* 在这个规范发行之前,上下文listeners按随机顺序被调用。在Servlet 3.0中,listeners按照它们在web.xml中声明的顺序被调用:
* *javax.servlet.ServletContextListener*的实现在它们的*contextInitialized*按照它们被声明的顺序被调用,而在*contextDestroyed* 方法里按照相反的顺序被调用。
* *javax.servlet.ServletRequestListener*的实现在它们的*requestInitialized*按照它们被声明的顺序被调用,而在*requestDestroyed* 方法里按照相反的顺序被调用。
* *javax.servlet.http.HttpSessionListener*的实现在它们的*sessionCreated*按照它们被声明的顺序被调用,而在*sessionDestroyed* 方法里按照相反的顺序被调用。
* 任何其它listener接口的调用顺序都不能确定。
- 如果一个servlet在web.xml中使用enabled元素来disabled掉,那么这个servlet在为servlet指定的url-pattern上不会生效。
- 当解决web.xml, web-fragment.xml和注解之间的冲突时,web应用的web.xml有最高优先级。
- 如果metadata-complete在描述符中未指定,或者在部署描述符中被设置为false,应用的有效元数据来自注解和描述符的并集。合并的规则如下:
* web fragments中的配置设置用来增强主web.xml中设置,就好像它们在同一个web.xml中配置的一样。
* web fragments中的配置设置被添加到主web.xml的顺序如上述章节描述一样-*web.xml和web-fragment.xml的顺序*。
* 在web.xml中当*metadata-complete*属性被设置为true时,它就被认为是完整的,并且注解和fragment的扫描将不会在部署时期出现。如果*absolute-ordering*和*ordering*元素出现,那么它们将会被忽略。如果在fragment中设置为true,*metadata-complete*属性仅应用在那个特殊jar的注解扫描上。
* 除非*metadata-complete*被设置为true,那么web fragment被合并进主web.xml。这个合并发生在对应fragment上注解处理之后。
* 当用web fragments增强web.xml时,下列将被认为是配置冲突:
* 多个*init-param*元素,有相同的*param-name*,但是有不同的*param-value*。
* 多个*mime-mapping*元素,有相同的*extension*,但是不同的*mime-type*。
* 上述配置冲突这样来解决:
* web.xml和web fragment之间有配置冲突,那么web.xml中的配置生效。
* 在两个web fragments之间的冲突,而且冲突元素并不出现在web.xml中,将会导致一个错误。这时应该记录错误信息,而且应用必须失败,然后停止部署。
* 在上述冲突解决之后,这些额外的规则会被应用:
* 那些可以被声明任意多次的元素最后会从各个web-fragment汇总累加到web.xml。比如,*<context-param>*元素有不同的*<param-name>*累加。
* 那些可以被声明多次的元素,如果它们在web.xml有,那么web.xml中的值会覆盖web-fragments中的的同名元素。
* 如果一个最多会出现一次的元素出现在一个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中被使用。
* 如果最多能出现一次的元素在不同的web fragment中都出现了,而没有出现在主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>*声明。
* 仅当所有的web fragments被标记为*<distributable>*,合并后的web.xml被认为是<distributable>。
* 一个web fragment的顶层*<icon>*及它的子元素,*<display-name>*,*<description>*元素将会被忽略。
* jsp-property-group是累加的。当绑定一个jar文件的*META-INF/resources*目录中的静态资源时,推荐*jsp-config*元素使用**url-pattern**,而不是扩展映射。如果一个fragment存在更多JSP资源,那么应该放在一个子目录中。这有助于防止一个web fragment的*jsp-property-group*元素影响到应用的主文档中的JSPs,且防止影响到一个fragment的*META-INF/resources*目录中的JSPs。
* 下列规则应用于所有资源引用元素(env-entry, ejb-ref, ejb-local-ref, service-ref, resource-ref, resource-env-ref, message-destination-ref, persistence-context-ref和persistence-unit-ref):
* 如果任何资源引用元素出现在一个web fragment,并且不出现在web.xml中,那么主web.xml继承web fragment中的值。如果元素同时出现在web.xml和web fragment中,且有相同的名字,web.xml有最高优先级。除了下面将描述的*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中有相同的名字,同时并不出现在主web.xml中,并且在两个fragment中属性和子元素都不相同,将会被认为是一个错误。错误必须被报告出来且应用必须失败并停止部署。比如,如果两个fragment声明了有相同*<resource-ref-name>*的*<resource-ref>*元素,但是其中一个的类型是*javax.sql.DataSource*,而另外一个中的类型是一个*JavaMail* 资源,它会是个错误且应用应该报错并停止部署。
* 对于资源引用,来自fragment且有同名*<injection-target>*元素的元素将会被合并进主web.xml。
* 除了上述针对web-fragment.xml的合并规则,当使用资源引用注解(*@Resource,@Resources,@EJB,@EJBs,@WebServiceRef,@WebServiceRefs,@PersistenceContext, @PersistenceContexts, @PersistenceUnit和@PersistenceUnits*)时,下列规则将会被应用。
如果一个资源引用被应用在一个类上,它等价于定义了一个资源,然而,它并不等价于定义一个injection-target。上述规则应用于这个场景中的inject-target元素中。
如果一个资源引用被应用于一个域上,等价于在web.xml中定义了一个injection-target元素。然而,如果在描述符中没有injection-target元素,那么来自fragments的injection-target 元素将会被合并进web.xml。
如果web.xml中有一个injection-target,以及有一个有相同资源名字的资源引用注解,那么它被认为是对资源引用注解的重写。这种情况由于有一个injection-target在描述符中,除了为资源引用注解重写值,上述定义的规则将会应用。
* 如果一个data-source元素在两个fragment中被指定,同时不出现在主web.xml中,并且data-source元素的所有属性和子元素都一样,data-source元素将会被合并进主web.xml。如果data-source元素在两个fragment中有相同的名字,同时不出现在web.xml并且在两个fragment中属性和子元素不一样,那么将会被认为是一个错误。这种情况下,一个错误必须被报告出来且应用必须立即失败并停止部署。
下面是一些展示不同情况下结果的例子。
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>
有效的元数据将会是:
<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>
有效的元数据将是:
<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>
有效的元数据如下:
<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的作者负责保证<post-construct>列表完成。
* 如果主web.xml没有指定任何<pre-destroy>元素,并且web-fragment指定了<pre-destroy>,那么来自fragment的<pre-destroy>元素将会被合并进主web.xml。然而,如果在主web.xml至少有一个<pre-destroy>,那么来自fragment的<pre-destroy>将不会被合并。web.xml的作者负责保证<pre-destroy>列表完成。
* 在处理web-fragment.xml之后,在处理下一个fragment之前,来自对应fragment的注解将会被处理来为fragment完成有效元数据。下列规则被用来处理注解:
* 任何通过并不出现在描述符中的注解指定的元数据将会被用来扩展有效描述符。
3. 主web.xml或者一个web fragment中的配置比注解的优先级更高。
3. 对于通过@WebServlet注解定义的一个servlet,为了通过描述符重写值,描述符中servlet的名字必须匹配通过注解(如果没有通过注解指定,那么显示指定或者默认名字)指定的servlet的名字。
3. 如果初始化参数的名字匹配通过注解指定的名字,那么通过注解为servlets何filter定义的初始化参数将会在描述符中的被重写。初始化参数在注解和描述符之间会累加。
3. 当在描述符中为给定的servlet名字指定了url-patterns,它将会重写通过注解配置的url patterns。
3. 对于通过@WebFilter注解定义的一个filter,为了重写通过描述符的值,描述符中filter的名字必须匹配通过注解(如果没有通过注解指定一个,那么显示指定或者默认值)指定的filter的名字。
3. 当在描述符中为一个指定的filter配置一个url-patterns时候,它会重写通过注解指定的url patterns。
3. 当在描述符中为一个指定的filter名字指定了一个DispatcherTypes,它会重写通过注解指定的DispatcherTypes。
3. 下面的例子说明了一些上述规则-
通过一个注解声明的servlet并且与描述符中对应的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用以下内容代替:
<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容器,如构建在web容器之上的JAX-WS,JAX-RS和JSF。ServletContainerInitializer 允许处理此种情况。
容器会在容器或者应用启动时刻,通过jar服务API查找ServletContainerInitializer 的一个实例。提供ServletContainerInitializer的实现的框架必须在jar文件的META-INF/services目录下捆绑叫做javax.servlet.ServletContainerInitializer的文件,作为每个jar服务API,它指向ServletContainerInitializer的实现类。
除了ServletContainerInitializer,我们也有一个注解-HandlesTypes。在ServletContainerInitializer的实现上,HandlesTypes注解用来表达对那些可以有在HandlersTypes的值里指定的注解(类型,方法或者域级注解)的类感兴趣,或者如果它在类的超类的任意位置扩展/实现了那些类。容器使用HandlersTypes注解来决定何时调用初始化的onStartup方法。当检测一个应用的类来看它们是否匹配通过ServletContainerInitializer的HandlersTypes注解指定的任意标准时,如果缺少一个或者多个应用的可选JAR文件,容器可以在类加载问题中执行。由于容器不是在一个点决定这些加载失败的类的类型是否会阻碍应用正常工作,容器必须忽略它们,同时提供一个配置选项来记录这些错误日志。
如果ServletContainerInitializer的一个实现没有@HandlersTypes注解,或者如果没有匹配任意指定的HandlersType,那么它会为每一个应用被调用一次,且集合的值为null。这允许初始化来决定基于应用中的可用资源来判断它是否需要初始化一个servlet或者filter。
当在任意listener的事件调用之前一个应用起来时,ServletContainerInitializer的onStartup方法将被调用。
ServletContainerInitializer的onStartup方法获得一个类的集合,这些类或者继承/实现初始化程序感兴趣的类,或者通过@HandlesTypes注解配置了。
下面是一个具体的例子来说明这如何工作。
我们拿JAX-WS服务运行时来说。
JAX-WS运行时的实现通常不捆绑在每个war文件中。这个实现会捆绑一个ServletContainerInitializer的实现,并且容器将会使用服务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文件中被捆绑到一个应用的WEB-INF/lib的目录下,它的onStartup方法在应用启动阶段仅被调用一次。另一方面,如果ServletContainerInitializer在JAR文件中被捆绑到WEB-INF/lib目录下,但是仍然没有被运行时服务提供者查找机制发现,它的onStartup方法将在每次应用开始时都被调用。
ServletContainerInitializer接口的实现将会被运行时服务查找机制,或者与它等价的容器具体机制发现。无论哪种情况下,来自被从绝对顺序中排除的web fragment JAR文件的ServletContainerInitializer服务必须被忽略,并且这些服务被发现的顺序必须遵循应用的类加载委托模型。
JSP容器可插入性
通过让Servlet容器仅负责解析web.xml和web-fragment.xml资源,且把Tag Library Descriptor(TLD)委托给JSP容器,ServletContainerInitializer和项目注册的特点在Servlet和JSP容器之间提供了清晰的职责隔离。
之前,一个web容器必须为任何listener声明扫描TLD资源。在Servlet 3.0中,这个职责可以委托给JSP容器。嵌入到兼容Servlet 3.0的Servlet容器的JSP容器可以提供它自己的ServletContainerInitializer 实现,可以搜索为任何TLD资源传递给onStartup的ServletContext,可以为listener声明扫描那些资源,以及用ServletContext注册对应的listeners。
除此之外,在Servlet 3.0之前,JSP容器通常会为了任何jsp-config相关的配置去扫描一个应用的部署描述符。在Servlet 3.0,Servlet容器必须通过ServletContext.getJspConfigDescriptor方法准备好来自应用的web.xml和web-fragment.xml部署描述符的任何jsp-config 相关的配置。
任何在TLD中被发现以及通过编程方式注册的ServletContextListener在它们提供的功能中会受限制。任何尝试在它们之上去调用在Servlet 3.0中添加的ServletContext API方法会导致一个UnsupportedOperationException。
此外,兼容Servlet 3.0的Servlet容器必须提供一个有javax.servlet.context.orderedLibs 名字的ServletContext 属性,它的值(java.util.List<java.lang.String>类型)包含一个被ServletContext代表的应用WEB-INF/lib目录里JAR文件的名字列表,它会按照它们的web fragment名字(如果fragmentJAR文件被排除在absolute-ordering就会被排除)排序,或者如果应用没有指明任何绝对或相对顺序就为null。
处理注解和fragments
Web应用可能同时包含注解和web.xml/web-fragment.xml部署描述符。如果没有部署描述符或者有一个但是没有把metadata-complete设置为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