Specification: Jakarta RESTful Web Services Version: 2.2-SNAPSHOT Status: DRAFT Release: December 10, 2020
Eclipse Foundation 规范许可
通过使用和/或复制本文档,或从中链接的 Eclipse Foundation 文档,您(被许可方)同意您已阅读、理解并遵守以下条款和条件。
特此授予复制和分发本文档或从中链接的 Eclipse Foundation 文档的任何媒介的许可,用于任何目的且无需支付费用或版税,前提是您在文档的任何副本或部分上包含以下内容:
-
指向原始 Eclipse Foundation 文档的链接或 URL。
-
所有现有的版权声明,或者如果没有,则包含“版权所有 (c) [$date-of-document] Eclipse Foundation, Inc. <
>”形式的声明(优先使用超文本,但允许使用文本表示)。
必须提供 NOTICE 的完整文本。我们要求在您根据本文档内容或其任何部分进行实现而创建的任何软件、文档或其他项目或产品中提供作者署名。
根据本许可,未授予创建 Eclipse Foundation 文档的修改版或衍生作品的权利,除非任何人可以准备和分发此文档的衍生作品和部分,用于实现规范的软件中、随附的材料中以及该软件的文档中,前提是所有此类作品都包含以下通知。但是,明确禁止发布此文档的衍生作品作为技术规范使用。
通知内容如下:
“版权所有 (c) 2018 Eclipse Foundation。本软件或文档包含从 [Eclipse Foundation 规范文档的标题和 URI] 复制或派生的材料。”
1. 简介
本规范定义了一套 Java API,用于开发符合 Representational State Transfer[1] (REST) 架构样式的 Web 服务。假定读者熟悉 REST;有关 REST 架构样式和 RESTful Web 服务的更多信息,请参阅:
1.1. 状态
这是 2.1 版本的最终发布。此版本的缺陷跟踪系统可在
查阅。相应的 Javadocs 在线地址为:
参考实现可从以下网址获取:
专家组寻求社区对本规范任何方面的反馈,请将意见发送至:
1.2. 目标
API 的目标如下:
- POJO-based
-
API 将提供一套注解和相关的类/接口,可用于 POJO 以将其暴露为 Web 资源。规范将定义对象生命周期和范围。
- HTTP-centric
-
规范将假定 HTTP[4] 是底层网络协议,并将提供 HTTP 与 URI[5] 元素和相应的 API 类及注解之间的清晰映射。API 将为常见的 HTTP 使用模式提供高级支持,并将足够灵活以支持各种 HTTP 应用,包括 WebDAV[6] 和 Atom Publishing Protocol[7]。
- Format independence
-
API 将适用于各种 HTTP 实体体内容类型。它将提供必要的插件功能,允许应用程序以标准方式添加其他类型。
- Container independence
-
使用 API 的制品可以部署在各种 Web 级容器中。规范将定义制品如何在 Servlet[8] 容器中部署以及如何作为 JAX-WS[9] Provider 部署。
- Inclusion in Jakarta EE
-
规范将定义托管在 Jakarta EE 容器中的 Web 资源类的环境,并指定如何在 Web 资源类中使用 Jakarta EE 功能和组件。
1.3. 非目标
以下是非目标:
- Support for Java versions prior to Java SE 8
-
API 将广泛使用注解和 lambda 表达式,这些需要 Java SE 8 或更高版本。
- Description, registration and discovery
-
本规范既不定义也不要求任何服务描述、注册或发现能力。
- HTTP Stack
-
本规范不定义新的 HTTP 堆栈。HTTP 协议支持由托管使用 API 开发的制品的容器提供。
- Data model/format classes
-
API 不会定义支持实体体内容操作的类,而是提供插件功能,允许使用 API 开发的制品使用此类。
1.4. 约定
本文档中的关键字 `MUST', `MUST NOT', `REQUIRED', `SHALL', `SHALL NOT', `SHOULD', `SHOULD NOT', `RECOMMENDED', `MAY', 和 `OPTIONAL' 的解释应遵循 RFC 2119[10] 的描述。
Java 代码和示例数据片段的格式如图 [1.1] 所示。
package com.example.hello;
public class Hello {
public static void main(String args[]) {
System.out.println("Hello World");
}
}
一般形式为 `http://example.org/…' 和 `http://example.com/…' 的 URI 代表应用程序或上下文相关的 URI。
本文档的所有部分均具有规范性,但示例、注释和明确标记为“非规范性”的部分除外。非规范性注释格式如下:
注意: 这是一个注释。
1.5. 术语
- 资源类
-
一个使用 JAX-RS 注解来实现相应 Web 资源的 Java 类,请参阅 [resources] 章节。
- 根资源类
-
一个使用
@Path注解的资源类。根资源类提供资源类树的根,并提供对子资源的访问,请参阅 [resources] 章节。 - 请求方法设计符
-
一个使用
@HttpMethod注解的运行时注解。用于标识由资源方法处理的 HTTP 请求方法。 - 资源方法
-
一个资源类的方法,该方法使用请求方法设计符进行注解,用于处理相应资源的请求,请参阅 资源方法。
- 子资源定位符
-
一个资源类的方法,用于定位相应资源的子资源,请参阅 子资源。
- 子资源方法
-
一个资源类的方法,用于处理相应子资源的请求,请参阅 子资源。
- 提供者
-
JAX-RS 扩展接口的实现。提供者扩展了 JAX-RS 运行时的能力,并在 提供者 章节进行了描述。
- Filter
-
用于过滤请求和响应的提供者。
- 实体拦截器
-
用于拦截对消息体读取器和写入器调用的提供者。
- 调用
-
一个客户端 API 对象,可以配置为发出 HTTP 请求。
- WebTarget
-
调用的接收者,由 URI 标识。
- Link
-
一个带有附加元数据(如媒体类型、关系、标题等)的 URI。
1.6. 专家组组员
本规范是作为 JSR 370 在 Java Community Process 下开发的。它是 JSR 370 专家组组员协作工作的成果。以下是当前的专家组组员:
— Sergey Beryozkin (Talend SA)
— Adam Bien (个人成员)
— Sebastian Dashner (个人成员)
— Markus Karg (个人成员)
— Casey Lee (Vision Service Plan)
— Marcos Luna (个人成员)
— Andy McCright (IBM)
— Julian Reschke (个人成员)
— Alessio Soldano (Red Hat)
以下是 JSR 339 专家组的前组员:
— Jan Algermussen (个人成员)
— Florent Beniot (OW2)
— Sergey Beryozkin (Talend SA)
— Adam Bien (个人成员)
— Bill Burke (Red Hat)
— Clinton L. Combs (个人成员)
— Jian Wu Dai (IBM)
— Bill De Hora (个人成员)
— Markus Karg (个人成员)
— Sastry Malladi (eBay, Inc)
— Julian Reschke (个人成员)
— Guilherme de Azevedo Silveira (个人成员)
— Synodinos, Dionysios G. (个人成员)
JAX-RS 1.X 是作为 JSR 311 在 Java Community Process 下开发的。以下是 JSR 311 专家组的组员:
— Heiko Braun (Red Hat Middleware LLC)
— Larry Cable (BEA Systems)
— Roy Fielding (Day Software, Inc.)
— Harpreet Geekee (Nortel)
— Nickolas Grabovas (个人成员)
— Mark Hansen (个人成员)
— John Harby (个人成员)
— Hao He (个人成员)
— Ryan Heaton (个人成员)
— David Hensley (个人成员)
— Stephan Koops (个人成员)
— Changshin Lee (NCsoft Corporation)
— Francois Leygues (Alcatel-Lucent)
— Jerome Louvel (个人成员)
— Hamid Ben Malek (Fujitsu Limited)
— Ryan J. McDonough (个人成员)
— Felix Meschberger (Day Software, Inc.)
— David Orchard (BEA Systems)
— Dhanji R. Prasanna (个人成员)
— Julian Reschke (个人成员)
— Jan Schulz-Hofen (个人成员)
— Joel Smith (IBM)
— Stefan Tilkov (innoQ Deutschland GmbH)
1.7. 致谢
在此 JSR 开发过程中,我们得到了许多个人的大力贡献和建议。特别感谢 Marek Potociar 和 Michal Gajdos。还有 Gunnar Morling、Ondrej Mihalyi、Arjan Tijms、Guillermo Gonzales de Aguero、Christian Kaltepoth 以及许多许多其他人。最后但同样重要的是,感谢 JSR 370 专家组的所有贡献。
在 JSR 339 开发过程中,我们收到了许多出色的建议。特别感谢 Oracle 的 Martin Matula、Gerard Davison、Jakub Podlesak 和 Pavel Bucek,以及 Red Hat 的 Pete Muir 和 Emmanuel Bernard。还要感谢 Gunnar Morling 和 Ron Sigal (Red Hat) 关于如何改进资源验证的建议,以及 Mattias Arthursson 关于超媒体的见解。
在 JSR 311 开发过程中,我们在 JSR 和 Jersey (RI) 邮件列表中收到了许多出色的建议,特别感谢 James Manger (Telstra) 和 Reto Bachmann-Gmür (Trialox) 的贡献。以下个人(当时均在 Sun Microsystems)也做出了宝贵的技术贡献:Roberto Chinnici、Dianne Jiao (TCK)、Ron Monzillo、Rajiv Mordani、Eduardo Pelegri-Llopart、Jakub Podlesak (RI) 和 Bill Shannon。
GenericEntity 类受到 Google Guice TypeLiteral 类的启发。感谢 Bob Lee 和 Google 将此类捐赠给 JAX-RS。
2. 应用
JAX-RS 应用由一个或多个资源(参见 资源 章节)和零个或多个提供者(参见 提供者 章节)组成。本章描述适用于整个应用的 JAX-RS 方面,后续章节描述 JAX-RS 应用的特定方面和对 JAX-RS 实现的要求。
2.1. 配置
构成 JAX-RS 应用的资源和提供者通过应用程序提供的 Application 子类进行配置。实现 MAY 提供查找资源类和提供者的替代机制(例如,运行时类扫描),但 Application 的使用是唯一的便携式配置方式。
2.2. 验证
具体的应用要求在此规范和 JAX-RS Javadocs 中详细说明。实现 MAY 执行超出本文档所述的验证步骤。
JAX-RS 实现 MAY 在检测到两个或多个资源可能导致执行 请求匹配 中描述的算法时出现歧义时报告错误条件。例如,如果同一资源类中的两个资源方法在对该部分描述的算法的所有相关注解中具有相同的(甚至重叠的)值。确切的验证步骤集以及错误报告机制取决于实现。
2.3. 发布
应用程序的发布方式因应用程序是在 Java SE 环境中运行还是在容器中运行而异。本节介绍替代的发布方式。
2.3.1. Java SE
在 Java SE 环境中,可以通过 RuntimeDelegate 的 createEndpoint 方法获取端点类的已配置实例。应用程序提供 Application 的实例和所需的端点类型。实现 MAY 支持任意期望类型的零个或多个端点类型。
结果端点类实例如何用于发布应用程序不在本规范的范围之内。
2.3.2. Servlet
JAX-RS 应用程序打包为 .war 文件中的 Web 应用程序。应用程序类打包在 WEB-INF/classes 或 WEB-INF/lib 中,必需的库打包在 WEB-INF/lib 中。有关 Web 应用程序打包的完整详细信息,请参阅 Servlet 规范。
建议实现支持 Servlet 3 框架的可插拔机制,以实现容器之间的可移植性并利用容器提供的类扫描功能。在使用可插拔机制时,必须满足以下条件:
-
如果没有
Application子类存在,JAX-RS 实现必须动态添加一个 servlet,并将其名称设置为:javax.ws.rs.core.Application并自动发现打包在应用程序中的所有根资源类和提供者。此外,应用程序必须打包一个
web.xml文件,该文件指定已添加 servlet 的 servlet 映射。 suchweb.xml文件的示例为:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/myresources/*</url-pattern>
</servlet-mapping>
</web-app>
-
If an
Applicationsubclass is present-
如果已有一个 servlet 处理此应用程序。即,一个 servlet 具有名为
javax.ws.rs.Application且其值为
Application子类的完全限定名称,则 JAX-RS 实现无需其他配置步骤。 -
如果没有 servlet 处理此应用程序,JAX-RS 实现必须动态添加一个 servlet,其完全限定名称必须是
Application子类的名称。如果Application子类被注解为@ApplicationPath,实现必须使用此注解的值加上 “/*” 来定义已添加服务器的映射。否则,应用程序必须打包一个web.xml文件,该文件指定 servlet 映射。例如,如果org.example.MyApplication是Application子类的名称,则示例web.xml将是:
-
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
<servlet-name>org.example.MyApplication</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>org.example.MyApplication</servlet-name>
<url-pattern>/myresources/*</url-pattern>
</servlet-mapping>
</web-app>
当存档中存在 Application 子类时,如果 Application.getClasses 和 Application.getSingletons 都返回一个空集合,则必须包含 Web 应用程序中打包的所有根资源类和提供者,并且 JAX-RS 实现必须通过如上所述扫描 .war 文件自动发现它们。如果 getClasses 或 getSingletons 返回一个非空集合,则必须仅包含返回的类或单例。如果存在一个 Application 子类,并且 Application.getClasses 和 Application.getSingletons 都返回一个空集合,则 Web 应用程序中打包的所有根资源类和提供者都必须包含在已发布的 JAX-RS 应用程序中,并且 JAX-RS 实现必须通过扫描 .war 文件自动发现它们。如果 getClasses 或 getSingletons 返回一个非空集合,则仅包含返回的类或单例。
下表总结了 Servlet 3 框架的可插拔机制:
条件 |
操作 |
Servlet 名称 |
web.xml |
无 |
添加 servlet |
|
对于 servlet 映射是必需的 |
由现有 servlet 处理的 |
(无) |
(已定义) |
不需要 |
未由现有 servlet 处理的 |
添加 servlet |
子类名称 |
如果无 |
如果未使用 Servlet 3 框架可插拔机制(例如,在 Servlet 3.0 之前的容器中),则 web.xml 描述符的 servlet-class 或 filter-class 元素应该命名 JAX-RS 实现提供的 servlet 或 filter 类。Application 子类应使用名为 javax.ws.rs.Application 的 init-param 来标识。
请注意,上述 Servlet 3 框架可插拔机制基于 servlet 而非 filter。偏好使用实现提供的 filter 类的应用程序必须使用 Servlet 3.0 之前的配置机制。
3. 资源
使用 JAX-RS,Web 资源实现为资源类,请求由资源方法处理。本章详细介绍了资源类和资源方法。
3.1. 资源类
资源类是使用 JAX-RS 注解来实现相应 Web 资源的 Java 类。资源类是 POJO,至少有一个方法被注解为 @Path 或请求方法设计符。
3.1.1. 生命周期和环境
默认情况下,为每个请求创建一个新的资源类实例。首先调用构造函数(参见 构造函数),然后注入任何请求的依赖项(参见 字段和 Bean 属性),然后调用适当的方法(参见 资源方法),最后对象可供垃圾回收。
实现 MAY 提供其他资源类生命周期,指定这些的机制不在本规范范围内。例如,基于控制反转框架的实现可以支持该框架提供的所有生命周期选项。
3.1.2. 构造函数
根资源类由 JAX-RS 运行时实例化,并且必须有一个公共构造函数,JAX-RS 运行时可以为该构造函数提供所有参数值。请注意,根据此规则,允许使用零参数构造函数。
公共构造函数 MAY 包含被注解为以下之一的参数: @Context、@HeaderParam、@CookieParam、@MatrixParam、@QueryParam 或 @PathParam。但是,根据资源类的生命周期和并发性,每个请求的信息在构造函数中可能没有意义。如果有一个以上的公共构造函数是合适的,则实现必须使用参数最多的那个。在具有相同参数数量的合适构造函数之间进行选择是实现特定的,实现应生成关于此类歧义的警告。
非根资源类由应用程序实例化,并且不需要上述公共构造函数。
3.2. 字段和 Bean 属性
当实例化资源类时,被注解为以下任一注解的字段和 Bean 属性的值将根据注解的语义进行设置:
由于注入发生在对象创建时,因此这些注解(@Context 除外)在资源类字段和 Bean 属性上的使用仅支持默认的每个请求资源类生命周期。如果资源类的生命周期不同,实现应警告在资源类字段或 Bean 属性上使用这些注解。
JAX-RS 实现仅需要设置其运行时创建的实例的注解字段和 Bean 属性值。由子资源定位符(参见 子资源)返回的对象期望由其创建者初始化。
每个上述注解的有效参数类型列在相应的 Javadoc 中,但通常(不包括 @Context)支持以下类型:
-
存在
ParamConverterProvider可用的ParamConverter的类型。有关更多信息,请参阅这些类的 Javadoc。 -
原始类型。
-
具有接受单个
String参数的构造函数的类型。 -
具有名为
valueOf或fromString的静态方法的类型,该方法接受单个String参数并返回该类型的实例。如果两个方法都存在,则使用valueOf,除非该类型是枚举,在这种情况下使用fromString[1]。 -
List<T>、Set<T>或SortedSet<T>,其中T满足 1、3 或 4。
DefaultValue 注解可用于为上述某些类型提供默认值,有关用法详细信息以及在缺少此注解和请求数据的情况下生成值的规则,请参阅 DefaultValue 的 Javadoc。Encoded 注解可用于禁用 @MatrixParam、@QueryParam 和 @PathParam 注解的字段和属性的自动 URI 解码。
使用上述 5 个步骤之一在构造字段或属性值期间抛出的 WebApplicationException 将被直接处理,如 异常 中所述。在构造字段或属性值期间抛出的其他异常(使用上述 5 个步骤之一)将被视为客户端错误:如果字段或属性被注解为 @MatrixParam、@QueryParam 或 @PathParam ,则实现必须生成一个 NotFoundException(404 状态)实例来包装抛出的异常,并且没有实体;如果字段或属性被注解为 @HeaderParam 或 @CookieParam ,则实现必须生成一个 BadRequestException(400 状态)实例来包装抛出的异常,并且没有实体。异常必须按照 异常 中的描述进行处理。
3.3. 资源方法
资源方法是资源类中被请求方法设计符注解的方法。它们用于处理请求,并且必须符合本节描述的某些限制。
请求方法设计符是使用 @HttpMethod 注解的运行时注解。JAX-RS 为常见的 HTTP 方法定义了一组请求方法设计符:@GET、@POST、@PUT、@DELETE、@PATCH、@HEAD 和 @OPTIONS。用户可以定义自己的自定义请求方法设计符,包括常见 HTTP 方法的替代设计符。
3.3.2. 参数
当调用资源方法时,使用 @FormParam 或 [resource_field] 章节中列出的注解之一进行注解的参数会根据注解的语义从请求中映射。与字段和 Bean 属性类似:
-
DefaultValue注解可用于为参数提供默认值。 -
Encoded注解可用于禁用参数值的自动 URI 解码。 -
在构造参数值期间抛出的异常的处理方式与在构造字段或 Bean 属性值期间抛出的异常的处理方式相同,请参阅 字段和 Bean 属性。在构造
@FormParam注解的参数值期间抛出的异常的处理方式与参数被注解为@HeaderParam时相同。
3.3.2.1. 实体参数
未被 @FormParam 或 字段和 Bean 属性 章节中列出的任何注解注解的参数,称为实体参数,其值从请求实体体中映射。实体体与 Java 类型之间的转换由实体提供者负责,请参阅 实体提供者。资源方法最多必须有一个实体参数。
3.3.3. 返回类型
资源方法 MAY 返回 void、Response、GenericEntity 或其他 Java 类型,这些返回类型映射到响应实体体,如下所示:
void-
导致空实体体,状态码为 204。
Response-
导致实体体从
Response的实体属性映射,状态码由Response的状态属性指定。null返回值导致 204 状态码。如果Response的状态属性未设置:对于非null实体属性,使用 200 状态码;如果实体属性为null,则使用 204 状态码。 GenericEntity-
导致实体体从
GenericEntity的Entity属性映射。如果返回值不为null,则使用 200 状态码;null返回值导致 204 状态码。 - Other
-
导致实体体从返回实例的类或其类型参数
T(如果返回类型是CompletionStage<T>)映射(参见 CompletionStage);如果类是匿名内部类,则使用其超类。如果返回值不为null,则使用 200 状态码;null返回值导致 204 状态码。
需要提供附加元数据给响应的方法应返回 Response 实例,ResponseBuilder 类提供了一种使用构建器模式创建 Response 实例的便捷方法。
Java 对象与实体体之间的转换由实体提供者负责,请参阅 实体提供者。资源方法的返回类型和返回实例的类型用于确定提供给 MessageBodyWriter 的 isWritable 方法的原始类型和泛型类型,如下所示:
返回类型 |
返回实例 使用[2] |
原始类型 |
泛型类型 |
|
|
|
|
|
|
|
|
|
|
实例的类 |
实例的类 |
|
返回类型或子类 |
实例的类 |
返回类型的泛型类型 |
为了说明以上内容,请考虑一个始终返回 ArrayList<String> 实例的方法,无论是直接返回还是包装在 Response 和 GenericEntity 的某种组合中。结果的原始类型和泛型类型如下所示。
返回类型 |
返回实例 |
原始类型 |
*泛型类型 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3.3.4. 异常
资源方法、子资源方法或子资源定位符可以抛出任何已检查或未检查的异常。实现必须捕获所有异常并按以下顺序处理:
-
WebApplicationException及其子类的实例必须按如下方式映射到响应。如果异常的response属性不包含实体,并且存在适用于WebApplicationException或相应子类的异常映射提供者(参见 异常映射提供者),则实现必须使用该提供者创建一个新的Response实例,否则直接使用response属性。生成的Response实例随后将按照 返回类型 进行处理。 -
如果存在适用于异常或其超类的异常映射提供者(参见 异常映射提供者),则实现必须使用其泛型类型为异常的最近超类的提供者来创建
Response实例,然后将其按照 返回类型 进行处理。如果在创建Response期间异常映射提供者抛出异常,则向客户端返回服务器错误(状态码 500)响应。 -
未映射的未检查异常和错误必须被重新抛出,并允许它们传播到底层容器。
-
未映射且不能直接抛出的已检查异常和可抛出对象必须包装在容器特定的异常中,然后抛出并允许其传播到底层容器。基于 Servlet 的实现必须使用
ServletException作为包装器。基于 JAX-WSProvider的实现必须使用WebServiceException作为包装器。
注意: 第 3 点和第 4 点允许使用现有的容器设施(例如 Servlet filter 或错误页面)来处理错误(如果需要)。
3.4. URI 模板
根资源类通过 @Path 注解锚定在 URI 空间中。注解的值是一个相对 URI 路径模板,其基础 URI 由部署上下文和应用程序路径的组合提供(请参阅 @ApplicationPath 注解)。
URI 路径模板是一个带有零个或多个嵌入式参数的字符串,当所有参数都替换为值时,它是一个有效的 URI[5] 路径。@Path 注解的 Javadoc 描述了它们的语法。例如:
@Path("widgets/{id}")
public class Widget {
...
}
在上面的示例中,Widget 资源类由相对 URI 路径 widgets/xxx 标识,其中 xxx 是 id 参数的值。
注意: 由于 \{ 和 } 不是 URI[5] 的保留或未保留部分的组成部分,因此它们不会出现在有效的 URI 中。
注解的值会自动编码,例如,以下两行是等效的:
@Path("widget list/{id}")
@Path("widget%20list/{id}")
模板参数可以可选地指定用于匹配其值的正则表达式。默认值匹配任何文本并终止于路径段的末尾,但其他值可以用于改变此行为,例如:
@Path("widgets/{path:.+}")
public class Widget {
...
}
在上面的示例中,Widget 资源类将匹配任何以 widgets 开头且至少包含一个额外路径段的请求;path 参数的值将是 widgets 之后的请求路径。例如,给定请求路径 widgets/small/a,path 的值将是 small/a。
URI 路径参数的值可以通过 @PathParam 在字段、属性或方法参数上进行注入。请注意,如果 URI 模板用于方法,则在字段或属性中注入的路径参数可能不可用(设置为 null)。以下示例说明了这种情况:
@Path("widgets")
public class WidgetsResource {
@PathParam("id") String id;
@GET
public WidgetList getWidgets() {
... // id is null here
}
@GET
@Path("{id}")
public Widget findWidget() {
return new WidgetResource(id);
}
}
3.4.1. 子资源
资源类中被 @Path 注解的方法要么是子资源方法,要么是子资源定位符。子资源方法直接处理 HTTP 请求,而子资源定位符返回一个对象或类,该对象或类将处理 HTTP 请求。请求方法设计符(例如 @GET)的存在与否区分了两者:
- Present
-
这些方法称为子资源方法,它们被当作普通资源方法处理(参见 资源方法),只是该方法仅在请求 URI 匹配通过连接资源类的 URI 模板和方法的 URI 模板创建的 URI 模板时才被调用[3]。
- Absent
-
这些方法称为子资源定位符,用于动态解析将处理请求的对象。子资源定位符可以返回对象或类;如果返回的是类,则实现将使用合适的构造函数获取对象,如 构造函数 中所述。无论哪种情况,结果对象都用于处理请求或进一步解析将处理请求的对象,请参阅 将请求匹配到 Java 方法 以获取更多详细信息。+ 当返回对象时,实现必须动态确定其类,而不是依赖于静态子资源定位符返回类型,因为返回的实例可能是声明类型的子类,并且可能具有不同的注解,有关注解继承的规则,请参阅 注解继承。子资源定位符可以拥有与普通资源方法相同的参数(参见 资源方法),只是它们不得具有实体参数。
以下示例说明了区别:
@Path("widgets")
public class WidgetsResource {
@GET
@Path("offers")
public WidgetList getDiscounted() {...}
@Path("{id}")
public WidgetResource findWidget(@PathParam("id") String id) {
return new WidgetResource(id);
}
}
public class WidgetResource {
public WidgetResource(String id) {...}
@GET
public Widget getDetails() {...}
}
在上面,对 widgets/offers 资源的 GET 请求由 WidgetsResource 资源类的 getDiscounted 子资源方法直接处理,而对 widgets/xxx 资源的 GET 请求由 WidgetResource 资源类的 getDetails 方法处理。
注意: 一组用相同 URI 模板值注解的子资源方法在功能上等同于一个用相同 URI 模板值注解的子资源定位符,该定位符返回一个具有相同子资源方法集的资源类实例。
3.5. 声明媒体类型能力
应用程序类可以使用 @Consumes 和 @Produces 注解分别声明支持的请求和响应媒体类型。这些注解可以应用于资源方法、资源类或实体提供者(参见 声明媒体类型能力)。在资源方法上使用这些注解会覆盖资源类或实体提供者上的任何注解,用于方法参数或返回类型。如果缺少这些注解中的任何一个,则假定支持任何媒体类型(/*)。
以下示例说明了这些注解的用法:
@Path("widgets")
@Produces("application/widgets+xml")
public class WidgetsResource {
@GET
public Widgets getAsXML() {...}
@GET
@Produces("text/html")
public String getAsHtml() {...}
@POST
@Consumes("application/widgets+xml")
public void addWidget(Widget widget) {...}
}
@Provider
@Produces("application/widgets+xml")
public class WidgetsProvider implements MessageBodyWriter<Widgets> {...}
@Provider
@Consumes("application/widgets+xml")
public class WidgetProvider implements MessageBodyReader<Widget> {...}
在上面:
-
getAsXML资源方法将被调用用于指定application/widgets+xml响应媒体类型的GET请求。它返回一个Widgets实例,该实例将使用WidgetsProvider类映射到该格式(有关MessageBodyWriter的更多信息,请参阅 实体提供者)。 -
getAsHtml资源方法将被调用用于指定text/html响应媒体类型的GET请求。它返回一个包含text/html的String,将使用MessageBodyWriter<String>的默认实现写入。 -
addWidget资源方法将被调用用于包含application/widgets+xml媒体类型实体的POST请求。widget参数的值将使用WidgetProvider类从请求实体映射(有关MessageBodyReader的更多信息,请参阅 实体提供者)。
实现不得调用其 @Produces 的有效值与请求 Accept Header 不匹配的方法。实现不得调用其 @Consumes 的有效值与请求 Content-Type Header 不匹配的方法。
在接受多种媒体类型时,客户端可以通过使用称为 q 参数的相对质量因子来指示偏好。q 参数的值,或 q-value,用于对接受的类型集进行排序。例如,客户端可以通过 1 的相对质量因子指示对 application/widgets+xml 的偏好,而对 application/xml 则为 0.8。Q-value 的范围从 0(不受欢迎)到 1(非常理想),省略时默认为 1。与 WidgetsResource 类匹配的 GET 请求,其 accept header 为 text/html; q=1, application/widgets+xml; q=0.8,将根据 q 的值调用 getAsHtml 方法而不是 getAsXML。
服务器也可以使用 qs 参数指示媒体类型偏好;仅当客户端接受多种媒体类型且 q-value 相同时,才会检查服务器偏好。考虑以下示例:
@Path("widgets2")
public class WidgetsResource2 {
@GET
@Produces("application/xml", "application/json")
public Widgets getWidget() {...}
}
假设客户端发出带有 accept header application/*; q=0.5, text/html 的 GET 请求。根据此请求,服务器确定 application/xml 和 application/json 都被客户端同样偏好,q-value 为 0.5。通过在 @Produces 注解中指定服务器相对质量因子,可以控制选择哪个响应媒体类型:
@Path("widgets2")
public class WidgetsResource2 {
@GET
@Produces("application/xml; qs=1", "application/json; qs=0.75")
public Widgets getWidget() {...}
}
在此示例中,更新了 @Produces 的值,并且响应带有包含 application/*; q=0.5 的 accept header 的 GET 请求时,JAX-RS 实现必须选择媒体类型 application/xml,因为其 qs-value 更高。请注意,qs-value 与 q-value 一样,是相对的,因此仅能与同一 @Produces 注解实例中的其他 qs-value 进行比较。有关更多信息,请参阅 确定 MediaType 的响应。
3.6. 注解继承
JAX-RS 注解可以用于超类或已实现接口的方法和方法参数。只要方法及其参数本身没有 JAX-RS 注解,这些注解就会被相应的子类或实现类方法继承。超类上的注解优先于已实现接口上的注解。在多个已实现接口中定义的冲突注解的优先级是实现特定的。请注意,不支持类或接口注解的继承。
如果子类或实现方法具有任何 JAX-RS 注解,则所有超类或接口方法上的注解都将被忽略。例如:
public interface ReadOnlyAtomFeed {
@GET @Produces("application/atom+xml")
Feed getFeed();
}
@Path("feed")
public class ActivityLog implements ReadOnlyAtomFeed {
public Feed getFeed() {...}
}
在上面,ActivityLog.getFeed 从接口继承了 @GET 和 @Produces 注解。相反:
@Path("feed")
public class ActivityLog implements ReadOnlyAtomFeed {
@Produces("application/atom+xml")
public Feed getFeed() {...}
}
在上面,ReadOnlyAtomFeed.getFeed 上的 @GET 注解不会被 Activity-Log.getFeed 继承,并且由于它重新定义了 @Produces 注解,因此需要自己的请求方法设计符。
为了与其他 Jakarta EE 规范保持一致,建议始终重复注解而不是依赖于注解继承。
3.7. 将请求匹配到资源方法
本节描述了请求如何匹配到资源类和方法。实现不要求使用编写的算法,但必须产生与算法产生的结果等效的结果。
3.7.2. 请求匹配
通过将规范化后的请求 URI(参见 请求预处理)、任何请求实体的媒体类型以及请求的响应实体格式与资源类及其方法的元数据注解进行比较,将请求匹配到相应的资源方法或子资源方法。如果找不到匹配的资源方法或子资源方法,则会返回适当的错误响应。此算法报告的所有异常都必须按照 异常 中的描述进行处理。
请求到资源方法的匹配过程分为三个阶段:
-
识别一组匹配请求的候选根资源类
- 输入
-
\(U=\mbox{请求 URI 路径},C=\{\mbox{根资源类}\}\)
- 输出
-
\(U=\mbox{最终捕获组尚未匹配}, C'=\{\mbox{到目前为止已匹配的根资源类}\}\)
-
设置 \(E=\{\}\)。
-
对于 \(C\) 中的每个类 \(Z\),添加一个正则表达式(使用 将 URI 模板转换为正则表达式 中描述的 \(R(A)\) 函数计算)到 \(E\),如下所示:
-
添加 \(R(T_Z)\),其中 \(T_Z\) 是为类 \(Z\) 指定的 URI 路径模板。
请注意,\(C\) 中的两个或多个类可能添加相同的正则表达式到 \(E\),如果它们被注解为相同的 URI 路径模板(忽略变量名)。
-
-
通过匹配 \(E\) 中的每个成员与 \(U\) 来过滤 \(E\),如下所示:
-
删除不匹配 \(U\) 的成员。
-
删除其最后一个正则表达式捕获组(以下简称捕获组)的值既不是空也不是 / ,且类 \(Z\) 没有子资源方法或定位符的成员。
-
-
如果 \(E\) 为空,则找不到匹配的资源,算法终止,实现必须生成
NotFoundException(404 状态)且无实体。 -
使用每个成员中的字面字符数[5](降序)作为主键,捕获组的数量(降序)作为次键,以及具有非默认正则表达式(即非 ([ /]+?))的捕获组的数量(降序)作为第三键,对 \(E\) 进行排序。
-
将 \(R_{\mbox{match}}\) 设置为 \(E\) 的第一个成员,并将 \(U\) 设置为 \(U\) 匹配 \(R_{\mbox{match}}\) 时的最后一个捕获组的值。设 \(C'\) 为类 \(Z\) 的集合,使得 \(R(T_Z)=R_{\mbox{match}}\)。根据定义,\(C'\) 中的所有根资源类都必须被注解为具有相同的 URI 路径模板(忽略变量名)。
-
-
获取请求的候选资源方法集
- 输入
-
\(U=\mbox{最终捕获组尚未匹配}, C'=\{\mbox{到目前为止已匹配的根资源类}\}\)
- 输出
-
\(M=\{\mbox{候选资源方法}\)}
-
[check_null] 如果 \(U\) 为 null 或 /,则设置 \(\[M = \{\mbox{所有类 $C'$ 的资源方法(不包括子资源方法)}\}]\) 并且如果 \(M \neq \{\}\) 则转到步骤 [find_method]
-
设置 \(E=\{\}\)。
-
对于 \(C'\) 中的每个类 \(Z'\),如下为每个子资源方法和定位符添加正则表达式到 \(E\):
-
对于每个子资源方法 \(D\),添加 \(R(T_D)\),其中 \(T_D\) 是子资源方法的 URI 路径模板。
-
对于每个子资源定位符 \(L\),添加 \(R(T_L)\),其中 \(T_L\) 是子资源定位符的 URI 路径模板。
-
-
通过匹配 \(E\) 中的每个成员与 \(U\) 来过滤 \(E\),如下所示:
-
删除不匹配 \(U\) 的成员。
-
删除来自 \(T_D\)(在步骤 [t_method_items] 中添加)且最后一个捕获组值既不是空也不是 / 的成员。
-
-
如果 \(E\) 为空,则找不到匹配的资源,算法终止,并通过生成
NotFoundException(404 状态)且无实体。 -
使用每个成员中的字面字符数(降序)作为主键,捕获组的数量(降序)作为次键,具有非默认正则表达式(即非 ([ /]+?))的捕获组的数量(降序)作为第三键,以及每个成员的来源作为第四键(将来自子资源方法的成员排在来自子资源定位符的成员之前)对 \(E\) 进行排序。
-
将 \(R_{\mbox{match}}\) 设置为 \(E\) 的第一个成员。
-
设置 \(M\) 如下:\(M = \{\mbox{所有类 $C'$ 的子资源方法(不包括子资源定位符)}\}]\) 并且如果 \(M \neq \{\}\) 则转到步骤 3。
-
令 \(L\) 为一个子资源定位符,使得 \(R_{\mbox{match}} = R(T_L)\)。实现应报告错误,如果存在一个以上的子资源定位符满足此条件。将 \(U\) 设置为 \(U\) 匹配 \(R(T_L)\) 时的最后一个捕获组的值,并将 \(C'\) 设置为仅包含定义 \(L\) 的类的单例集。
-
转到步骤 2a。
-
-
确定将处理请求的方法
- 输入
-
\(M=\mbox{候选资源方法}\)
- 输出
-
\(O=\mbox{匹配的资源类实例}, D=\mbox{从 $M$ 匹配的资源方法}\)
-
通过删除不满足以下标准的成员来过滤 \(M\)
-
请求方法是受支持的。如果没有方法支持请求方法,实现必须生成
NotAllowedException(405 状态)且无实体。请注意对HEAD和OPTIONS的额外支持,请参阅 HEAD 和 OPTIONS。 -
请求实体体(如果存在)的媒体类型是支持的输入数据格式(参见 声明媒体类型能力)。如果没有方法支持请求实体体的媒体类型,实现必须生成
NotSupportedException(415 状态)且无实体。 -
至少一个可接受的响应实体体媒体类型是支持的输出数据格式(参见 声明媒体类型能力)。如果没有方法支持可接受的响应实体体媒体类型之一,实现必须生成
NotAcceptableException(406 状态)且无实体。
-
-
如果在过滤后集合 \(M\) 包含多于一个元素,则按如下方式降序排序。首先,让我们将客户端媒体类型定义为请求
AcceptHeader 所表示的,将服务器媒体类型定义为资源方法上的@Produces注解所表示的。令客户端媒体类型为 \(\mbox{$n$/$m$;q=$v_1$}\) 的形式,服务器媒体类型为 \(\mbox{$n$/$m$;qs=$v_2$}\) 的形式,组合媒体类型为 \(\mbox{$n$/$m$;q=$v_1$;qs=$v_2$;d=$v_3$}\) 的形式,其中距离因子 \(d\) 定义如下。对于这些类型中的任何一个,\(m\) 可以是 \(*\),或者 \(m\) 和 \(n\) 可以是 \(*\),并且如果省略,则 q 和 qs 的值假定为 \(1.0\)。令 \(S(p_1, p_2)\) 在客户端媒体类型 \(p_1\) 和服务器媒体类型 \(p_2\) 上定义为,如果 \(p_1\) 和 \(p_2\) 兼容,则返回最具体组合类型以及距离因子(如果兼容),否则返回 \({\perp}\)。例如:* \(S(\mbox{text/html;q=1}, \mbox{text/html;qs=1}) = \mbox{text/html;q=1;qs=1;d=0}\),* \(S(\mbox{text/*;q=0.5}, \mbox{text/html;qs=0.8}) = \mbox{text/html;q=0.5;qs=0.8;d=1}\),* \(S(\mbox{*/*;q=0.2}, \mbox{text/*;qs=0.9}) = \mbox{text/*;q=0.2;qs=0.9;d=1}\),* \(S(\mbox{text/*;q=0.4}, \mbox{application/*;qs=0.3}) = {\perp}\)。
+ 其中 \(d\) 因子对应于与具体类型或子类型匹配的通配符数量。请注意,q 和 qs 不匹配,而是简单地组合在结果媒体类型中。可以根据以下方式定义组合媒体类型上的总顺序:
+ 我们写 \(\mbox{$n_1$/$m_1$};q=$v_1$;qs=$v_1'$;d=$v_1''$} \ge \mbox{$n_2$/$m_2$};q=$v_2$;qs=$v_2'$;d=$v_2''$}\) 如果以下任一有序条件成立:
-
\(\mbox{$n_1$/$m_1$} \succ \mbox{$n_2$/$m_2$}\),其中偏序 \(\succ\) 定义为 \(\mbox{$n$/$m$} \succ \mbox{$n$/*} \succ \mbox{*/*}\),
-
\(\mbox{$n_2$/$m_2$} \nsucc \mbox{$n_1$/$m_1$}\) 且 \(v_1 > v_2\),
-
\(\mbox{$n_2$/$m_2$} \nsucc \mbox{$n_1$/$m_1$}\) 且 \(v_1 = v_2\) 且 \(v_1' > v_2'\)。
-
\(\mbox{$n_2$/$m_2$} \nsucc \mbox{$n_1$/$m_1$}\) 且 \(v_1 = v_2\) 且 \(v_1' = v_2'\) 且 \(v_1'' \le v_2''\)。
请注意,即使 \(\succ\) 是偏序,\( \ge \) 也是全序。例如,以下关系成立:\(\mbox{text/html;q=1.0;qs=0.7;d=0} \ge \mbox{application/xml;q=1.0;qs=0.2;d=0}\),尽管 \(\mbox{text/html}\) 根据 \(\succ\) 与 \(\mbox{application/xml}\) 不可比。此外,在 \(\ge\) 下,两个类型可能相等,即使它们不相同 [6]。为了方便起见,我们定义 \(p \ge {\perp}\) 对于每个媒体类型 \(p\)。
根据这些定义,我们可以按如下方式以降序对 \(M\) 进行排序[7]:* 令 \(t\) 为请求内容类型,令 \(C_M\) 为资源方法的
@Consumes服务器媒体类型集,我们使用媒体类型 \(\max_\ge \{ S(t,c) \, | \, (t, c) \in \{t\} \times C_M\}\) 作为主键。* 令 \(A\) 为请求 accept header 客户端媒体类型集,令 \(P_M\) 为资源方法的@Produces服务器媒体类型集,我们使用媒体类型 \(\max_\ge \{ S(a,p) \, | \, (a,p) \in A \times P_M\}\) 作为次键。如果存在多个最大元素,实现应报告警告并以实现特定的方式选择其中一种类型。
-
-
令 \(D\) 为集合 \(M\) 中的第一个资源方法[8] 确保集合至少包含一个成员。令 \(O\) 为定义 \(D\) 的类的一个实例。如果在排序后,\(M\) 中存在多个最大元素,实现应报告警告并以实现特定的方式选择其中一种方法。
考虑以下示例,并假设请求为
GETwidgets/1:@Path("widget") public class WidgetResource { private String id; public WidgetResource() { this("0"); } public WidgetResource(String id) { this.id = id; } @GET public Widget findWidget() { return Widget.findWidgetById(id); } } @Path("widgets") public class WidgetsResource { @Path("{id}") public WidgetResource getWidget(@PathParam("id") String id) { return new WidgetResource(id); } }匹配算法的 3 个阶段的输入和输出如下:
-
- 第 1 步
-
识别一组匹配请求的候选根资源类。令 \(R(\mbox{widgets}) = \mbox{widgets(/.*)?}\) 和 \(R(\mbox{widget}) = \mbox{widget(/.*)?}\),+
- 输入
-
\(U = \mbox{widgets/1}\) 和 \(C = \{\mbox{WidgetResource}, \mbox{WidgetsResource}\}\)
- 输出
-
\(U = \mbox{/1}\) 和 \(C' = \{\mbox{WidgetsResource}\}\)
- 第 2 步
-
获取请求的候选资源方法集。令 \($R(\{\mbox{id}\}) = \mbox{([\^{ }/\)+?)(/.*)?}$],+
- 输入
-
\(U = \mbox{/1}\) 和 \(C' = \{\mbox{WidgetsResource}\}\)
- 输出
-
\(M = \{\mbox{findWidget}\}\)
- 第 3 步
-
确定将处理请求的方法,+
- 输入
-
\(M = \{\mbox{findWidget}\}\)
- 输出
-
\(O = \mbox{WidgetResource 实例}\) 和 \(D = \mbox{findWidget}\)
请注意,该算法匹配一个根资源类 (WidgetsResource),因此,对于请求
GETwidgets/1,WidgetResource 上的@Path注解将被忽略。
3.7.3. 将 URI 模板转换为正则表达式
函数 \(R(A)\) 将 URI 路径模板注解 \(A\) 转换为正则表达式,如下所示:
-
URI 编码模板,忽略 URI 模板变量规范。
-
转义 URI 模板中的任何正则表达式字符,同样忽略 URI 模板变量规范。
-
将每个 URI 模板替换为包含指定正则表达式的捕获组,如果未指定正则表达式,则为 ([ /]+?)[9]。
-
如果结果字符串以 / 结尾,则删除最后一个字符。
-
将 (/.*)? 附加到结果。
请注意,以上将模板变量的名称对于模板匹配而言变得无关紧要。但是,实现需要保留模板变量名,以便通过 @PathParam 或 UriInfo.getPathParameters 提取模板变量值。
3.8. 确定响应的 MediaType
在许多情况下,无法静态确定响应的媒体类型。以下算法用于在运行时确定响应媒体类型 \(M_{\mbox{selected}}\):
-
如果方法返回一个
Response实例,其元数据包含响应媒体类型(\(M_{\mbox{specified}}\)),则设置 \(M_{\mbox{selected}} = M_{\mbox{specified}}\),完成。 -
收集可生成媒体类型集 \(P\)
-
如果方法被注解为
@Produces,则设置 \(P = \{ V(\mbox{method}) \}\),其中 \(V(t)\) 表示指定目标 \(t\) 上的@Produces值。 -
否则,如果类被注解为
@Produces,则设置 \(P = \{ V(\mbox{class}) \}\)。 -
否则,设置 \(P = \{ V(\mbox{writers}) \}\),其中 writers 是支持返回实体对象类的
MessageBodyWriter集。
-
-
如果 \(P = \{\}\),则设置 \(P = \{\mbox{\lq*/*\rq}\}
-
获取可接受媒体类型 \(A\)。如果 \(A = \{\}\),则设置 \(A = \{\mbox{\lq*/*\rq}\}
-
设置 \(M=\{\}\)。对于 \(A\) 中的每个成员 \(a\)
-
对于 \(P\) 中的每个成员 \(p\)
-
如果 \(a\) 与 \(p\) 兼容,则将 \(S(a,p)\) 添加到 \(M\),其中函数 \(S\) 返回该对中最具体的媒体类型,具有 \(a\) 的 q-value 和 \(p\) 的服务器端 qs-value。
-
-
-
如果 \(M = \{\}\),则生成
NotAcceptableException(406 状态)且无实体。异常必须按照 异常 中的描述进行处理。完成。 -
将 \(M\) 按降序排序,主键为特异性(\(\mbox{n/m} > \mbox{n/*} > \mbox{*/*}\)),次键为 q-value,第三键为 qs-value。
-
对于 \(M\) 中的每个成员 \(m\)
-
如果 \(m\) 是具体类型,则设置 \(M_{\mbox{selected}} = m\),完成。
-
-
如果 \(M\) 包含 / 或 application/,则设置 \(M_{\mbox{selected}} = \mbox{\lq application/octet-stream\rq}\),完成。
-
生成
NotAcceptableException(406 状态)且无实体。异常必须按照 异常 中的描述进行处理。完成。
请注意,上述设置在无法确定具体类型时,响应的默认媒体类型为 application/octet-stream 。建议 MessageBodyWriter 实现通过 @Produces 指定至少一种具体类型。
4. 提供者
JAX-RS 中的提供者负责各种横切关注点,如过滤请求、将表示转换为 Java 对象、将异常映射到响应等。提供者可以是 JAX-RS 运行时中预打包的,也可以由应用程序提供。所有应用程序提供的提供者都实现了 JAX-RS API 中的接口,并且 MAY 被注解为 @Provider 以进行自动发现;预打包提供者与 JAX-RS 运行时的集成取决于实现。
本章介绍了一些基本的 JAX-RS 提供者;其他提供者将在 客户端 API 和 Filter 和拦截器 章节中介绍。
4.1. 生命周期和环境
默认情况下,每个提供者类的一个实例为每个 JAX-RS 应用程序实例化。首先调用构造函数(参见 构造函数),然后注入任何请求的依赖项(参见 上下文 章节),然后可以多次(同时)调用适当的提供者方法,最后对象可供垃圾回收。提供者 描述了提供者如何通过依赖注入访问其他提供者。
实现 MAY 提供其他提供者生命周期,指定这些的机制不在本规范范围内。例如,基于控制反转框架的实现可以支持该框架提供的所有生命周期选项。
4.1.2. 构造函数
由 JAX-RS 运行时实例化的提供者类必须有一个公共构造函数,JAX-RS 运行时可以为该构造函数提供所有参数值。请注意,根据此规则,允许使用零参数构造函数。
公共构造函数 MAY 包含被注解为 @Context 的参数 — 上下文 章节定义了此注解允许的参数类型。由于提供者可能在特定请求范围之外创建,因此在构造时,只有部署特定的属性可能可以从注入的接口获取;请求特定的属性在调用提供者方法时可用。如果可以使用一个以上的公共构造函数,则实现必须使用参数最多的那个。在具有相同参数数量的构造函数之间进行选择是实现特定的,实现应生成关于此类歧义的警告。
4.1.3. 优先级
应用程序提供的提供者使开发人员能够扩展和自定义 JAX-RS 运行时。因此,如果需要单个提供者,应用程序提供的提供者必须始终优先于预打包的提供者。
应用程序提供的提供者可以被注解为 @Priority。如果两个或多个提供者是某个任务的候选者,则选择优先级最高的提供者:在这种情况下,最高优先级定义为值最低的优先级。也就是说,@Priority(1) 的优先级高于 @Priority(10)。如果两个或多个提供者符合条件且优先级相同,则以实现特定的方式选择一个。所有应用程序提供的提供者的默认优先级为 javax.ws.rs.Priorities.USER。
关于优先级的普遍规则对于 Filter 和拦截器有所不同,因为这些提供者被收集到链中。有关更多信息,请参阅 优先级 章节。
4.2. 实体提供者
实体提供者提供表示与其关联的 Java 类型之间的映射服务。实体提供者有两种形式:MessageBodyReader 和 MessageBodyWriter ,如下所述。
4.2.1. Message Body Reader
MessageBodyReader 接口定义了 JAX-RS 运行时与提供表示到相应 Java 类型映射服务的组件之间的契约。希望提供此类服务的类实现 MessageBodyReader 接口,并可以被注解为 @Provider 以进行自动发现。
以下描述了 JAX-RS 实现将消息实体体映射到 Java 方法参数时所采取的逻辑[10] 步骤:
-
获取请求的媒体类型。如果请求不包含
Content-TypeHeader,则使用application/octet-stream。 -
确定参数的 Java 类型,其值将从实体体映射。 将请求匹配到 Java 方法 描述了如何选择 Java 方法。
-
选择支持请求媒体类型的
MessageBodyReader类集,请参阅 声明媒体类型能力。 -
遍历选定的
MessageBodyReader类,并利用每个类的isReadable方法,选择支持所需 Java 类型的MessageBodyReader提供者。 -
如果步骤 4 找到一个或多个合适的
MessageBodyReader,则根据 优先级 部分选择优先级最高的那个,并使用其readFrom方法将实体正文映射到所需的 Java 类型。 -
否则,服务器运行时必须生成一个
NotSupportedException(415 状态)且没有实体(按 异常 部分处理),客户端运行时必须生成一个ProcessingException实例。
有关处理 MessageBodyReader.readFrom 中抛出的异常的信息,请参阅 异常。
4.2.2. Message Body Writer
MessageBodyWriter 接口定义了 JAX-RS 运行时与提供从 Java 类型到表示形式的映射服务的组件之间的契约。希望提供此类服务的类实现 MessageBodyWriter 接口,并可能使用 @Provider 注解以实现自动发现。
以下描述了 JAX-RS 实现将返回值映射到消息实体正文时所采取的逻辑步骤
-
获取将被映射到消息实体正文的对象。对于返回类型为
Response或其子类的情况,该对象是entity属性的值;对于其他返回类型,则是返回的对象。 -
确定响应的媒体类型,请参阅 [determine_response_type] 部分。
-
选择一组
MessageBodyWriter提供程序,这些提供程序支持(参见 声明媒体类型能力)对象和消息实体正文的媒体类型。 -
使用通用类型作为主键对选定的
MessageBodyWriter提供程序进行排序,其中通用类型是对象类的最近超类的提供程序首先排序,然后使用媒体类型作为次键(参见 [declaring_provider_capabilities] 部分)。 -
遍历已排序的
MessageBodyWriter提供程序,并利用每个提供程序的isWriteable方法,选择一个支持将被映射到实体正文的对象 的MessageBodyWriter。 -
如果步骤 5 找到一个或多个符合条件的
MessageBodyWriter并且它们在步骤 4 的排序中是相等的,则根据 优先级 部分选择优先级最高的那个,并使用其writeTo方法将实体正文映射到所需的 Java 类型。 -
否则,服务器运行时必须生成一个
InternalServerErrorException,它是WebApplicationException的一个子类,其状态设置为 500,且没有实体(按 异常 部分处理),客户端运行时必须生成一个ProcessingException。
通过实践获得的经验表明,此规范中的步骤 4 的排序键已反转。这代表了与 JAX-RS 1.X 相比的向后不兼容的更改。此规范的实现被要求提供一个向后兼容的标志,以供依赖于先前排序的应用程序使用。用于启用此标志的机制是实现依赖的。
有关处理 MessageBodyWriter.write 中抛出的异常的信息,请参阅 异常。
4.2.3. 声明媒体类型能力
消息正文读取器和写入器可以使用 @Consumes 和 @Produces 注解分别限制其支持的媒体类型。缺少这些注解等同于包含媒体类型 (/*),即缺少意味着支持任何媒体类型。实现不得将实体提供程序用于该提供程序不支持的媒体类型。
选择实体提供程序时,实现会根据它们声明支持的媒体类型对可用提供程序进行排序。媒体类型的排序遵循一般规则:x/y < x/* < /,即显式列出媒体类型的提供程序排在列出 / 的提供程序之前。
4.2.4. 标准实体提供程序
实现必须包含以下 Java 和媒体类型组合的预打包 MessageBodyReader 和 MessageBodyWriter 实现
byte[]-
所有媒体类型 (
/*)。 java.lang.String-
所有媒体类型 (
/*)。 java.io.InputStream-
所有媒体类型 (
/*)。 java.io.Reader-
所有媒体类型 (
/*)。 java.io.File-
所有媒体类型 (
/*)。 javax.activation.DataSource-
所有媒体类型 (
/*)。 javax.xml.transform.Source-
XML 类型(
text/xml、application/xml以及形式为application/*+xml的媒体类型)。 javax.xml.bind.JAXBElement和应用程序提供的 JAXB 类-
XML 类型(
text/xml和application/xml以及形式为application/*+xml的媒体类型)。 MultivaluedMap<String,String>-
表单内容(
application/x-www-form-urlencoded)。 StreamingOutput-
所有媒体类型 (
/*),仅MessageBodyWriter。 java.lang.Boolean、java.lang.Character、java.lang.Number-
仅用于
text/plain。通过装箱/拆箱转换支持相应的原始类型。
根据环境,标准实体提供程序列表还必须包括 JSON 的提供程序。有关这些提供程序的更多信息,请参阅 Java API for JSON Processing 和 Java API for JSON Binding。
在读取零长度消息实体时,所有预打包的 MessageBodyReader 实现,除了 JAXB 实现和上述(装箱的)原始类型的实现外,都必须创建一个代表零长度数据的相应 Java 对象。预打包的 JAXB 和预打包的原始类型 MessageBodyReader 实现必须为零长度消息实体抛出 NoContentException。
当从 MessageBodyReader 读取服务器请求实体时抛出 NoContentException,服务器运行时必须将其转换为 BadRequestException,包装原始 NoContentException 并重新抛出以供任何注册的异常映射器处理。
javax.xml.bind.JAXBElement 和应用程序提供的 JAXB 类的实现提供的实体提供程序必须使用应用程序提供的上下文解析器提供的 JAXBContext 实例(参见 上下文提供程序)。如果应用程序未为特定类型提供 JAXBContext,则实现提供的实体提供程序必须使用自己的默认上下文。
在写入响应时,实现应尊重应用程序提供的字符集元数据,如果未指定字符集或应用程序指定的字符集不受支持,则应使用 UTF-8。
4.3. 上下文提供程序
上下文提供程序为资源类和其他提供程序提供上下文。上下文提供程序类实现 ContextResolver<T> 接口,并可能使用 @Provider 注解以实现自动发现。例如,希望为默认 JAXB 实体提供程序提供自定义 JAXBContext 的应用程序将提供一个实现 ContextResolver<JAXBContext> 的类。
上下文提供程序可以从 getContext 方法返回 null,如果它们不希望为特定 Java 类型提供其上下文。例如,JAXB 上下文提供程序可能只想为某些 JAXB 类提供上下文。上下文提供程序还可以管理同一类型的多个上下文,这些上下文以不同的 Java 类型为键。
4.4. 异常映射提供程序
异常映射提供程序将已检查异常或运行时异常映射到 Response 实例。异常映射提供程序实现 ExceptionMapper<T> 接口,并可能使用 @Provider 注解以实现自动发现。
当资源类或提供程序方法抛出异常,并且存在该异常的异常映射提供程序时,将使用匹配的提供程序来获取 Response 实例。生成的 Response 将像 Web 资源方法返回 Response 一样进行处理(参见 返回类型)。特别是,映射的 Response 必须使用 Filters and Interceptors 章节中定义的 ContainerResponse 过滤器链进行处理。
选择异常映射提供程序来映射异常时,实现必须使用其通用类型是异常的最近超类的提供程序。如果两个或多个异常提供程序适用,则必须按照 优先级 部分所述选择优先级最高的那个。
为避免潜在的无限循环,在处理请求及其相应响应期间必须使用单个异常映射器。JAX-RS 实现不得尝试映射在处理先前从异常映射的响应时抛出的异常。相反,此异常必须按照 异常 部分的步骤 3 和 4 的描述进行处理。
请注意,异常映射提供程序 **不** 支持作为客户端 API 的一部分。
4.5. 异常
异常处理根据提供程序是客户端运行时还是服务器运行时的组成部分而有所不同。这将在接下来的两个部分中介绍。
5. Client API
Client API 用于访问 Web 资源。它提供了比 HttpURLConnection 更高级别的 API,以及与 JAX-RS 提供程序的集成。除非另有说明,本章中介绍的类型位于 javax.ws.rs.client 包中。
5.1. 引导 Client 实例
需要一个 Client 实例才能使用 Client API 访问 Web 资源。可以通过调用 ClientBuilder 上的 newClient 来获取 Client 的默认实例。Client 实例可以使用从 Configurable 继承的方法进行配置,如下所示
// Default instance of client
Client client = ClientBuilder.newClient();
// Additional configuration of default client
client.property("MyProperty", "MyValue")
.register(MyProvider.class)
.register(MyFeature.class);
有关提供程序的信息,请参阅 Providers 章节。属性只是名称-值对,其中值是任意对象。功能也是提供程序,并且必须实现 Feature 接口;它们对于将一组逻辑上相关的属性和提供程序(包括其他功能)分组在一起并作为单元启用很有用。
5.2. 资源访问
可以使用流畅的 API 访问 Web 资源,其中方法调用被链接起来以构建并最终提交 HTTP 请求。以下示例获取由 http://example.org/hello 标识的资源的 text/plain 表示形式
Client client = ClientBuilder.newClient();
Response res = client.target("http://example.org/hello")
.request("text/plain").get();
从概念上讲,提交请求所需的步骤如下:(i) 获取 Client 实例 (ii) 创建 WebTarget (iii) 从 WebTarget 创建请求,以及 (iv) 提交请求或获取准备好的 Invocation 以供稍后提交。有关使用 Invocation 的更多信息,请参阅 [invocations] 部分。
方法链接不限于上面显示的示例。请求可以通过设置标头、Cookie、查询参数等进一步指定。例如
Response res = client.target("http://example.org/hello")
.queryParam("MyParam","...")
.request("text/plain")
.header("MyHeader", "...")
.get();
有关 javax.ws.rs.client 包中类的更多信息,请参阅 Javadoc。
5.3. Client 目标
使用 WebTarget 的好处在构建复杂 URI 时显而易见,例如通过向基 URI 添加其他路径段或模板。以下示例重点介绍了这些情况
WebTarget base = client.target("http://example.org/");
WebTarget hello = base.path("hello").path("{whom}");
Response res = hello.resolveTemplate("whom", "world").request("...").get();
请注意 {whom} URI 模板参数的使用。上面的示例获取由 http://example.org/hello/world 标识的资源的表示。
WebTarget 实例对于它们的 URI(或 URI 模板)是 **不可变** 的:用于指定其他路径段和参数的方法会返回一个新的 WebTarget 实例。但是,WebTarget 实例对于它们的配置是 **可变** 的。因此,配置 WebTarget 不会创建新实例。
// Create WebTarget instance base
WebTarget base = client.target("http://example.org/");
// Create new WebTarget instance hello and configure it
WebTarget hello = base.path("hello");
hello.register(MyProvider.class);
在此示例中,创建了两个 WebTarget 实例。实例 hello 继承了 base 的配置,并由注册 MyProvider.class 进一步配置。请注意,对 hello 配置的更改不会影响 base,即继承执行配置的 **深拷贝**。有关可配置类型的更多信息,请参阅 [configurable_types] 部分。
5.4. 类型化实体
对请求的响应不限于 Response 类型。以下示例将客户编号 123 的状态升级为“金卡会员”,首先获取 Customer 类型实体,然后将该实体发布到不同的 URI
Customer c = client.target("http://examples.org/customers/123")
.request("application/xml").get(Customer.class);
String newId = client.target("http://examples.org/gold-customers/")
.request().post(xml(c), String.class);
请注意在调用 post 时使用了 xml() 的 **变体**。javax.ws.rs.client.Entity 类为 JAX-RS 应用程序中最常用的媒体类型定义了变体。
在上面的示例中,就像在服务器 API 中一样,JAX-RS 实现被要求使用实体提供程序将 application/xml 类型的表示映射到 Customer 实例,反之亦然。有关所有 JAX-RS 实现必须支持的实体提供程序列表,请参阅 标准实体提供程序。
5.5. 调用
调用是已准备好并可执行的请求。调用提供了一个 **通用接口**,它允许创建者和提交者之间进行关注点分离。特别是,提交者不需要知道调用是如何准备的,只需要知道它应该如何执行:即同步执行或异步执行。
让我们来看以下示例 [11]
// Executed by the creator
Invocation inv1 = client.target("http://examples.org/atm/balance")
.queryParam("card", "111122223333").queryParam("pin", "9876")
.request("text/plain").buildGet();
Invocation inv2 = client.target("http://examples.org/atm/withdrawal")
.queryParam("card", "111122223333").queryParam("pin", "9876")
.request().buildPost(text("50.0"));
Collection<Invocation> invs = Arrays.asList(inv1, inv2);
// Executed by the submitter
Collection<Response> ress =
Collections.transform(invs,
new F<Invocation, Response>() {
@Override
public Response apply(Invocation inv) {
return inv.invoke(); } });
Collections.transform(invs, new F<Invocation, Response>() @Override public Response apply(Invocation inv) return inv.invoke(); );
在此示例中,创建了两个调用并由创建者存储在一个集合中。然后提交者遍历集合,应用一个将 Invocation 映射到 Response 的转换。该映射调用 Invocation.invoke 来同步执行调用;通过调用 Invocation.submit 也支持异步执行。有关异步调用的更多信息,请参阅 Asynchronous Processing 章节。
5.6. 可配置类型
以下 Client API 类型是可配置的:Client、ClientBuilder 和 WebTarget。配置方法是从所有这些类实现的 Configurable 接口继承的。此接口支持配置
- 属性
-
名称-值对,用于其他功能或 JAX-RS 实现的其他组件的附加配置。
- 功能
-
一种特殊类型的提供程序,实现
Feature接口,并可用于配置 JAX-RS 实现。 - 提供程序
-
实现 Providers 章节中的一个或多个提供程序接口的类或类的实例。提供程序可以是消息正文读取器、过滤器、上下文解析器等。
上述任何类型的实例上定义的配置都将由从它创建的其他实例继承。例如,从 Client 创建的 WebTarget 实例将继承 Client 的配置。但是,对 WebTarget 实例的任何其他更改都不会影响 Client 的配置,反之亦然。因此,一旦继承了配置,它就会从父配置中分离(深拷贝),父配置和子配置的更改对彼此不可见。
5.6.1. 过滤器和实体拦截器
如 Filters and Interceptors 章节所述,过滤器和拦截器被定义为 JAX-RS 提供程序。因此,它们可以注册到上一节中列出的任何可配置类型。以下示例演示了如何将过滤器和拦截器注册到 Client 和 WebTarget 的实例
// Create client and register logging filter
Client client = ClientBuilder.newClient().register(LoggingFilter.class);
// Executes logging filter from client and caching filter from target
WebTarget wt = client.target("http://examples.org/customers/123");
Customer c = wt.register(CachingFilter.class).request("application/xml")
.get(Customer.class);
在此示例中,LoggingFilter 由从 client 创建的每个 WebTarget 实例继承;名为 CachingFilter 的附加提供程序注册在 WebTarget 实例上。
5.7. 响应式客户端
Client API 在 Client API 中引入了异步编程。JAX-RS 中的异步编程使客户端可以通过将工作推送到后台线程来解除某些线程的阻塞,这些线程可以稍后监视和可能等待(join)。这可以通过提供 InvocationCallback 实例或操作异步调用者返回的 Future<T> 类型的结果来实现,或者两者的组合。
使用 InvocationCallback 可实现更具 **响应式** 的编程风格,用户提供的代码仅在发生特定事件时激活(或响应)。回调在简单情况下效果很好,但在涉及多个事件时,源代码会更难理解。例如,当需要组合、合并或以任何方式操作异步调用时。这些类型的场景可能导致回调嵌套在其他回调中,使代码可读性大大降低——由于调用固有的嵌套,通常被称为 **“厄运金字塔”**。
为了满足提高可读性和使程序员能够更好地推理异步计算的要求,Java 8 引入了一个名为 CompletionStage 的新接口,其中包含大量用于管理异步计算的方法。
JAX-RS 2.1 定义了一种新的调用程序类型,称为 RxInvoker,以及该类型的一个默认实现 CompletionStageRxInvoker,它基于 Java 8 的 CompletionStage 类型。有一个新的 rx 方法,其用法类似于 Client API 中描述的 async。让我们来看以下示例
CompletionStage<String> csf = client.target("forecast/{destination}")
.resolveTemplate("destination", "mars")
.request()
.rx()
.get(String.class);
csf.thenAccept(System.out::println);
此示例首先创建一个 CompletionStage<String> 类型的异步计算,然后简单地等待它完成并显示其结果(技术上,在最后一行创建了一个 CompletionStage<Void> 类型的第二个计算,仅用于消耗第一个计算的结果)。
当多个异步计算对于完成一项任务是必需的时,CompletionStage 的价值变得显而易见。以下示例并行获取目的地价格和预测,并仅在满足所需条件时进行预订。
CompletionStage<Number> csp = client.target("price/{destination}")
.resolveTemplate("destination", "mars")
.request()
.rx()
.get(Number.class);
CompletionStage<String> csf = client.target("forecast/{destination}")
.resolveTemplate("destination", "mars")
.request()
.rx()
.get(String.class);
csp.thenCombine(csf, (price, forecast) ->
reserveIfAffordableAndWarm(price, forecast));
请注意,调用 thenCombine 方法时传递的 Consumer 需要每个阶段的值可用,因此只能在两个并行阶段完成后执行。
正如我们将在下一节中看到的,CompletionStage 的支持是所有 JAX-RS 实现的 **默认** 支持,但其他响应式 API 也可能作为扩展支持。
5.7.1. 响应式 API 扩展
Java 中曾有几个关于响应式 API 的提案。所有 JAX-RS 实现都必须支持 CompletionStage 的调用程序,如上所示。此外,JAX-RS 实现可以支持其他响应式 API,使用内置于 Client API 的扩展。
RxJava [11] 是 Java 中一个流行的响应式库。此 API 中表示异步计算的类型称为 Observable。实现可以通过提供一个新的调用程序来支持此类型,如以下示例所示
Client client = client.register(ObservableRxInvokerProvider.class);
Observable<String> of = client.target("forecast/{destination}")
.resolveTemplate("destination", "mars")
.request()
.rx(ObservableRxInvoker.class) // overrides default invoker
.get(String.class);
of.subscribe(System.out::println);
首先,必须在 Client 对象上注册新调用程序的提供程序。其次,调用程序的类型必须作为参数传递给 rx 方法。请注意,由于这是一个 JAX-RS 扩展,因此上面示例中提供程序和调用程序的实际名称是实现依赖的。读者应参考所选 JAX-RS 实现的文档以获取更多信息。
RxJava [12] 的 2.0 版本已完全重写,基于 Reactive-Streams 规范。这种新架构促使引入了一种称为 Flowable 的新类型。JAX-RS 实现可以通过实现新的提供程序(例如 FlowableRxInvokerProvider)并使用上面示例中所示的相同模式来轻松支持此新版本。
5.8. Executor 服务
Executor 服务可用于提交异步任务以供执行。JAX-RS 应用程序可以在构建 Client 实例时指定 executor 服务。ClientBuilder 中为此目的提供了两个方法,即 executorService 和 scheduledExecutorService。
在支持 Jakarta Concurrency [13] 的环境中(例如完整的 Jakarta EE 平台产品),实现必须分别使用 ManagedExecutorService 和 ManagedScheduledExecutorService。有关 executor 服务的信息,请参阅 ClientBuilder 的 Javadoc。
6. 过滤器和拦截器
过滤器和实体拦截器可以注册在 JAX-RS 实现中的明确定义的扩展点以供执行。它们用于扩展实现,以提供日志记录、保密性、身份验证、实体压缩等功能。
6.1. 引言
实体拦截器包装在特定扩展点的方法调用。过滤器在扩展点执行代码,但不包装方法调用。过滤器有四个扩展点:ClientRequest、ClientResponse、ContainerRequest 和 ContainerResponse。实体拦截器有两个扩展点:ReadFrom 和 WriteTo。对于每个扩展点,都有一个对应的接口
public interface ClientRequestFilter {
void filter(ClientRequestContext requestContext) throws IOException;
}
public interface ClientResponseFilter {
void filter(ClientRequestContext requestContext,
ClientResponseContext responseContext) throws IOException;
}
public interface ContainerRequestFilter {
void filter(ContainerRequestContext requestContext) throws IOException;
}
public interface ContainerResponseFilter {
void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException;
}
public interface ReaderInterceptor {
Object aroundReadFrom(ReaderInterceptorContext context)
throws java.io.IOException, javax.ws.rs.WebApplicationException;
}
public interface WriterInterceptor {
void aroundWriteTo(WriterInterceptorContext context)
throws java.io.IOException, javax.ws.rs.WebApplicationException;
}
**客户端** 过滤器是实现 ClientRequestFilter 或 ClientResponseFilter 或两者的类。 **容器** 过滤器是实现 ContainerRequestFilter 或 ContainerResponseFilter 或两者的类。实体拦截器是实现 ReaderInterceptor 或 WriterInterceptor 或两者的类。过滤器和实体拦截器是提供程序,因此可以使用 @Provider 注解以实现自动发现。
在 Client API 中,ClientRequestFilter 在 HTTP 请求发送到网络之前作为调用管道的一部分执行;ClientResponseFilter 在收到服务器响应后,在控制权返回到应用程序之前执行。在 Server API 中,ContainerRequestFilter 在收到来自客户端的请求时执行;ContainerResponseFilter 在 HTTP 响应发送到网络之前,作为响应管道的一部分执行。
全局绑定的(参见 全局绑定)ContainerRequestFilter 是在资源匹配后执行的容器过滤器,**除非** 它被注解了 @PreMatching。此注解在此类过滤器上的使用定义了一个新的扩展点供应用程序使用,即。某些 ContainerRequestContext 方法在此扩展点可能不可用。
实现 ReaderInterceptor 的实体拦截器包装对 MessageBodyReader 的 readFrom 方法的调用。实现 WriterInterceptor 的实体拦截器包装对 MessageBodyWriter 的 writeTo 方法的调用。JAX-RS 实现被要求在将表示形式映射到 Java 类型及反之亦然时调用已注册的拦截器。有关实体提供程序的信息,请参阅 Entity Providers。
有关客户端和服务器处理管道中过滤器和实体拦截器交互的图示,请参阅附录 Processing Pipeline。
6.2. 过滤器
过滤器被分组到 **过滤器链** 中。对于上一节中介绍的每个扩展点,都有一个单独的过滤器链,即:ClientRequest、ClientResponse、ContainerRequest、ContainerResponse 和 PreMatchContainerRequest。链中的过滤器根据其优先级(参见 Priorities)进行排序,并按顺序执行。
以下示例显示了一个容器日志过滤器实现:每个方法仅记录消息并返回。
@Provider
class LoggingFilter implements ContainerRequestFilter,
ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
log(requestContext);
}
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
log(responseContext);
}
...
}
ContainerRequestContext 是一个可变类,它为过滤器提供请求特定信息,例如请求 URI、消息头、消息实体或请求范围的属性。暴露的 setter 允许在请求被资源方法处理之前(对请求)进行修改。类似地,有一个对应的 ContainerResponseContext,它提供响应特定信息。
实现 ClientRequestFilter 或 ContainerRequestFilter 的请求过滤器可以通过在其相应的上下文对象中调用 abortWith(Response) 来停止其相应链的执行。如果调用此方法,JAX-RS 实现被要求中止链的执行,并将响应对象视为通过调用资源方法(Server API)或执行 HTTP 调用(Client API)生成的。例如,在缓存命中时,客户端 **缓存** 过滤器可能会调用 abortWith(Response) 来中止执行并优化网络访问。
如上所述,注解了 @PreMatching 的 ContainerRequestFilter 在接收到客户端请求时执行,但在匹配资源方法 **之前** 执行。因此,此类型的过滤器能够修改匹配算法的输入(参见 Request Matching),并因此改变其结果。以下示例使用了一个注解了 @PreMatching 的 ContainerRequestFilter,通过使用 X-HTTP-Method-Override 头在资源匹配之前覆盖 HTTP 方法来隧道化 POST 请求。
@Provider
@PreMatching
public class HttpMethodOverrideFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
if (requestContext.getMethod().equalsIgnoreCase("POST")) {
String override = requestContext.getHeaders()
.getFirst("X-HTTP-Method-Override");
if (override != null) {
requestContext.setMethod(override);
}
}
}
}
6.3. 实体拦截器
实体拦截器实现 ReaderInterceptor 或 WriterInterceptor 接口,或两者都实现。对于每种类型的实体拦截器,都存在一个 **拦截器链**。链中的实体拦截器根据其优先级(参见 Priorities)进行排序,并按顺序执行。
作为 JAX-RS 处理管道(参见附录 Processing Pipeline)的一部分,实体拦截器包装对实现 MessageBodyReader 的类的 readFrom 方法的调用以及对实现 MessageBodyWriter 的类的 writeTo 方法的调用。拦截器应显式调用上下文方法 proceed 以继续链的执行。由于其包装性质,未能调用此方法将阻止相应消息正文读取器或消息正文写入器中的被包装方法执行。
以下示例显示了一个 GZIP 实体拦截器实现,它提供解压缩和压缩功能 [12]。
@Provider
class GzipInterceptor implements ReaderInterceptor, WriterInterceptor {
@Override
Object aroundReadFrom(ReaderInterceptorContext ctx) ... {
if (isGzipped(ctx)) {
InputStream old = ctx.getInputStream();
ctx.setInputStream(new GZIPInputStream(old));
try {
return ctx.proceed();
} finally {
ctx.setInputStream(old);
}
} else {
return ctx.proceed();
}
}
@Override
void aroundWriteTo(WriterInterceptorContext ctx) ... {
OutputStream old = ctx.getOutputStream();
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(old);
ctx.setOutputStream(gzipOutputStream);
updateHeaders(ctx);
try {
ctx.proceed();
} finally {
gzipOutputStream.finish();
ctx.setOutputStream(old);
}
}
...
}
上下文类型 ReaderInterceptorContext 和 WriterInterceptorContext 提供对相应被包装方法的参数的读写访问。在上面显示的示例中,在继续之前,输入和输出流被包装并更新在上下文对象中。JAX-RS 实现必须在调用被包装的 MessageBodyReader.readFrom 和 MessageBodyWriter.writeTo 方法时使用上下文中设置的最后参数值。
值得注意的是,直接从应用程序代码调用(例如,通过注入 Providers 实例)的 readFrom 或 writeTo **不会** 触发任何实体拦截器的执行,因为它不是正常 JAX-RS 处理管道的一部分。
6.4. 生命周期
默认情况下,与其他所有提供程序一样,每个过滤器或实体拦截器的单个实例都会为每个 JAX-RS 应用程序实例化。首先调用构造函数,然后注入任何请求的依赖项,然后根据需要调用相应的方法(同时)。实现可以提供除默认选项之外的替代生命周期选项。有关更多信息,请参阅 Lifecycle and Environment。
6.5. 绑定
绑定是将过滤器或拦截器与资源类或方法(Server API)或调用(Client API)相关联的过程。下一节中介绍的绑定形式仅作为 Server API 的一部分受支持。有关 Client API 中的绑定的信息,请参阅 Binding in Client API。
6.5.1. 全局绑定
全局绑定是默认的绑定类型。没有注解的过滤器或拦截器假定为全局绑定,即它适用于应用程序中的所有资源方法。与其他提供程序一样,过滤器或拦截器可以手动注册(例如,通过 Application 或 Configuration)或自动发现。请注意,为了自动发现过滤器或拦截器,它 **必须** 被注解为 @Provider(参见 Automatic Discovery)。
例如,第 [filters] 节中定义的 LoggingFilter 既可以自动发现(它被注解为 @Provider),也可以全局绑定。如果此过滤器是应用程序的一部分,则会为所有资源方法记录请求和响应。
如 Introduction 所述,全局 ContainerRequestFilter 在资源匹配后执行,除非被注解为 @PreMatching。注入 ResourceInfo 的全局过滤器,以及通常依赖于资源信息才能执行的过滤器,不应被注解为 @PreMatching。
6.5.2. 名称绑定
过滤器或拦截器可以通过声明一个新的 **绑定** 注解(类似 CDI [14])与资源类或方法关联。这些注解使用 JAX-RS 元注解 @NameBinding 进行声明,并用于修饰过滤器(或拦截器)以及资源方法或资源类。例如,Filters 节中定义的 LoggingFilter 可以绑定到 MyResourceClass 中的 hello 方法,而不是全局绑定,如下所示
@Provider
@Logged
class LoggingFilter implements ContainerRequestFilter,
ContainerResponseFilter {
...
}
@Path("/")
public class MyResourceClass {
@Logged
@GET
@Produces("text/plain")
@Path("{name}")
public String hello(@PathParam("name") String name) {
return "Hello " + name;
}
}
根据 LoggingFilter 的语义,请求将在调用 hello 方法之前被记录,响应将在其返回之后被记录。@Logged 注解的声明如下所示。
@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Logged { }
使用额外的注解可以绑定多个过滤器和拦截器到单个资源方法。例如,给定以下过滤器
@Provider
@Authenticated
class AuthenticationFilter implements ContainerRequestFilter {
...
}
上面 hello 方法可以被 @Logged 和 @Authenticated 注解,以便为该资源提供日志记录和身份验证功能。
过滤器或拦截器类可以被多个绑定注解修饰。在这种情况下,根据 CDI [14] 中描述的语义,所有这些注解必须存在于资源类或方法中才能建立绑定。例如,如果 LoggingFilter 定义如下
@Provider
@Logged @Verbose
class LoggingFilter implements ContainerRequestFilter,
ContainerResponseFilter {
...
}
那么上面 hello 方法必须同时注解 @Logged 和 @Verbose 才能使绑定生效。
绑定注解也可以应用于资源类和 Application 子类。修饰资源类的绑定注解适用于其中定义的所有资源方法。修饰 Application 子类的绑定注解也可以用于全局绑定过滤器和拦截器,即使存在其他注解。例如,本节开头定义的 LoggingFilter 可以全局绑定如下
@Logged
public class MyApplication extends Application {
...
}
请注意,从应用程序子类中的 getClasses 或 getSingletons 方法返回过滤器或拦截器,只有在它们 **未** 被名称绑定注解修饰时,才会将它们全局绑定。如果它们被注解了至少一个名称绑定注解,则应用程序子类必须如上注解,以便这些过滤器或拦截器被全局绑定。有关 JAX-RS 应用程序的更多信息,请参阅 Applications 章节。
6.5.3. 动态绑定
到目前为止介绍的基于注解的绑定形式是 **静态** 的。动态绑定也通过动态功能得到支持。动态功能是实现 DynamicFeature 接口的提供程序。这些提供程序用于增强绑定到资源方法的一组过滤器和实体拦截器。
以下示例定义了一个动态功能,它将过滤器 LoggingFilter(在此示例中假定为非全局绑定)与 MyResource 中所有注解了 @GET 的资源方法进行绑定。
@Provider
public final class DynamicLoggingFilterFeature implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo,
FeatureContext context) {
if (MyResource.class.isAssignableFrom(resourceInfo.getResourceClass())
&& resourceInfo.getResourceMethod().isAnnotationPresent(GET.class)) {
context.register(new LoggingFilter());
}
}
}
此提供程序中的覆盖方法更新了分配给每个资源方法的 Configuration 对象;有关资源方法的信息以 ResourceInfo 实例的形式提供。JAX-RS 实现 **应该** 为每个资源方法一次性解析过滤器和拦截器的动态功能。建议在应用程序部署时处理动态功能。
6.5.4. Client API 中的绑定
Client API 中的绑定是通过 API 调用而不是注解来完成的。Client、Invocation、Invocation.Builder 和 WebTarget 都是可配置类型:可以使用从 Configurable 接口继承的方法来访问它们的配置。有关更多信息,请参阅 Configurable Types。
6.6. 优先级
过滤器和拦截器作为其相应链一部分的执行顺序由 [15] 中定义的 @Priority 注解控制。优先级由整数表示。扩展点 、 、 、 和 的执行链按 **升序** 排序;数字越小,优先级越高。扩展点 和 的执行链按 **降序** 排序;数字越大,优先级越高。这些规则确保响应过滤器按与请求过滤器相反的顺序执行。
JAX-RS 中的 Priorities 类定义了一组用于安全、头装饰器、解码器和编码器的内置优先级。默认绑定优先级是 javax.ws.rs.Priorities.USER。例如,身份验证过滤器的优先级可以设置为
@Provider
@Authenticated
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
...
}
请注意,尽管如 Binding in Client API 所解释的,注解不用于 Client API 中的绑定,但它们仍用于定义优先级。因此,如果需要与默认值不同的优先级,则必须为注册到 Client API 的过滤器或拦截器使用 @Priority 注解。
属于同一优先级类的过滤器和拦截器的执行顺序是实现依赖的。
6.7. 异常
6.7.1. 服务器运行时
当过滤器或拦截器方法抛出异常时,服务器运行时将按照 Server Runtime 中描述的方式处理异常。如 Exception Mapping Providers 所述,应用程序可以提供异常映射提供程序。为避免潜在的无限循环,在单个请求处理周期中最多必须使用一个异常映射器。
从异常映射的响应必须使用 和 过滤器链以及 (如果映射响应中存在实体)拦截器链进行处理。这些链中的条目数取决于抛出异常时是否已匹配资源方法。有两种情况
-
如果在抛出异常之前已匹配 Web 资源,则 和 中的过滤器将包括已绑定到该方法以及全局绑定的所有内容;
-
否则,仅包括全局过滤器和拦截器。
请注意,在情况 2 中调用的过滤器或拦截器将无法访问依赖于资源的,例如由可注入的 ResourceInfo 实例返回的信息。
6.7.2. 客户端运行时
当过滤器或拦截器方法抛出异常时,客户端运行时将按照 Client Runtime 中描述的方式处理异常。
7. 验证
验证是验证某些数据是否符合一个或多个预定义约束的过程。Bean Validation 规范 [16] 定义了用于验证 Java Bean 的 API。本章介绍 JAX-RS 如何基于 [16] 中提出的概念提供对资源类的本地支持。有关实现要求的信息,请参阅 Bean Validation。
7.1. 约束注解
Server API 支持使用 @HeaderParam、@QueryParam 等注解来提取请求值并将其映射到 Java 字段、属性和参数。它还支持通过非注解参数(即没有 JAX-RS 注解的参数)将请求实体正文映射到 Java 对象。有关更多信息,请参阅 Resources 章节。
在 JAX-RS 的早期版本中,任何附加验证都需要以编程方式进行。此版本的 JAX-RS 引入了对基于 Bean Validation 规范 [16] 的声明式验证的支持。
Bean Validation 规范 [16] 支持使用 **约束注解** 作为声明式验证 Bean、方法参数和方法返回值的方式。例如,考虑以下使用约束注解增强的资源类
@Path("/")
class MyResourceClass {
@POST
@Consumes("application/x-www-form-urlencoded")
public void registerUser(
@NotNull @FormParam("firstName") String firstName,
@NotNull @FormParam("lastName") String lastName,
@Email @FormParam("email") String email) {
...
}
}
@NotNull 和 @Email 注解对表单参数 firstName、lastName 和 email 施加了附加约束。@NotNull 约束是 Bean Validation API 内置的;@Email 约束假定在上面的示例中是用户定义的。这些约束注解不限于方法参数,它们可以在 JAX-RS 绑定注解允许的任何位置使用,除了构造函数和属性 setter。上例中的 MyResourceClass 可以写成如下形式,而不是使用方法参数
@Path("/")
class MyResourceClass {
@NotNull @FormParam("firstName")
private String firstName;
@NotNull @FormParam("lastName")
private String lastName;
private String email;
@FormParam("email")
public void setEmail(String email) {
this.email = email;
}
@Email
public String getEmail() {
return email;
}
...
}
请注意,在此版本中,firstName 和 lastName 是通过注入初始化的字段,而 email 是一个资源类属性。属性上的约束注解在它们相应的 getter 中指定。
约束注解也允许在资源类上使用。除了注解字段和属性外,还可以为整个类定义注解。假设 @NonEmptyNames 验证 MyResourceClass 中的两个 **名称** 字段之一是否已提供。使用此类注解,可以扩展上面的示例
@Path("/")
@NonEmptyNames
class MyResourceClass {
@NotNull @FormParam("firstName")
private String firstName;
@NotNull @FormParam("lastName")
private String lastName;
private String email;
...
}
资源类上的约束注解对于定义跨字段和跨属性的约束非常有用。
7.2. 注解和验证器
注解约束和验证器根据 Bean Validation 规范 [16] 定义。上面显示的 @Email 注解是使用 Bean Validation 的 @Constraint 元注解定义的
@Target( { METHOD, FIELD, PARAMETER })
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
public @interface Email {
String message() default "{com.example.validation.constraints.email}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint 注解必须包含对将用于验证被修饰值的验证器类的引用。EmailValidator 类必须实现 ConstraintValidator<Email, T>,其中 T 是正在被验证的值的类型。例如,
public class EmailValidator implements ConstraintValidator<Email, String> {
public void initialize(Email email) {
...
}
public boolean isValid(String value, ConstraintValidatorContext context) {
...
}
}
因此,EmailValidator 应用于被 @Email 注解且类型为 String 的值。可以为相同的约束注解定义不同类型的验证器。
约束注解还必须定义一个 groups 元素,以指示它们所属的处理组。如果未指定组(如上例所示),则假定为 Default 组。为简化起见,JAX-RS 实现 **不** 要求支持 Default 以外的处理组。在下面的内容中,我们假设约束验证在 Default 处理组中进行。
7.3. 实体验证
请求实体正文可以映射到资源方法参数。可以通过两种方式验证这些实体。如果请求实体被映射到注解了 Bean Validation 注解的 Java bean 类,则可以使用 @Valid 启用验证
@StandardUser
class User {
@NotNull
private String firstName;
...
}
@Path("/")
class MyResourceClass {
@POST
@Consumes("application/xml")
public void registerUser(@Valid User user) {
...
}
}
在这种情况下,将调用与 @StandardUser(以及像 @NotNull 这样的非类级别约束)关联的验证器来验证映射到 user 的请求实体。或者,可以定义一个新注解并直接用于资源方法参数。
@Path("/")
class MyResourceClass {
@POST
@Consumes("application/xml")
public void registerUser(@PremiumUser User user) {
...
}
}
在上面的示例中,将使用 @PremiumUser 而不是 @StandardUser 来验证请求实体。这两种触发实体验证的方式也可以通过将 @Valid 包含在约束列表中来组合。@Valid 的存在将触发对注解 Java bean 类 **所有** 约束注解的验证。除非存在 @ConvertGroup 注解,否则此验证将在 Default 处理组中进行。有关 @ConvertGroup 的更多信息,请参阅 [16]。
从资源方法返回的响应实体正文可以通过注解资源方法本身以类似的方式进行验证。为了举例说明,假设在返回用户之前需要检查 @StandardUser 和 @PremiumUser,则 getUser 方法可以如下注解
@Path("/")
class MyResourceClass {
@GET
@Path("{id}")
@Produces("application/xml")
@Valid @PremiumUser
public User getUser(@PathParam("id") String id) {
User u = findUser(id);
return u;
}
...
}
请注意,@PremiumUser 被显式列出,而 @StandardUser 是由 @Valid 注解的存在触发的(参见本节前面 User 类的定义)。
7.4. 默认验证模式
根据 [16],默认情况下仅为所谓的 **约束** 方法启用验证。Java Beans 规范中定义的 Getter 方法不是约束方法,因此默认情况下它们不会被验证。@ValidateOnExecution 注解(在 [16] 中定义)可用于选择性地启用和禁用验证。例如,您可以按如下方式为上面显示的 getEmail 方法启用验证
@Path("/")
class MyResourceClass {
@Email
@ValidateOnExecution
public String getEmail() {
return email;
}
...
}
@ValidateOnExecution 的 type 属性的默认值是 IMPLICIT,在上面的示例中,这会导致验证 getEmail 方法。有关此注解的其他用途,请参阅 [16]。
请注意,如果 **启用** 了 Getter 方法的验证,并且资源方法的签名符合 Getter 的规则,则资源方法可能(无意中)在验证期间被调用。反之,如果 **禁用** 了 Getter 方法的验证,并且 **匹配** 的资源方法的签名符合 Getter 的规则,JAX-RS 运行时仍将在调用之前验证该方法(即,将忽略验证首选项)。
7.5. 注解继承
约束注解的继承规则在 [16] 中定义。值得注意的是,这些规则与 Annotation Inheritance 中定义的规则不兼容。总的来说,约束注解在类型层次结构中是累积的(可以加强),而 JAX-RS 注解被继承或被覆盖和忽略。
本规范的目标是通过利用现有的 Bean Validation 实现来验证 JAX-RS 资源。因此,JAX-RS 实现 **必须** 遵循 [16] 中定义的约束注解规则。
7.6. 验证和错误报告
约束注解允许出现在以下注解的相同位置:@MatrixParam、@QueryParam、@PathParam、@CookieParam、@HeaderParam 和 @Context,**除了** 类构造函数和属性 setter。具体来说,它们允许出现在资源方法参数、字段和属性 getter 以及资源类、实体参数和资源方法(返回值)中。
JAX-RS 中默认的资源类实例生命周期是每个请求。实现可以支持其他生命周期;在资源类中使用其他 JAX-RS 注解的相同注意事项也适用于约束注解。例如,注解了生命周期为单例的资源类的构造函数参数的约束验证将只执行一次。
JAX-RS 实现 **应** 使用以下过程在实例化资源类实例后对其进行验证
- 阶段 1
-
注入字段值并初始化 Bean 属性,如 Fields and Bean Properties 部分所述。
- 阶段 2
-
验证字段、属性 getter(如果已启用)和资源类上的注解。这些验证执行的顺序是实现依赖的。
- 阶段 3
-
验证已匹配的资源方法参数上的注解。
- 阶段 4
-
如果在迄今为止未发现任何约束冲突,则调用资源方法并验证返回值。
[16] 中的异常模型定义了一个基类 javax.validation.ValidationException 和几个子类来报告特定于约束定义、约束声明、组定义和约束冲突的错误。JAX-RS 实现 **必须** 提供一个默认的异常映射器(参见 Exception Mapping Providers),遵循以下规则
-
如果异常类型为
javax.validation.ValidationException或其任何子类(**不包括**javax.validation.ConstraintViolationException),则将其映射到状态码为 500(内部服务器错误)的响应。 -
如果异常是
javax.validation.ConstraintViolationException的实例,则-
如果异常是在验证方法返回类型时抛出的,则将其映射到状态码为 500(内部服务器错误)的响应 [13]。
-
否则,将其映射到状态码为 400(错误请求)的响应。
-
在所有情况下,JAX-RS 实现 **应** 包含一个描述错误来源的响应实体;但是,此实体的确切内容和格式超出了本规范的范围。如 Exception Mapping Providers 所述,应用程序可以提供自己的异常映射器,从而定制上述默认映射器。
8. 异步处理
本章介绍 JAX-RS 中的异步处理功能。异步处理在 Client API 和 Server API 中都得到支持。
8.1. 引言
异步处理是一种能够更好地有效利用处理线程的技术。在客户端,发出请求的线程也可能负责更新 UI 组件;如果该线程被阻塞等待响应,应用程序的用户感知性能将受到影响。类似地,在服务器端,处理请求的线程应避免在等待外部事件完成时阻塞,以便在此期间到达服务器的其他请求可以得到处理 [14]。
8.2. 服务器 API
8.2.1. AsyncResponse
同步处理要求资源方法在将控制权返回给 JAX-RS 实现时生成响应。异步处理允许资源方法通知 JAX-RS 实现响应尚未准备好,但将在稍后时间生成。这可以通过首先 **挂起** 然后 **恢复** 接收请求的客户端连接来实现。
让我们通过一个例子来说明这些概念
@Path("/async/longRunning")
public class MyResource {
@GET
public void longRunningOp(@Suspended final AsyncResponse ar) {
executor.submit(
new Runnable() {
public void run() {
executeLongRunningOp();
ar.resume("Hello async world!");
}
});
}
...
}
选择异步生成响应的资源方法必须使用特殊注解 @Suspended 将 AsyncResponse 类的实例作为方法参数注入。在上面的示例中,在收到 GET 请求后调用 longRunningOp 方法。该方法不立即生成响应,而是分叉一个(非请求)线程来执行长时间运行的操作并立即返回。一旦长时间运行的操作执行完成,通过调用注入的 AsyncResponse 实例上的 resume 来恢复连接并返回响应。
有关 Jakarta EE 环境中执行器、并发和线程管理的更多信息,请参阅 JSR 236 [13]。有关 JAX-RS Client API 中执行器的更多信息,请参阅 Executor Services。
8.2.1.1. 超时和回调
在挂起连接时可以指定超时值,以避免无限期等待响应。默认单位是毫秒,但可以使用任何 java.util.concurrent.TimeUnit 类型的单位。以下示例设置了 15 秒的超时,并在超时在连接恢复之前到达时注册 TimeoutHandler 实例。
@GET
public void longRunningOp(@Suspended final AsyncResponse ar) {
// Register handler and set timeout
ar.setTimeoutHandler(new TimeoutHandler() {
public void handleTimeout(AsyncResponse ar) {
ar.resume(Response.status(SERVICE_UNAVAILABLE).entity(
"Operation timed out -- please try again").build());
}
});
ar.setTimeout(15, SECONDS);
// Execute long running operation in new thread
executor.execute(
new Runnable() {
public void run() {
executeLongRunningOp();
ar.resume("Hello async world!");
}
});
}
JAX-RS 实现被要求生成一个 ServiceUnavailableException(WebApplicationException 的一个子类,状态码为 503),如果超时值到达且未注册超时处理程序。异常必须按照 Exceptions 部分的描述进行处理。如果注册的超时处理程序重置了超时值或恢复了连接并返回了响应,JAX-RS 实现不得生成异常。
还可以向 AsyncResponse 实例注册回调,以监听处理完成(CompletionCallback)和连接终止(ConnectionCallback)事件。有关如何注册这些回调的更多信息,请参阅 AsyncResponse 的 Javadoc。请注意,对 ConnectionCallback 的支持是 **可选** 的。
8.2.2. CompletionStage
与注入 AsyncResponse 替代方法是资源方法返回 CompletionStage<T> 实例,以指示底层 JAX-RS 实现已启用异步处理。可以使用 CompletionStage 重写 AsyncResponse 的示例
@Path("/async/longRunning")
public class MyResource {
@GET
public CompletionStage<String> longRunningOp() {
CompletableFuture<String> cs = new CompletableFuture<>();
executor.submit(
new Runnable() {
public void run() {
executeLongRunningOp();
cs.complete("Hello async world!");
}
});
return cs;
}
...
}
在此示例中,在资源方法中创建并返回了一个 CompletableFuture 实例;在该实例上调用 complete 方法仅在长时间运行的操作终止后执行。
8.3. EJB 资源类
如 Enterprise Java Beans (EJBs) 所述,支持 EJB 的产品中的 JAX-RS 实现还必须支持将无状态和单例会话 Bean 用作根资源类。当 EJB 方法注解了 @Asynchronous 时,EJB 容器会自动为其执行分配必要的资源。因此,在这种情况下,使用 Executor 对于生成异步响应是不必要的。
考虑以下示例
@Stateless
@Path("/")
class EJBResource {
@GET @Asynchronous
public void longRunningOp(@Suspended AsyncResponse ar) {
executeLongRunningOp();
ar.resume("Hello async world!");
}
}
在这种情况下,不需要显式的线程管理,因为这是由 EJB 容器控制的。就像本章中的其他示例一样,通过调用注入的 AsyncResponse 上的 resume 来生成响应。因此,longRunningOp 的返回类型只是 void。
8.4. Client API
流畅的 API 在调用构建过程中支持异步调用。默认情况下,调用是同步的,但可以通过调用 async 方法并(可选地)注册 InvocationCallback 实例来使其异步运行,如下所示
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://example.org/customers/{id}");
target.resolveTemplate("id", 123).request().async().get(
new InvocationCallback<Customer>() {
@Override
public void completed(Customer customer) {
// Do something
}
@Override
public void failed(Throwable throwable) {
// Process error
}
});
请注意,在此示例中,调用 async 后的 get 调用会立即返回,而不会阻塞调用者的线程。响应类型被指定为 InvocationCallback 的类型参数。当调用成功完成且响应可用时,会调用 completed 方法;当调用失败时,会使用 Throwable 实例调用 failed 方法。
所有异步调用都返回一个 Future<T> 实例,其中类型参数 T 匹配 InvocationCallback 中指定的类型。此实例可用于监视或取消异步调用
Future<Customer> ff = target.resolveTemplate("id", 123).request().async()
.get(new InvocationCallback<Customer>() {
@Override
public void completed(Customer customer) {
// Do something
}
@Override
public void failed(Throwable throwable) {
// Process error
}
});
// After waiting for a while ...
if (!ff.isDone()) {
ff.cancel(true);
}
虽然建议在执行异步调用时传递 InvocationCallback 实例,但并非强制要求。省略时,调用返回的 Future<T> 可用于通过调用 Future.get 方法来访问响应,该方法在调用成功时返回 T 的实例,在调用失败时返回 null。
9. Server-Sent Events
9.1. 引言
Server-sent events (SSE) 是 W3C 最初作为 HTML5 的一部分引入的规范,但目前由 WHATWG [17] 维护。它提供了一种从服务器到客户端建立单向通道的方法。连接是长期的:它被重用于从服务器发送的多个事件,但它仍然基于 HTTP 协议。客户端通过在 Accept 头中使用特殊媒体类型 text/event-stream 来请求打开 SSE 连接。
事件是结构化的,包含多个字段,即 event、data、id、retry 和 comment。SSE 是一种消息传递协议,其中 event 字段对应一个主题,而 id 字段可用于验证事件顺序和保证连续性。如果连接因任何原因中断,id 可以在请求头中发送,以便服务器重播过去的事件 —尽管这是一种可选行为,并非所有实现都可能支持。事件负载通过 data 字段传输,并且必须是文本格式;retry 用于控制重连,最后 comment 是一个通用字段,也可用于保持连接活动。
9.2. Client API
JAX-RS 针对SSE的客户端API的灵感来源于HTML5中对应的JavaScript API,但由于使用了不同的语言而进行了相应的更改。客户端API的入口点是SseEventSource类型,它提供了一个流畅的构建器,类似于JAX-RS API中的其他类。SseEventSource是从一个已配置了资源位置的WebTarget构建的;SseEventSource不重复WebTarget中的任何功能,只添加了SSE所需的逻辑。
以下示例展示了如何打开一个SSE连接并读取一些消息一段时间
WebTarget target = client.target("http://...");
try (SseEventSource source = SseEventSource.target(target).build()) {
source.register(System.out::println);
source.open();
Thread.sleep(500); // Consume events for just 500 ms
} catch (InterruptedException e) {
// falls through
}
如本例所示,SseEventSource实现了AutoCloseable。在打开源之前,客户端注册了一个事件消费者,该消费者仅打印每个事件。其他生命周期事件(如onComplete和onError)的处理器也得到支持,但为了简单起见,本示例仅显示了onEvent。
9.3. 服务器API
JAX-RS SSE服务器API用于接受连接并向一个或多个客户端发送事件。注入了SseEventSink并产生text/event-stream媒体类型的资源方法是一个SSE资源方法。
以下示例接受SSE连接,并使用一个执行器线程在关闭连接前发送3个事件
@GET
@Path("eventStream")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void eventStream(@Context SseEventSink eventSink,
@Context Sse sse) {
executor.execute(() -> {
try (SseEventSink sink = eventSink) {
eventSink.send(sse.newEvent("event1"));
eventSink.send(sse.newEvent("event2"));
eventSink.send(sse.newEvent("event3"));
}
});
}
SSE资源方法遵循与异步处理(参见引言)相似的模式,即代表传入连接的对象(在此例中为SseEventSink)被注入到资源方法中。
上面的示例还注入了Sse类型,该类型提供了事件和广播器的工厂方法。有关广播的更多信息,请参阅广播。请注意,与SseEventSource一样,SseEventSink接口也是自动关闭的,因此使用了上面的try-with-resources语句。
SseEventSink上的send方法返回一个CompletionStage<?>,作为提供异步发送消息到客户端的操作的句柄。
9.4. 广播
应用程序可能需要同时向多个客户端发送事件。此操作在JAX-RS中称为广播。多个SseEventSink可以注册到单个SseBroadcaster上。
广播器只能通过调用注入的Sse实例上的newBroadcaster方法来创建。SseBroadcaster的生命周期和范围完全由应用程序控制,不由JAX-RS 运行时控制。以下示例展示了广播器的使用,请注意资源类上的@Singleton注解
@Path("/")
@Singleton
public class SseResource {
@Context
private Sse sse;
private volatile SseBroadcaster sseBroadcaster;
@PostConstruct
public init() {
this.sseBroadcaster = sse.newBroadcaster();
}
@GET
@Path("register")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void register(@Context SseEventSink eventSink) {
eventSink.send(sse.newEvent("welcome!"));
sseBroadcaster.register(eventSink);
}
@POST
@Path("broadcast")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public void broadcast(@FormParam("event") String event) {
sseBroadcaster.broadcast(sse.newEvent(event));
}
}
广播器上的register方法用于添加新的SseEventSink;broadcast方法用于将SSE事件发送给所有已注册的消费者。
9.5. 处理管道
来自SSE客户端的连接由可注入的SseEventSink实例表示。SSE与异步处理(参见异步处理章节)之间存在一些相似之处。异步响应最多可以恢复一次,而SseEventSink可以多次用于流式传输单个事件。
10. 上下文
JAX-RS 提供了用于获取和处理应用程序部署上下文以及单个请求上下文的信息的设施。这些信息可供Application子类(参见配置章节)、根资源类(参见资源章节)和提供者(参见提供者章节)使用。本章将介绍这些设施。
10.1. 并发
上下文特定于特定请求,但是某些JAX-RS 组件的实例(生命周期非按请求的提供者和资源类)可能需要支持多个并发请求。在注入上下文类型列表中类型的实例时,提供的实例必须能够为特定请求选择正确的上下文。使用线程局部代理是实现此目的的常用方法。
10.2. 上下文类型
本节介绍可用于提供者(客户端和服务器)以及资源类和Application子类(仅服务器端)的上下文类型。除了Configuration和Providers(可在客户端和服务器端提供者中注入)之外,所有其他类型都是仅服务器端。
10.2.1. Application
应用程序提供的Application子类的实例可以使用@Context 注解注入到类字段或方法参数中。访问Application子类实例允许在其中集中配置信息。请注意,这不能注入到Application子类本身,因为这会产生循环依赖。
10.2.2. URI和URI模板
UriInfo 的实例可以使用@Context 注解注入到类字段或方法参数中。UriInfo提供关于请求URI组件的静态和动态的、每个请求的信息。例如,以下代码将返回请求中任何查询参数的名称
@GET
@Produces{"text/plain"}
public String listQueryParamNames(@Context UriInfo info) {
StringBuilder buf = new StringBuilder();
for (String param: info.getQueryParameters().keySet()) {
buf.append(param);
buf.append("\n");
}
return buf.toString();
}
请注意,UriInfo的方法提供对请求URI信息的访问,遵循请求预处理中描述的预处理。
10.2.3. Headers
HttpHeaders 的实例可以使用@Context 注解注入到类字段或方法参数中。HttpHeaders提供通过映射形式或强类型便捷方法访问请求头信息。例如,以下代码将返回请求中所有头部的名称
@GET
@Produces{"text/plain"}
public String listHeaderNames(@Context HttpHeaders headers) {
StringBuilder buf = new StringBuilder();
for (String header: headers.getRequestHeaders().keySet()) {
buf.append(header);
buf.append("\n");
}
return buf.toString();
}
请注意,HttpHeaders的方法提供对请求信息的访问,遵循请求预处理中描述的预处理。
响应头可以使用Response 类提供,更多详细信息请参见返回类型。
10.2.4. 内容协商和先决条件
JAX-RS 使用Request接口简化了对内容协商和先决条件的支持。Request的实例可以使用@Context 注解注入到类字段或方法参数中。Request的方法允许调用者确定最佳匹配的表示变体,并评估资源的当前状态是否符合请求中的任何先决条件。先决条件支持方法返回一个ResponseBuilder,该ResponseBuilder可以返回给客户端,告知它请求的先决条件未满足。例如,以下代码在更新资源之前检查当前实体标签是否与请求中的任何先决条件匹配
@PUT
public Response updateFoo(@Context Request request, Foo foo) {
EntityTag tag = getCurrentTag();
ResponseBuilder responseBuilder = request.evaluatePreconditions(tag);
if (responseBuilder != null)
return responseBuilder.build();
else
return doUpdate(foo);
}
应用程序还可以设置内容位置、过期日期和缓存控制信息到返回的ResponseBuilder中,然后再构建响应。
10.2.5. 安全上下文
SecurityContext接口提供了访问当前请求的安全上下文信息的能力。SecurityContext的实例可以使用@Context 注解注入到类字段或方法参数中。SecurityContext的方法提供了对当前用户主体、关于请求者所承担的角色信息、请求是否通过安全通道到达以及使用的认证方案的访问。
10.2.6. Providers
Providers接口允许根据一组搜索条件查找提供者实例。Providers的实例可以使用@Context 注解注入到类字段或方法参数中。
此接口预计主要对希望使用其他提供者功能的提供者作者感兴趣。它可在客户端和服务器端提供者中注入。
10.2.7. 资源上下文
ResourceContext接口提供了访问默认的按请求范围内的资源或子资源类的实例化和初始化。它可以被注入以帮助应用程序创建和初始化实例,或仅初始化实例。
让我们回顾一下子资源中的示例,并做一些简单的修改
@Path("widgets")
public class WidgetsResource {
@Context
private ResourceContext rc;
@Path("{id}")
public WidgetResource findWidget(@PathParam("id") String id) {
return rc.initResource(new WidgetResource(id));
}
}
public class WidgetResource {
@Context
private HttpHeaders headers;
public WidgetResource(String id) {...}
@GET
public Widget getDetails() {...}
}
请注意,资源定位器findWidget在WidgetsResource中返回的实例在返回之前使用注入的ResourceContext进行初始化。如果没有这一步,WidgetResource中的headers字段将无法正确初始化。
10.2.8. 配置
客户端和服务器运行时的配置都可以通过@Context进行注入。这些配置可用于提供者(客户端或服务器)和资源类(仅服务器端)的注入。
例如,考虑一个客户端日志过滤器,它不仅记录消息,还记录处理请求期间启用的某些功能的信息
public class LoggingFilter implements ClientRequestFilter {
@Context
private Configuration config;
@Override
public void filter(ClientRequestContext ctx) throws IOException {
if (config.isEnabled(MyFeature.class)) {
logMyFeatureEnabled(ctx);
}
logMessage(ctx);
}
...
}
上面的过滤器注入了一个客户端运行时配置,并调用其isEnabled方法来检查MyFeature是否已启用。
11. 环境
JAX-RS 根资源类或提供者可用的容器管理的资源取决于其部署的环境。第 [contexttypes] 节描述了不依赖于容器的可用上下文类型。以下各节描述了在各种环境中部署的JAX-RS 根资源类或提供者可用的其他容器管理的资源。
11.1. Servlet容器
@Context 注解可用于指示对Servlet定义的资源的依赖。基于Servlet的实现必须支持以下Servlet定义类型的注入:ServletConfig, ServletContext, HttpServletRequest和HttpServletResponse。
注入的HttpServletRequest允许资源方法流式传输请求实体的内容。如果资源方法有一个参数的值来自请求实体,那么流将已经被消耗,尝试访问它可能会导致异常。
注入的HttpServletResponse允许资源方法在返回之前提交HTTP响应。实现必须检查已提交的状态,并且仅在响应尚未提交时才处理返回值。
Servlet过滤器可以通过访问请求参数来触发请求体消耗。在Servlet容器中,如果请求体已经被消耗,则@FormParam注解和application/x-www-form-urlencoded的标准实体提供者必须从Servlet请求参数中获取其值。Servlet API不区分URI中的参数和请求体中的参数,因此基于URI的查询参数可能包含在实体参数中。
11.2. 与Jakarta EE技术的集成
本节描述了在与支持以下规范的产品中结合使用JAX-RS 实现时适用的其他要求。
11.2.1. Servlets
在也支持Servlet规范的产品中,实现必须支持打包为Web应用程序的JAX-RS 应用程序。有关Web应用程序打包的更多信息,请参见第[servlet]节。
建议JAX-RS 实现通过在底层Servlet 3容器中启用异步处理(即asyncSupported=true)来提供异步处理支持(如异步处理中所定义)。JAX-RS 实现支持在Servlet容器版本早于3时运行的异步处理是可选的。
如Servlet容器中所解释,可以使用@Context 注解注入Servlet定义的类型。此外,Web应用程序的<context-param>和Servlet的<init-param>可用于定义传递给服务器端功能或注入到服务器端JAX-RS组件的应用程序属性。有关更多信息,请参见Application.getProperties的Javadoc。
11.2.2. Managed Beans
在支持Managed Beans的产品中,实现必须支持将Managed Beans用作根资源类、提供者和Application子类。
例如,一个使用managed-bean拦截器的bean可以定义为JAX-RS 资源,如下所示
@ManagedBean
@Path("/managedbean")
public class ManagedBeanResource {
public static class MyInterceptor {
@AroundInvoke
public String around(InvocationContext ctx) throws Exception {
System.out.println("around() called");
return (String) ctx.proceed();
}
}
@GET
@Produces("text/plain")
@Interceptors(MyInterceptor.class)
public String getIt() {
return "Hi managedbean!";
}
}
上面的示例使用managed-bean拦截器来拦截对资源方法getIt的调用。有关Managed Beans的其他要求,请参见附加要求。
11.2.3. Context and Dependency Injection (CDI)
在支持CDI的产品中,实现必须支持将CDI风格的Beans用作根资源类、提供者和Application子类。提供者和Application子类必须是单例或使用应用程序范围。
例如,假设通过包含beans.xml文件启用了CDI,一个可以定义为JAX-RS 资源的CDI风格bean如下所示
@Path("/cdibean")
public class CdiBeanResource {
@Inject MyOtherCdiBean bean; // CDI injected bean
@GET
@Produces("text/plain")
public String getIt() {
return bean.getIt();
}
}
上面的示例通过使用另一个类型为MyOtherCdiBean的bean,来返回资源表示,从而利用了CDI中提供的类型安全依赖注入。有关CDI风格Beans的其他要求,请参见附加要求。
11.2.4. Enterprise Java Beans (EJBs)
在支持EJBs的产品中,实现必须支持将无状态和单例会话bean用作根资源类、提供者和Application子类。JAX-RS 注解可以应用于EJB的本地接口中的方法,或直接应用于无接口EJB中的方法。资源类注解(如@Path)必须直接应用于EJB的类,遵循注解继承中定义的注解继承规则。
例如,实现本地接口的无状态EJB可以定义为JAX-RS 资源类,如下所示
@Local
public interface LocalEjb {
@GET
@Produces("text/plain")
public String getIt();
}
@Stateless
@Path("/stateless")
public class StatelessEjbResource implements LocalEjb {
@Override
public String getIt() {
return "Hi stateless!";
}
}
JAX-RS 实现被要求通过检查类和本地接口上的注解来发现EJB;它们不被要求读取EJB部署描述符(ejb-jar.xml)。因此,EJB部署描述符中的任何用于覆盖EJB注解或提供附加元数据的信息都可能导致非可移植的JAX-RS 应用程序。
如果应用程序未包含EJBException或其子类的ExceptionMapper,则EJB资源类或提供者方法抛出的异常必须被解包并按异常中所述进行处理。
11.2.6. Java API for JSON Processing
在支持Java API for JSON Processing (JSON-P) [18]的产品中,实现必须支持JsonValue及其所有子类型:JsonStructure, JsonObject, JsonArray, JsonString和JsonNumber的实体提供者。
请注意,JSON-P API中的其他类型,如JsonParser, JsonGenerator, JsonReader和JsonWriter,也可以通过InputStream和StreamingOutput的实体提供者集成到JAX-RS应用程序中。
11.2.7. Java API for JSON Binding
在支持Java API for JSON Binding (JSON-B) [19]的产品中,实现必须支持与以下媒体类型结合使用的所有Java类型的实体提供者:application/json, text/json以及任何匹配/json或/*+json的媒体类型。
请注意,如果同一环境中同时支持JSON-B和JSON-P,那么JSON-B的实体提供者对于除JsonValue及其子类型以外的所有类型都优先于JSON-P。
11.3. 其他
其他容器技术可以指定其自己的可注入资源集,但至少必须支持访问上下文类型中列出的类型。
12. Runtime Delegate
RuntimeDelegate 是一个抽象工厂类,它提供各种方法来创建实现JAX-RS API的对象。这些方法设计用于其他JAX-RS API类使用,不打算由应用程序直接调用。RuntimeDelegate允许标准的JAX-RS API类在不更改代码的情况下使用不同的JAX-RS 实现。
JAX-RS 的实现必须提供RuntimeDelegate的具体子类。使用提供的RuntimeDelegate,可以通过以下两种方式之一将其提供给JAX-RS
-
可以实例化
RuntimeDelegate的实例并使用其静态方法setInstance注入。在这种情况下,实现负责创建实例;此选项旨在与基于IoC框架的实现一起使用。 -
可以配置要使用的类,请参见配置。在这种情况下,JAX-RS负责实例化该类的实例,并且已配置的类必须有一个不带参数的公共构造函数。
请注意,实现可以提供RuntimeDelegate API类的替代实现(只要它通过TCK签名测试并按照规范运行),以支持定位具体子类的替代方法。
JAX-RS 实现可能会依赖于特定的RuntimeDelegate 实现;应用程序不应使用应用程序提供的替代品覆盖提供的RuntimeDelegate 实例,这样做可能会导致意外的问题。
12.1. 配置
如果未通过注入提供,则提供的RuntimeDelegate API类使用以下算法获取具体实现类。下面列出的步骤按顺序执行,在每个步骤中,最多会产生一个候选实现类名。然后,实现将尝试使用当前上下文类加载器或java.lang.Class.forName(String)方法加载具有给定类名的类。一旦某个步骤成功加载了实现类,算法就会终止。
-
使用Java SE类
java.util.ServiceLoader尝试从META-INF/services/javax.ws.rs.ext.RuntimeDelegate加载实现。请注意,为了尝试上下文类加载器和当前类加载器(如上所述),这可能需要多次调用ServiceLoader.load(Class, ClassLoader)方法。[16] -
如果存在
${java.home}/lib/jaxrs.properties文件,并且该文件可以被java.util.Properties.load(InputStream)方法读取,并且其中包含一个键为javax.ws.rs.ext.RuntimeDelegate的条目,则该条目的值将用作实现类的名称。 -
如果定义了名为
javax.ws.rs.ext.RuntimeDelegate的系统属性,则其值将用作实现类的名称。 -
最后,使用默认的实现类名。
附录 A:注解摘要
注解 |
目标 |
描述 |
|
类型或方法 |
指定可以消耗的媒体类型列表。 |
|
类型或方法 |
指定可以产生的媒体类型列表。 |
|
方法 |
指定所注解方法处理HTTP GET请求。 |
|
方法 |
指定所注解方法处理HTTP POST请求。 |
|
方法 |
指定所注解方法处理HTTP PUT请求。 |
|
方法 |
指定所注解方法处理HTTP DELETE请求。 |
|
方法 |
指定所注解方法处理HTTP PATCH请求。 |
|
方法 |
指定所注解方法处理HTTP HEAD请求。请注意,HEAD可能会被自动处理,参见HEAD和OPTIONS。 |
|
方法 |
指定所注解方法处理HTTP OPTIONS请求。 |
|
类型 |
指定资源范围内的应用程序路径,该路径构成所有根资源类的基URI。 |
|
类型或方法 |
为资源指定一个相对路径。当用于类时,此注解标识该类为根资源。当用于方法时,此注解标识一个子资源方法或定位器。 |
|
参数、字段或方法 |
指定方法参数、类字段或Bean属性的值将从请求URI路径中提取。注解的值标识URI模板参数的名称。 |
|
参数、字段或方法 |
指定方法参数、类字段或Bean属性的值将从URI查询参数中提取。注解的值标识查询参数的名称。 |
|
参数、字段或方法 |
指定方法参数的值将从请求实体体中的表单参数中提取。注解的值标识表单参数的名称。请注意,虽然注解目标允许用于字段和方法,但规范仅要求支持用于资源方法参数。 |
|
参数、字段或方法 |
指定方法参数、类字段或Bean属性的值将从URI矩阵参数中提取。注解的值标识矩阵参数的名称。 |
|
参数、字段或方法 |
指定方法参数、类字段或Bean属性的值将从HTTP cookie中提取。注解的值标识cookie的名称。 |
|
参数、字段或方法 |
指定方法参数、类字段或Bean属性的值将从HTTP头中提取。注解的值标识HTTP头的名称。 |
|
类型、构造函数、方法、字段或参数 |
禁用对路径、查询、表单和矩阵参数的自动URI解码。 |
|
参数、字段或方法 |
为用 |
|
字段、方法或参数 |
|
|
注解 |
为请求方法设计器注解指定HTTP方法。 |
|
类型 |
指定所注解的类实现了JAX-RS扩展接口。 |
自JAX-RS 2.0起 |
||
|
注解 |
元注解,用于创建注解以将过滤器或拦截器绑定到资源方法和应用程序。名称绑定仅支持作为Server API的一部分。 |
|
参数 |
指示资源方法是异步的。即,它在返回时不会产生响应。JAX-RS实现将挂起传入连接,直到响应可用。 |
|
类型 |
全局绑定注解,可应用于容器过滤器,以指示它应全局应用并在资源方法匹配之前应用。 |
|
参数、字段或方法 |
可用于注入用户定义的Bean,其字段和属性可以被JAX-RS参数注解注解。 |
|
类型 |
可用于将提供者的适用性限制为仅客户端API或仅服务器API。如果省略,提供者可以在任一上下文中用于。 |
|
类型 |
指示应仅在实际请求值时才发生对默认值委托给 |
附录 B:HTTP头支持
下表列出了直接支持的HTTP头,这些头可以通过JAX-RS 运行时自动支持,或由应用程序使用JAX-RS API支持。任何请求头都可以使用HttpHeaders获取,请参见头;此处未列出的响应头可以使用ResponseBuilder.header方法设置。
Header |
描述 |
|
运行时用于选择资源方法,与 |
|
如果应用程序使用 |
|
如果应用程序使用 |
|
如果应用程序使用 |
|
包含在自动生成的405错误响应(参见第[request_matching]节)和自动生成的OPTIONS请求响应(参见HEAD和OPTIONS)中。 |
|
取决于容器,信息可通过 |
|
参见 |
|
响应头由应用程序使用 |
|
响应头由应用程序使用 |
|
对于请求,运行时会自动处理;对于响应,如果值由用于序列化消息实体的 |
|
请求头由运行时用于选择资源方法,与 |
|
参见 |
|
根据HTTP/1.1自动包含在响应中。 |
|
参见 |
|
取决于底层容器。 |
|
由应用程序使用 |
|
如果应用程序使用相应的 |
|
如果应用程序使用相应的 |
|
如果应用程序使用相应的 |
|
由应用程序使用 |
|
参见 |
|
|
|
由应用程序使用 |
|
取决于容器。 |
附录 D:变更日志
D.2. 自2.1公开审查以来的更改
-
处理管道:新增章节描述SSE处理管道。
-
子资源:允许子资源定位器返回类。
-
执行器服务:移除了不支持Java EE API的Java并发工具的所有环境的默认值。
-
Java API for JSON Processing:支持
JsonValue及其所有子类型。 -
Java API for JSON Binding:JSON-B支持的新章节。
-
CompletionStage:作为资源方法返回类型
CompletionStage<T>的新章节。 -
优先级:引入
@Priority支持所有提供者的新章节。 -
实体提供者:修改了算法步骤,增加了对消息体读取器和写入器选择中的
@Priority的支持。
D.5. 自2.0建议最终草案以来的更改
-
标准实体提供者:新的异常
NoContentException用于处理零长度实体。 -
可配置类型:
Invocation和Invocation.Builder不再是可配置类型。 -
过滤器和实体拦截器:基于客户端API的更改更新了示例。
-
默认验证模式:基于Bean Validation API的更改更新了章节。
-
验证和错误报告:对第二阶段进行了少量措辞修改。
-
超时和回调:改进了示例,以在超时到期时返回响应。
-
客户端API:基于客户端API的更改更新了示例。
-
上下文类型:澄清了服务器与客户端可注入类型。
-
配置:关于注入
Configuration实例的新章节。 -
Bean Validation:新章节声明,仅在支持Bean Validation的产品中才需要支持资源验证。
D.6. 自2.0公开审查草案以来的更改
-
在Javadoc中:类
MessageBodyWriter中的方法getSize已弃用。 -
字段和Bean属性:新增
ParamConverter的步骤。 -
标准实体提供者:零长度实体和原始类型的特殊情况。
-
第客户端API章:更新了客户端API类型的配置示例和文本。
configuration方法已被移除,取而代之的是Configurable接口。 -
第客户端API章:
ClientFactory已重命名为ClientBuilder。 -
第客户端API章:取消了对
@Uri注解的支持。 -
实体拦截器:新增段落,阐明当应用程序代码直接调用
readFrom或writeTo方法时,不会调用实体拦截器。 -
实体拦截器:改进了
GzipInterceptor示例。 -
名称绑定:阐明了具有多个注解的名称绑定。使语义与CDI拦截器绑定兼容。
Application子类的名称绑定。 -
优先级:注解
@BindingPriority已由通用注解@Priority替换。 -
异常:阐明了通过异常映射器映射的响应的处理。
-
服务器API:更新了本节中的示例。新增了JSR 236的引用。
-
超时和回调:改进了示例。
-
客户端API:修复了示例中
failed方法的签名。 -
默认验证模式:关于默认验证和
@ValidateExecutable的新章节。 -
验证和错误报告:恢复了验证阶段。
-
Java API for JSON Processing:关于与JSON-P集成的新的章节。
D.7. 自2.0早期草案(第三版)以来的更改
-
“验证”章节:从PR中移除。JAX-RS 2.0将把Bean Validation委托给CDI。只有作为CDI Bean的资源类才会被验证。
-
异常:阐明了步骤必须按顺序进行,并且只有在无法映射时,才会将可抛出项传播到底层容器。
-
资源方法:在列表中添加了
@OPTIONS。 -
返回类型:关于匿名内部类的新注释。
-
请求匹配:允许多个根资源类在算法中共享相同的URI。注意步骤1和2输出和输入的变化。
-
消息体写入器:抛出新异常
InternalServerErrorException。 -
实体提供者:移除了需要使用JavaBeans Activation Framework[20]来映射对象到表示以及反之的步骤。EG认为此功能令人困惑且在不同实现之间不可移植。
-
标准实体提供者:支持Java类型
java.lang.Boolean、java.lang.Character、java.lang.Number和媒体类型text/plain的预打包读取器和写入器。 -
标准实体提供者:详细说明了读取器和零长度请求实体的行为。
-
动态绑定:
DynamicBinder已替换为DynamicFeature。 -
异常:阐明了使用异常映射器映射的响应的处理。
-
第异步处理章:更新了与服务器API相关的章节。
@Suspended注解、超时和回调。 -
超时和回调:抛出新异常
ServiceUnavailableException。 -
资源上下文:
ResourceContext的新章节。 -
Enterprise Java Beans (EJBs):根据JAX-RS注解继承规则,阐明了注解的位置。
-
过滤器和拦截器:将
@PostMatching替换为@PreMatching。Post-matching现在是默认值。 -
注解摘要:编辑了关于JAX-RS 2.0注解的章节。
D.8. 自2.0早期草案(第二版)以来的更改
-
URI模板:新示例显示了
@PathParam不可用于注入的场景。 -
请求匹配:正式化了匹配算法中一些含糊不清的部分。定义了媒体类型之间的正式排序,并强调了实现应在匹配模糊时报告警告的情况。
-
请求匹配:显示资源匹配算法实际运行的新示例。
-
将URI模板转换为正则表达式:关于正则表达式语法的新的脚注。
-
第提供者章:
@Provider注解现在仅对提供者的自动发现是必需的(通常通过类扫描)。对于手动在Application或Configuration等类中注册的提供者,它不再是必需的。 -
自动发现:关于提供者类的自动发现的新章节。只有被
@Provider注解的才必须被自动发现。 -
第客户端API章:特性现在是提供者,可以这样注册。功能不再可以被禁用。
-
第客户端API章:类
Target已重命名为WebTarget。删除了描述如何使用构建器工厂类的文本(不再支持)。其他一些小的更新和错别字已修复。 -
第过滤器和拦截器章:修订了过滤器的扩展点。客户端API中的新过滤器接口
ClientRequestFilter和ClientResponseFilter,以及服务器API中的新过滤器接口ContainerRequestFilter和ContainerResponseFilter。相应的上下文类在本章中也已更新。ContainerRequestFilter在资源匹配之前执行,除非被注解为@PostMatching。现在不再可能在客户端API和服务器API之间共享过滤器实现;实体拦截器仍然可以共享。 -
第过滤器和拦截器章:删除了关于过滤器和实体拦截器之间关系的部分(包括图)。
-
全局绑定:根据
@Provider的新语义,阐明了过滤器和实体拦截器的全局绑定,用于自动发现。 -
动态绑定:用于过滤器的
DynamicBinding接口已由DynamicBinder替换。动态绑定器是一种新的提供者类型,用于将过滤器和实体拦截器与资源方法绑定。 -
“验证”章节:使用媒体类型名称而非Java常量以求清晰。对约束注解使用更具描述性的名称。将约束注解的继承规则更改为遵循[16]中定义的规则。关于
@Valid对返回值支持的新注释。修复了阶段编号的拼写错误。 -
超时和回调:新增句子,关于多次调用
suspend或在被注解为@Suspend的方法中调用。 -
EJB资源类:关于EJB资源类及其被注解为
@Asynchronous的方法的新章节。 -
第环境章:重构了章节,为JAX-RS与EE技术集成的每个技术都添加了子章节。新增了示例。
D.9. 自2.0早期草案以来的更改
-
专家组名单:更新了2.0专家组名单。
-
致谢:更新了2.0版本的致谢。
-
异常:新章节描述了所有提供者的异常处理。相关的更改引用了此新章节。
-
异常:过滤器和拦截器异常的新章节。相关的更改引用了此新章节。
-
请求匹配:更新了步骤2a,使其仅在\(M \neq \{\}\)时才转到步骤3。
-
请求匹配:关于在请求匹配期间使用服务器质量参数(qs-value)的新句子。
-
第过滤器和拦截器章:用于在资源匹配之前执行的预匹配过滤器的新的扩展点。
-
第过滤器和拦截器章:过滤器方法不再返回下一个操作;通过在关联的上下文中设置响应来停止执行请求过滤器链。
-
第过滤器和拦截器章:为清晰起见,将处理程序重命名为实体拦截器(在此日志中包含)。
-
第全局绑定节:全局绑定现在是过滤器和实体拦截器的默认设置。
@GlobalBinding注解已被删除。 -
第优先级节:阐明了响应过滤器链基于绑定优先级的反向排序。
-
附录 C:在对第过滤器和拦截器章进行更改后,已从此版本中删除。
-
“验证”章节:改为实例化后验证策略,其中不再支持构造函数参数和setter的验证。简化了验证过程,以更好地符合Bean Validation 1.1 [16]。特别是,资源类、字段和属性getter的验证都在一个步骤中完成。
D.10. 自1.1发布以来的更改
-
状态:更新了JSR页面的URL等。
-
非目标:移除了客户端API作为非目标。
-
术语:添加了新术语。
-
专家组名单:列出了2.0专家组名单。
-
致谢:2.0版本的致谢。
-
第应用程序章:移除了有些通用的关于验证的章节,以避免与“验证”章节中定义的验证类型混淆。
-
Servlet:阐明了Servlet 3框架的可插拔性使用。添加了示例web.xml文件和汇总所有情况的表。
-
实体参数:阐明了实体参数的概念,即未被任何JAX-RS注解注解的参数。
-
声明媒体类型能力:解释了质量因子q的使用。引入了服务器端质量因子qs并包含示例。
-
注解继承:关于冲突注解的句子,并建议重复注解以与Java EE其他规范保持一致。
-
请求预处理:突出显示算法中每一步的输入和输出。进行了少量编辑以简化表示。
-
确定响应的MediaType:更新了算法以支持服务器端质量因子qs。
-
客户端API:新的客户端API章节。
-
过滤器和拦截器:新的过滤器和拦截器章节。
-
“验证”章节:新的验证章节。
-
第异步处理章:新的异步处理章节。
-
附录注解摘要:2.0注解的新章节。
-
附录 C:描述过滤器和拦截器扩展点的新附录。
D.12. 自建议最终草案以来的更改
-
请求匹配:增加了额外的排序标准,以便包含显式正则表达式的模板排在默认模板之前。
-
请求匹配、确定响应的MediaType、声明媒体类型能力和声明媒体类型能力:Q值未在
@Consumes或@Produces中使用。 -
消息体写入器:修复了算法,使其引用确定响应的MediaType而不是重复。修复了当媒体类型已确定但找不到合适的写入器时返回的状态码。
-
第Runtime Delegate章:阐明实现可以提供替代的
RuntimeDelegateAPI类。
D.13. 自公开审查草案以来的更改
-
应用程序:将ApplicationConfig类重命名为Application。
-
第资源章:UriBuilder被重构为始终对组件进行编码。
-
字段和Bean属性:
FormParam不再要求在字段或属性上支持。 -
返回类型:添加了描述如何从方法返回类型和返回的实例确定原始类型和泛型类型的文本。
-
URI模板:模板参数可以指定构成其捕获组的正则表达式。
-
请求预处理:使预处理后的URI可用,而不是原始请求URI。添加了URI标准化。
-
请求预处理:删除了基于URI的内容协商。
-
请求匹配:重新组织了请求匹配算法,以消除冗余和提高可读性,功能无变化。
-
将URI模板转换为正则表达式:对正则表达式进行了更改,以消除边缘情况。
-
实体提供者:增加了当找不到实体提供者时使用JavaBean Activation Framework的要求。
-
标准实体提供者:要求标准JAXB实体提供者优先使用应用程序提供的JAXB上下文,而不是自己的上下文。
-
上下文提供者:增加了对指定上下文提供者媒体类型能力的支持。
-
上下文类型:从可注入资源列表中移除了
ContextResolver。 -
提供者:名称更改为Providers,移除了特定于实体提供者的文本,以反映更通用的功能。
-
HTTP头支持:新附录描述了特定HTTP头的支持位置。
参考文献
-
[1] R. Fielding. 架构风格和基于网络的软件架构的设计。博士论文,加州大学欧文分校,2000年。参见https://roy.gbiv.com/pubs/dissertation/top.htm。
-
[2] REST Wiki。网站。参见http://rest.blueoxen.net/cgi-bin/wiki.pl。
-
[3] Representational State Transfer。网站,维基百科。参见https://en.wikipedia.org/wiki/Representational State Transfer。
-
[4] R. Fielding, J. Gettys, J. C. Mogul, H. Frystyk, and T. Berners-Lee. RFC 2616: Hypertext Transfer Protocol – HTTP/1.1。RFC,IETF,1997年1月。参见https://www.ietf.org/rfc/rfc2616.txt。
-
[5] T. Berners-Lee, R. Fielding, and L. Masinter. RFC 3986: Uniform Resource Identifier (URI): Generic Syntax。RFC,IETF,2005年1月。参见https://www.ietf.org/rfc/rfc3986.txt。
-
[6] L. Dusseault. RFC 4918: HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)。RFC,IETF,2007年6月。参见https://www.ietf.org/rfc/rfc4918.txt。
-
[7] J.C. Gregorio and B. de hOra. The Atom Publishing Protocol。Internet Draft,IETF,2007年3月。参见https://bitworking.org/projects/atom/draft-ietf-atompub-protocol-14.html。
-
[8] Jakarta™ Servlet Specification, Version 4.0。可访问:https://jakarta.ee/specifications/servlet/4.0
-
[9] R. Chinnici, M. Hadley, and R. Mordani. Java API for XML Web Services。JSR,JCP,2005年8月。参见https://jcp.org/en/jsr/detail?id=224。
-
[10] S. Bradner. RFC 2119: Keywords for use in RFCs to Indicate Requirement Levels。RFC,IETF,1997年3月。参见https://www.ietf.org/rfc/rfc2119.txt。
-
[11] RxJava:一个用于Java VM的组合异步和事件驱动程序的可观察序列库。参见https://github.com/ReactiveX/RxJava。
-
[12] RxJava 2.0:2.0版本有什么不同。参见https://github.com/ReactiveX/RxJava/wiki/What’s-different-in-2.0。
-
[13] Jakarta™ Concurrency Specification, Version 1.1。可访问:https://jakarta.ee/specifications/concurrency/1.1
-
[14] Jakarta™ Contexts and Dependency Injection Specification, Version 2.0。可访问:https://jakarta.ee/specifications/cdi/2.0
-
[15] Jakarta™ Annotations Specification, Version 1.3。可访问:https://jakarta.ee/specifications/annotations/1.3
-
[16] Jakarta™ Bean Validation Specification, Version 2.0。可访问:https://jakarta.ee/specifications/bean-validation/2.0
-
[17] Server-Sent Events。参见https://html.whatwg.cn/#server-sent-events。
-
[18] Jakarta™ JSON Processing Specification, Version 1.1。可访问:https://jakarta.ee/specifications/jsonp/1.1
-
[19] Jakarta™ JSON Binding Specification, Version 1.0。可访问:https://jakarta.ee/specifications/jsonb/1.0
-
[20] Bill Shannon. JavaBeans Activation Framework。JSR,JCP,2006年5月。参见https://jcp.org/en/jsr/detail?id=925。