20200311 8. 注解和可插拔性
8. 注解和可插拔性
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
类。
下面是如何使用该注解的一个示例。
@WebServlet(”/foo”)
public class CalculatorServlet extends HttpServlet{
//...
}
下面是如何使用该注解指定更多的属性的一个示例。
@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
javax.servlet.http.HttpSessionIdListener
示例:
@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
和 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 顺序(除非出现重复名异常,如上文所述)。
两种情况必须被考虑,以允许应用程序配置资源来表达它们的顺序配置。
-
绝对顺序:在
web.xml
中的<absolute-ordering>
元素。在一个web.xml
中仅能有一个<absolute-ordering>
元素。 -
相对顺序:在
web-fragment.xml
中的<ordering>
元素,一个web-fragment.xml
只能有一个<ordering>
元素。
8.2.3 装配 web.xml、 web-fragment.xml 描述符和注解
如果对于一个应用 Listener、 Servlet 和 Filter 的调用顺序是很重要的,那么必须使用部署描述符。同样,如果有必要,可以使用上面定义的顺序元素。如上所述,当时有注解定义 Listener、 Servlet 和 Filter,它们调用的顺序是未指定的。下面是用于装配应用程序的最终部署描述符的一组规则:
- 如果有关的 Listener、 Servlet 和 Filter 的顺序必须指定,那么必须指定在
web-fragment.xml
或web.xml
。 - 顺 序 将 依 据 它 们 定 义 在 描 述 符 中 的 顺 序 , 和 依 赖 于
web.xml
中 的absolute-ordering
元 素 或web-fragment.xml
中的ordering
元素,如果存在。- 匹配请求的过滤器链的顺序是它们在
web.xml
中声明的顺序。 - Servlet 在请求处理时实例化或在部署时立即实例化。在后一种情况,以它们的
load-on-startup
元素表示的顺序实例化。load-on-startup
大于 0 时 Servlet 会在部署时立即实例化,顺序由大到小,小于 0 时,在请求处理时实例化。 - 在之前发布的规范,上下文 Listener 以随机顺序调用。在 Servlet3.0, Listener 以它们在
web.xml
中声明的顺序调用,初始化方法按声明顺序调用,销毁方法按声明逆序调用。
- 匹配请求的过滤器链的顺序是它们在
8.2.4 共享库 / 运行时可插拔性
除了支持 fragment 和使用注解的外,要求之一是我们不仅能 plug-in 绑定在 WEB-INF/lib
下的,也能 plugin 框架共享副本—包括能 plug-in 到容器的如建议在 web 容器之上的 JAX-WS 、 JAX-RS 和 JSF。ServletContainerInitializer
允许处理这样的使用情况下,如下所述。
ServletContainerInitializer
类通过 jar services API 查找。对于每一个应用,应用启动时,由容器创建一个ServletContainerInitializer
实 例 。 框 架 提 供 的 ServletContainerInitializer
实 现 必 须 绑 定 在 jar 包 的META-INF/services
目录中的一个叫做 javax.servlet.ServletContainerInitializer
的文件,根据 jar services API,指定 ServletContainerInitializer
的实现。
除 ServletContainerInitializer
外,我们还有一个注解——HandlesTypes
。在 ServletContainerInitializer
实现上的 HandlesTypes
注解用于表示感兴趣的一些类,它们可能指定了 HandlesTypes
的 value
中的注解(类型、方法或自动级别的注解),或者是其类型的超类继承/实现了这些类之一。 无论是否设置了 metadata-complete
,HandlesTypes
注解将应用。
当检测一个应用的类看是否它们匹配 ServletContainerInitializer
的HandlesTypes
指定的条件时,如果应用的一个或多个可选的 JAR 包缺失,容器可能遇到类装载问题。由于容器不能决定是否这些类型的类装载失败
将阻止应用正常工作,它必须忽略它们,同时也提供一个将记录它们的配置选项。
如果 ServletContainerInitializer
实现没有 @HandlesTypes
注解,或如果没有匹配任何指定的 HandlesType
,那么它会为每个应用使用 null
值的集合调用一次。这将允许 initializer 基于应用中可用的资源决定是否需要初始化 Servlet/Filter。
在任何 Servlet 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
服务被排除于一个 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 |