Specification: Jakarta RESTful Web Services

Version: 2.2-SNAPSHOT

Status: DRAFT

Release: December 10, 2020

版权所有 (c) 2019 Eclipse Foundation。

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] 复制或派生的材料。”

免责声明

本文档按“原样”提供,版权持有人和 Eclipse Foundation 不作任何明示或暗示的陈述或保证,包括但不限于适销性、特定用途的适用性、非侵权性或所有权保证;本文档的内容适用于任何目的;或此类内容的实现不会侵犯任何第三方专利、版权、商标或其他权利。

版权持有人和 Eclipse Foundation 对因使用本文档或其中内容的性能或实现而产生的任何直接、间接、特殊或后果性损害概不负责。

未经事先书面许可,不得在与本文档或其内容相关的广告或宣传中使用版权持有人或 Eclipse Foundation 的名称和商标。本文档的版权所有权始终归版权持有人。

1. 简介

本规范定义了一套 Java API,用于开发符合 Representational State Transfer[1] (REST) 架构样式的 Web 服务。假定读者熟悉 REST;有关 REST 架构样式和 RESTful Web 服务的更多信息,请参阅:

  • Architectural Styles and the Design of Network-based Software Architectures[1]

  • REST Wiki[2]

  • Wikipedia 上的 Representational State Transfer[3]

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 环境中,可以通过 RuntimeDelegatecreateEndpoint 方法获取端点类的已配置实例。应用程序提供 Application 的实例和所需的端点类型。实现 MAY 支持任意期望类型的零个或多个端点类型。

结果端点类实例如何用于发布应用程序不在本规范的范围之内。

2.3.1.1. JAX-WS

支持通过 JAX-WS 发布实现的必须支持具有 javax.xml.ws.Provider 端点类型的 createEndpoint。JAX-WS 描述了如何在 SE 环境中发布基于 Provider 的端点。

2.3.2. Servlet

JAX-RS 应用程序打包为 .war 文件中的 Web 应用程序。应用程序类打包在 WEB-INF/classesWEB-INF/lib 中,必需的库打包在 WEB-INF/lib 中。有关 Web 应用程序打包的完整详细信息,请参阅 Servlet 规范。

建议实现支持 Servlet 3 框架的可插拔机制,以实现容器之间的可移植性并利用容器提供的类扫描功能。在使用可插拔机制时,必须满足以下条件:

  • 如果没有 Application 子类存在,JAX-RS 实现必须动态添加一个 servlet,并将其名称设置为:

    javax.ws.rs.core.Application

    并自动发现打包在应用程序中的所有根资源类和提供者。此外,应用程序必须打包一个 web.xml 文件,该文件指定已添加 servlet 的 servlet 映射。 such 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>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 Application subclass is present

    • 如果已有一个 servlet 处理此应用程序。即,一个 servlet 具有名为

      javax.ws.rs.Application

      且其值为 Application 子类的完全限定名称,则 JAX-RS 实现无需其他配置步骤。

    • 如果没有 servlet 处理此应用程序,JAX-RS 实现必须动态添加一个 servlet,其完全限定名称必须是 Application 子类的名称。如果 Application 子类被注解为 @ApplicationPath,实现必须使用此注解的值加上 “/*” 来定义已添加服务器的映射。否则,应用程序必须打包一个 web.xml 文件,该文件指定 servlet 映射。例如,如果 org.example.MyApplicationApplication 子类的名称,则示例 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.getClassesApplication.getSingletons 都返回一个空集合,则必须包含 Web 应用程序中打包的所有根资源类和提供者,并且 JAX-RS 实现必须通过如上所述扫描 .war 文件自动发现它们。如果 getClassesgetSingletons 返回一个非空集合,则必须仅包含返回的类或单例。如果存在一个 Application 子类,并且 Application.getClassesApplication.getSingletons 都返回一个空集合,则 Web 应用程序中打包的所有根资源类和提供者都必须包含在已发布的 JAX-RS 应用程序中,并且 JAX-RS 实现必须通过扫描 .war 文件自动发现它们。如果 getClassesgetSingletons 返回一个非空集合,则仅包含返回的类或单例。

下表总结了 Servlet 3 框架的可插拔机制:

表 1. Servlet 3 框架可插拔情况摘要

条件

操作

Servlet 名称

web.xml

Application 子类

添加 servlet

javax.ws.rs.core.Application

对于 servlet 映射是必需的

由现有 servlet 处理的 Application 子类

(无)

(已定义)

不需要

未由现有 servlet 处理的 Application 子类

添加 servlet

子类名称

如果无 @ApplicationPath 则对于 servlet 映射是必需的

如果未使用 Servlet 3 框架可插拔机制(例如,在 Servlet 3.0 之前的容器中),则 web.xml 描述符的 servlet-classfilter-class 元素应该命名 JAX-RS 实现提供的 servlet 或 filter 类。Application 子类应使用名为 javax.ws.rs.Applicationinit-param 来标识。

请注意,上述 Servlet 3 框架可插拔机制基于 servlet 而非 filter。偏好使用实现提供的 filter 类的应用程序必须使用 Servlet 3.0 之前的配置机制。

2.3.3. 其他容器

实现 MAY 提供托管 JAX-RS 应用程序到其他类型容器中的功能,此类功能不在本规范范围内。

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 属性的值将根据注解的语义进行设置:

@MatrixParam

提取 URI 矩阵参数的值。

@QueryParam

提取 URI 查询参数的值。

@PathParam

提取 URI 模板参数的值。

@CookieParam

提取 Cookie 的值。

@HeaderParam

提取 Header 的值。

@Context

注入受支持资源的一个实例,有关更多详细信息,请参阅 上下文环境 章节。

由于注入发生在对象创建时,因此这些注解(@Context 除外)在资源类字段和 Bean 属性上的使用仅支持默认的每个请求资源类生命周期。如果资源类的生命周期不同,实现应警告在资源类字段或 Bean 属性上使用这些注解。

JAX-RS 实现仅需要设置其运行时创建的实例的注解字段和 Bean 属性值。由子资源定位符(参见 子资源)返回的对象期望由其创建者初始化。

每个上述注解的有效参数类型列在相应的 Javadoc 中,但通常(不包括 @Context)支持以下类型:

  1. 存在 ParamConverterProvider 可用的 ParamConverter 的类型。有关更多信息,请参阅这些类的 Javadoc。

  2. 原始类型。

  3. 具有接受单个 String 参数的构造函数的类型。

  4. 具有名为 valueOffromString 的静态方法的类型,该方法接受单个 String 参数并返回该类型的实例。如果两个方法都存在,则使用 valueOf,除非该类型是枚举,在这种情况下使用 fromString[1]

  5. 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.1. 可见性

只有 public 方法才能暴露为资源方法。如果非 public 方法带有方法设计符或 @Path 注解,实现应向用户发出警告。

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 返回 voidResponseGenericEntity 或其他 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. 确定返回值的原始类型和泛型类型

返回类型

返回实例 使用[2]

原始类型

泛型类型

GenericEntity

GenericEntity 或子类

RawType 属性

Type 属性

Response

GenericEntity 或子类

RawType 属性

Type 属性

Response

Object 或子类

实例的类

实例的类

Other

返回类型或子类

实例的类

返回类型的泛型类型

为了说明以上内容,请考虑一个始终返回 ArrayList<String> 实例的方法,无论是直接返回还是包装在 Response 和 GenericEntity 的某种组合中。结果的原始类型和泛型类型如下所示。

表 3. 返回值的示例原始类型和泛型类型

返回类型

返回实例

原始类型

*泛型类型

GenericEntity

GenericEntity<List<String>>

ArrayList<?>

List<String>

Response

GenericEntity<List<String>>

ArrayList<?>

List<String>

Response

ArrayList<String>

ArrayList<?>

ArrayList<?>

List<String>

ArrayList<String>

ArrayList<?>

List<String>

3.3.4. 异常

资源方法、子资源方法或子资源定位符可以抛出任何已检查或未检查的异常。实现必须捕获所有异常并按以下顺序处理:

  1. WebApplicationException 及其子类的实例必须按如下方式映射到响应。如果异常的 response 属性不包含实体,并且存在适用于 WebApplicationException 或相应子类的异常映射提供者(参见 异常映射提供者),则实现必须使用该提供者创建一个新的 Response 实例,否则直接使用 response 属性。生成的 Response 实例随后将按照 返回类型 进行处理。

  2. 如果存在适用于异常或其超类的异常映射提供者(参见 异常映射提供者),则实现必须使用其泛型类型为异常的最近超类的提供者来创建 Response 实例,然后将其按照 返回类型 进行处理。如果在创建 Response 期间异常映射提供者抛出异常,则向客户端返回服务器错误(状态码 500)响应。

  3. 未映射的未检查异常和错误必须被重新抛出,并允许它们传播到底层容器。

  4. 未映射且不能直接抛出的已检查异常和可抛出对象必须包装在容器特定的异常中,然后抛出并允许其传播到底层容器。基于 Servlet 的实现必须使用 ServletException 作为包装器。基于 JAX-WS Provider 的实现必须使用 WebServiceException 作为包装器。

注意: 第 3 点和第 4 点允许使用现有的容器设施(例如 Servlet filter 或错误页面)来处理错误(如果需要)。

3.3.5. HEAD 和 OPTIONS

HEADOPTIONS 请求获得额外的自动支持。在收到 HEAD 请求时,实现必须执行以下操作之一:

  1. 调用一个被注解为 HEAD 请求方法设计符的方法,或者,如果不存在,则

  2. 调用一个被注解为 GET 请求方法设计符的方法,并丢弃任何返回的实体。

请注意,选项 2 在实体创建很重要的情况下可能导致性能下降。

在收到 OPTIONS 请求时,实现必须执行以下操作之一:

  1. 调用一个被注解为 OPTIONS 请求方法设计符的方法,或者,如果不存在,则

  2. 使用 JAX-RS 注解在匹配类及其方法上提供的元数据生成自动响应。

3.4. URI 模板

根资源类通过 @Path 注解锚定在 URI 空间中。注解的值是一个相对 URI 路径模板,其基础 URI 由部署上下文和应用程序路径的组合提供(请参阅 @ApplicationPath 注解)。

URI 路径模板是一个带有零个或多个嵌入式参数的字符串,当所有参数都替换为值时,它是一个有效的 URI[5] 路径。@Path 注解的 Javadoc 描述了它们的语法。例如:

@Path("widgets/{id}")
public class Widget {
    ...
}

在上面的示例中,Widget 资源类由相对 URI 路径 widgets/xxx 标识,其中 xxxid 参数的值。

注意: 由于 \{ 和 } 不是 URI[5] 的保留或未保留部分的组成部分,因此它们不会出现在有效的 URI 中。

注解的值会自动编码,例如,以下两行是等效的:

@Path("widget list/{id}")
@Path("widget%20list/{id}")

模板参数可以可选地指定用于匹配其值的正则表达式。默认值匹配任何文本并终止于路径段的末尾,但其他值可以用于改变此行为,例如:

@Path("widgets/{path:.+}")
public class Widget {
    ...
}

在上面的示例中,Widget 资源类将匹配任何以 widgets 开头且至少包含一个额外路径段的请求;path 参数的值将是 widgets 之后的请求路径。例如,给定请求路径 widgets/small/apath 的值将是 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/htmlString,将使用 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/htmlGET 请求。根据此请求,服务器确定 application/xmlapplication/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.1. 请求预处理

在匹配之前,请求 URI 会根据 RFC 3986[5] 第 6.2.2 节中描述的大小写、路径段和百分比编码规范化规则进行规范化[4]。规范化后的请求 URI 必须反映在从注入的 UriInfo 获取的 URI 中。

3.7.2. 请求匹配

通过将规范化后的请求 URI(参见 请求预处理)、任何请求实体的媒体类型以及请求的响应实体格式与资源类及其方法的元数据注解进行比较,将请求匹配到相应的资源方法或子资源方法。如果找不到匹配的资源方法或子资源方法,则会返回适当的错误响应。此算法报告的所有异常都必须按照 异常 中的描述进行处理。

请求到资源方法的匹配过程分为三个阶段:

  1. 识别一组匹配请求的候选根资源类

    输入

    \(U=\mbox{请求 URI 路径},C=\{\mbox{根资源类}\}\)

    输出

    \(U=\mbox{最终捕获组尚未匹配}, C'=\{\mbox{到目前为止已匹配的根资源类}\}\)

    1. 设置 \(E=\{\}\)。

    2. 对于 \(C\) 中的每个类 \(Z\),添加一个正则表达式(使用 将 URI 模板转换为正则表达式 中描述的 \(R(A)\) 函数计算)到 \(E\),如下所示:

      • 添加 \(R(T_Z)\),其中 \(T_Z\) 是为类 \(Z\) 指定的 URI 路径模板。

        请注意,\(C\) 中的两个或多个类可能添加相同的正则表达式到 \(E\),如果它们被注解为相同的 URI 路径模板(忽略变量名)。

    3. 通过匹配 \(E\) 中的每个成员与 \(U\) 来过滤 \(E\),如下所示:

      • 删除不匹配 \(U\) 的成员。

      • 删除其最后一个正则表达式捕获组(以下简称捕获组)的值既不是空也不是 / ,且类 \(Z\) 没有子资源方法或定位符的成员。

    4. 如果 \(E\) 为空,则找不到匹配的资源,算法终止,实现必须生成 NotFoundException(404 状态)且无实体。

    5. 使用每个成员中的字面字符数[5](降序)作为主键,捕获组的数量(降序)作为次键,以及具有非默认正则表达式(即非 ([ /]+?))的捕获组的数量(降序)作为第三键,对 \(E\) 进行排序。

    6. 将 \(R_{\mbox{match}}\) 设置为 \(E\) 的第一个成员,并将 \(U\) 设置为 \(U\) 匹配 \(R_{\mbox{match}}\) 时的最后一个捕获组的值。设 \(C'\) 为类 \(Z\) 的集合,使得 \(R(T_Z)=R_{\mbox{match}}\)。根据定义,\(C'\) 中的所有根资源类都必须被注解为具有相同的 URI 路径模板(忽略变量名)。

  2. 获取请求的候选资源方法集

    输入

    \(U=\mbox{最终捕获组尚未匹配}, C'=\{\mbox{到目前为止已匹配的根资源类}\}\)

    输出

    \(M=\{\mbox{候选资源方法}\)}

    1. [check_null] 如果 \(U\) 为 null 或 /,则设置 \(\[M = \{\mbox{所有类 $C'$ 的资源方法(不包括子资源方法)}\}]\) 并且如果 \(M \neq \{\}\) 则转到步骤 [find_method]

    2. 设置 \(E=\{\}\)。

    3. 对于 \(C'\) 中的每个类 \(Z'\),如下为每个子资源方法和定位符添加正则表达式到 \(E\):

      1. 对于每个子资源方法 \(D\),添加 \(R(T_D)\),其中 \(T_D\) 是子资源方法的 URI 路径模板。

      2. 对于每个子资源定位符 \(L\),添加 \(R(T_L)\),其中 \(T_L\) 是子资源定位符的 URI 路径模板。

    4. 通过匹配 \(E\) 中的每个成员与 \(U\) 来过滤 \(E\),如下所示:

      • 删除不匹配 \(U\) 的成员。

      • 删除来自 \(T_D\)(在步骤 [t_method_items] 中添加)且最后一个捕获组值既不是空也不是 / 的成员。

    5. 如果 \(E\) 为空,则找不到匹配的资源,算法终止,并通过生成 NotFoundException(404 状态)且无实体。

    6. 使用每个成员中的字面字符数(降序)作为主键,捕获组的数量(降序)作为次键,具有非默认正则表达式(即非 ([ /]+?))的捕获组的数量(降序)作为第三键,以及每个成员的来源作为第四键(将来自子资源方法的成员排在来自子资源定位符的成员之前)对 \(E\) 进行排序。

    7. 将 \(R_{\mbox{match}}\) 设置为 \(E\) 的第一个成员。

    8. 设置 \(M\) 如下:\(M = \{\mbox{所有类 $C'$ 的子资源方法(不包括子资源定位符)}\}]\) 并且如果 \(M \neq \{\}\) 则转到步骤 3。

    9. 令 \(L\) 为一个子资源定位符,使得 \(R_{\mbox{match}} = R(T_L)\)。实现应报告错误,如果存在一个以上的子资源定位符满足此条件。将 \(U\) 设置为 \(U\) 匹配 \(R(T_L)\) 时的最后一个捕获组的值,并将 \(C'\) 设置为仅包含定义 \(L\) 的类的单例集。

    10. 转到步骤 2a。

  3. 确定将处理请求的方法

    输入

    \(M=\mbox{候选资源方法}\)

    输出

    \(O=\mbox{匹配的资源类实例}, D=\mbox{从 $M$ 匹配的资源方法}\)

    1. 通过删除不满足以下标准的成员来过滤 \(M\)

      • 请求方法是受支持的。如果没有方法支持请求方法,实现必须生成 NotAllowedException(405 状态)且无实体。请注意对 HEADOPTIONS 的额外支持,请参阅 HEAD 和 OPTIONS

      • 请求实体体(如果存在)的媒体类型是支持的输入数据格式(参见 声明媒体类型能力)。如果没有方法支持请求实体体的媒体类型,实现必须生成 NotSupportedException(415 状态)且无实体。

      • 至少一个可接受的响应实体体媒体类型是支持的输出数据格式(参见 声明媒体类型能力)。如果没有方法支持可接受的响应实体体媒体类型之一,实现必须生成 NotAcceptableException(406 状态)且无实体。

    2. 如果在过滤后集合 \(M\) 包含多于一个元素,则按如下方式降序排序。首先,让我们将客户端媒体类型定义为请求 Accept Header 所表示的,将服务器媒体类型定义为资源方法上的 @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''$}\) 如果以下任一有序条件成立:

      1. \(\mbox{$n_1$/$m_1$} \succ \mbox{$n_2$/$m_2$}\),其中偏序 \(\succ\) 定义为 \(\mbox{$n$/$m$} \succ \mbox{$n$/*} \succ \mbox{*/*}\),

      2. \(\mbox{$n_2$/$m_2$} \nsucc \mbox{$n_1$/$m_1$}\) 且 \(v_1 > v_2\),

      3. \(\mbox{$n_2$/$m_2$} \nsucc \mbox{$n_1$/$m_1$}\) 且 \(v_1 = v_2\) 且 \(v_1' > v_2'\)。

      4. \(\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\}\) 作为次键。如果存在多个最大元素,实现应报告警告并以实现特定的方式选择其中一种类型。

    3. 令 \(D\) 为集合 \(M\) 中的第一个资源方法[8] 确保集合至少包含一个成员。令 \(O\) 为定义 \(D\) 的类的一个实例。如果在排序后,\(M\) 中存在多个最大元素,实现应报告警告并以实现特定的方式选择其中一种方法。

      考虑以下示例,并假设请求为 GET widgets/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),因此,对于请求 GET widgets/1,WidgetResource 上的 @Path 注解将被忽略。

3.7.3. 将 URI 模板转换为正则表达式

函数 \(R(A)\) 将 URI 路径模板注解 \(A\) 转换为正则表达式,如下所示:

  1. URI 编码模板,忽略 URI 模板变量规范。

  2. 转义 URI 模板中的任何正则表达式字符,同样忽略 URI 模板变量规范。

  3. 将每个 URI 模板替换为包含指定正则表达式的捕获组,如果未指定正则表达式,则为 ([ /]+?)[9]

  4. 如果结果字符串以 / 结尾,则删除最后一个字符。

  5. 将 (/.*)? 附加到结果。

请注意,以上将模板变量的名称对于模板匹配而言变得无关紧要。但是,实现需要保留模板变量名,以便通过 @PathParam 或 UriInfo.getPathParameters 提取模板变量值。

3.8. 确定响应的 MediaType

在许多情况下,无法静态确定响应的媒体类型。以下算法用于在运行时确定响应媒体类型 \(M_{\mbox{selected}}\):

  1. 如果方法返回一个 Response 实例,其元数据包含响应媒体类型(\(M_{\mbox{specified}}\)),则设置 \(M_{\mbox{selected}} = M_{\mbox{specified}}\),完成。

  2. 收集可生成媒体类型集 \(P\)

    • 如果方法被注解为 @Produces,则设置 \(P = \{ V(\mbox{method}) \}\),其中 \(V(t)\) 表示指定目标 \(t\) 上的 @Produces 值。

    • 否则,如果类被注解为 @Produces,则设置 \(P = \{ V(\mbox{class}) \}\)。

    • 否则,设置 \(P = \{ V(\mbox{writers}) \}\),其中 writers 是支持返回实体对象类的 MessageBodyWriter 集。

  3. 如果 \(P = \{\}\),则设置 \(P = \{\mbox{\lq*/*\rq}\}

  4. 获取可接受媒体类型 \(A\)。如果 \(A = \{\}\),则设置 \(A = \{\mbox{\lq*/*\rq}\}

  5. 设置 \(M=\{\}\)。对于 \(A\) 中的每个成员 \(a\)

    • 对于 \(P\) 中的每个成员 \(p\)

      • 如果 \(a\) 与 \(p\) 兼容,则将 \(S(a,p)\) 添加到 \(M\),其中函数 \(S\) 返回该对中最具体的媒体类型,具有 \(a\) 的 q-value 和 \(p\) 的服务器端 qs-value。

  6. 如果 \(M = \{\}\),则生成 NotAcceptableException(406 状态)且无实体。异常必须按照 异常 中的描述进行处理。完成。

  7. 将 \(M\) 按降序排序,主键为特异性(\(\mbox{n/m} > \mbox{n/*} > \mbox{*/*}\)),次键为 q-value,第三键为 qs-value。

  8. 对于 \(M\) 中的每个成员 \(m\)

    • 如果 \(m\) 是具体类型,则设置 \(M_{\mbox{selected}} = m\),完成。

  9. 如果 \(M\) 包含 / 或 application/,则设置 \(M_{\mbox{selected}} = \mbox{\lq application/octet-stream\rq}\),完成。

  10. 生成 NotAcceptableException(406 状态)且无实体。异常必须按照 异常 中的描述进行处理。完成。

请注意,上述设置在无法确定具体类型时,响应的默认媒体类型为 application/octet-stream 。建议 MessageBodyWriter 实现通过 @Produces 指定至少一种具体类型。

4. 提供者

JAX-RS 中的提供者负责各种横切关注点,如过滤请求、将表示转换为 Java 对象、将异常映射到响应等。提供者可以是 JAX-RS 运行时中预打包的,也可以由应用程序提供。所有应用程序提供的提供者都实现了 JAX-RS API 中的接口,并且 MAY 被注解为 @Provider 以进行自动发现;预打包提供者与 JAX-RS 运行时的集成取决于实现。

本章介绍了一些基本的 JAX-RS 提供者;其他提供者将在 客户端 APIFilter 和拦截器 章节中介绍。

4.1. 生命周期和环境

默认情况下,每个提供者类的一个实例为每个 JAX-RS 应用程序实例化。首先调用构造函数(参见 构造函数),然后注入任何请求的依赖项(参见 上下文 章节),然后可以多次(同时)调用适当的提供者方法,最后对象可供垃圾回收。提供者 描述了提供者如何通过依赖注入访问其他提供者。

实现 MAY 提供其他提供者生命周期,指定这些的机制不在本规范范围内。例如,基于控制反转框架的实现可以支持该框架提供的所有生命周期选项。

4.1.1. 自动发现

@Provider 注解用于 JAX-RS 运行时通过类扫描等机制自动发现提供者类。支持类自动发现的 JAX-RS 实现必须只处理被注解为 @Provider 的类。

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] 步骤:

  1. 获取请求的媒体类型。如果请求不包含 Content-Type Header,则使用 application/octet-stream

  2. 确定参数的 Java 类型,其值将从实体体映射。 将请求匹配到 Java 方法 描述了如何选择 Java 方法。

  3. 选择支持请求媒体类型的 MessageBodyReader 类集,请参阅 声明媒体类型能力

  4. 遍历选定的 MessageBodyReader 类,并利用每个类的 isReadable 方法,选择支持所需 Java 类型的 MessageBodyReader 提供者。

  5. 如果步骤 4 找到一个或多个合适的 MessageBodyReader,则根据 优先级 部分选择优先级最高的那个,并使用其 readFrom 方法将实体正文映射到所需的 Java 类型。

  6. 否则,服务器运行时必须生成一个 NotSupportedException(415 状态)且没有实体(按 异常 部分处理),客户端运行时必须生成一个 ProcessingException 实例。

有关处理 MessageBodyReader.readFrom 中抛出的异常的信息,请参阅 异常

4.2.2. Message Body Writer

MessageBodyWriter 接口定义了 JAX-RS 运行时与提供从 Java 类型到表示形式的映射服务的组件之间的契约。希望提供此类服务的类实现 MessageBodyWriter 接口,并可能使用 @Provider 注解以实现自动发现。

以下描述了 JAX-RS 实现将返回值映射到消息实体正文时所采取的逻辑步骤

  1. 获取将被映射到消息实体正文的对象。对于返回类型为 Response 或其子类的情况,该对象是 entity 属性的值;对于其他返回类型,则是返回的对象。

  2. 确定响应的媒体类型,请参阅 [determine_response_type] 部分。

  3. 选择一组 MessageBodyWriter 提供程序,这些提供程序支持(参见 声明媒体类型能力)对象和消息实体正文的媒体类型。

  4. 使用通用类型作为主键对选定的 MessageBodyWriter 提供程序进行排序,其中通用类型是对象类的最近超类的提供程序首先排序,然后使用媒体类型作为次键(参见 [declaring_provider_capabilities] 部分)。

  5. 遍历已排序的 MessageBodyWriter 提供程序,并利用每个提供程序的 isWriteable 方法,选择一个支持将被映射到实体正文的对象 的 MessageBodyWriter

  6. 如果步骤 5 找到一个或多个符合条件的 MessageBodyWriter 并且它们在步骤 4 的排序中是相等的,则根据 优先级 部分选择优先级最高的那个,并使用其 writeTo 方法将实体正文映射到所需的 Java 类型。

  7. 否则,服务器运行时必须生成一个 InternalServerErrorException,它是 WebApplicationException 的一个子类,其状态设置为 500,且没有实体(按 异常 部分处理),客户端运行时必须生成一个 ProcessingException

通过实践获得的经验表明,此规范中的步骤 4 的排序键已反转。这代表了与 JAX-RS 1.X 相比的向后不兼容的更改。此规范的实现被要求提供一个向后兼容的标志,以供依赖于先前排序的应用程序使用。用于启用此标志的机制是实现依赖的。

有关处理 MessageBodyWriter.write 中抛出的异常的信息,请参阅 异常

4.2.3. 声明媒体类型能力

消息正文读取器和写入器可以使用 @Consumes@Produces 注解分别限制其支持的媒体类型。缺少这些注解等同于包含媒体类型 (/*),即缺少意味着支持任何媒体类型。实现不得将实体提供程序用于该提供程序不支持的媒体类型。

选择实体提供程序时,实现会根据它们声明支持的媒体类型对可用提供程序进行排序。媒体类型的排序遵循一般规则:x/y < x/* < /,即显式列出媒体类型的提供程序排在列出 / 的提供程序之前。

4.2.4. 标准实体提供程序

实现必须包含以下 Java 和媒体类型组合的预打包 MessageBodyReaderMessageBodyWriter 实现

byte[]

所有媒体类型 (/*)。

java.lang.String

所有媒体类型 (/*)。

java.io.InputStream

所有媒体类型 (/*)。

java.io.Reader

所有媒体类型 (/*)。

java.io.File

所有媒体类型 (/*)。

javax.activation.DataSource

所有媒体类型 (/*)。

javax.xml.transform.Source

XML 类型(text/xmlapplication/xml 以及形式为 application/*+xml 的媒体类型)。

javax.xml.bind.JAXBElement 和应用程序提供的 JAXB 类

XML 类型(text/xmlapplication/xml 以及形式为 application/*+xml 的媒体类型)。

MultivaluedMap<String,String>

表单内容(application/x-www-form-urlencoded)。

StreamingOutput

所有媒体类型 (/*),仅 MessageBodyWriter

java.lang.Booleanjava.lang.Characterjava.lang.Number

仅用于 text/plain。通过装箱/拆箱转换支持相应的原始类型。

根据环境,标准实体提供程序列表还必须包括 JSON 的提供程序。有关这些提供程序的更多信息,请参阅 Java API for JSON ProcessingJava API for JSON Binding

在读取零长度消息实体时,所有预打包的 MessageBodyReader 实现,除了 JAXB 实现和上述(装箱的)原始类型的实现外,都必须创建一个代表零长度数据的相应 Java 对象。预打包的 JAXB 和预打包的原始类型 MessageBodyReader 实现必须为零长度消息实体抛出 NoContentException

当从 MessageBodyReader 读取服务器请求实体时抛出 NoContentException,服务器运行时必须将其转换为 BadRequestException,包装原始 NoContentException 并重新抛出以供任何注册的异常映射器处理。

javax.xml.bind.JAXBElement 和应用程序提供的 JAXB 类的实现提供的实体提供程序必须使用应用程序提供的上下文解析器提供的 JAXBContext 实例(参见 上下文提供程序)。如果应用程序未为特定类型提供 JAXBContext,则实现提供的实体提供程序必须使用自己的默认上下文。

在写入响应时,实现应尊重应用程序提供的字符集元数据,如果未指定字符集或应用程序指定的字符集不受支持,则应使用 UTF-8。

实现必须支持应用程序提供的实体提供程序,并在任何一个提供程序可以处理同一请求时,优先使用它们而不是其自己的预打包提供程序。更准确地说,消息正文读取器 中的步骤 4 和 消息正文写入器 中的步骤 5 必须优先选择应用程序提供的实体提供程序而不是预打包的实体提供程序。

4.2.5. 传输编码

入站数据的传输编码由容器或 JAX-RS 运行时的组件处理。MessageBodyReader 提供程序始终在已解码的 HTTP 实体正文上操作,而不是直接在 HTTP 消息正文上操作。

JAX-RS 运行时或容器可以传输编码出站数据,也可以由应用程序代码完成。

4.2.6. 内容编码

内容编码是应用程序的责任。应用程序提供的实体提供程序可以执行此类编码并相应地操作 HTTP 头。

4.3. 上下文提供程序

上下文提供程序为资源类和其他提供程序提供上下文。上下文提供程序类实现 ContextResolver<T> 接口,并可能使用 @Provider 注解以实现自动发现。例如,希望为默认 JAXB 实体提供程序提供自定义 JAXBContext 的应用程序将提供一个实现 ContextResolver<JAXBContext> 的类。

上下文提供程序可以从 getContext 方法返回 null,如果它们不希望为特定 Java 类型提供其上下文。例如,JAXB 上下文提供程序可能只想为某些 JAXB 类提供上下文。上下文提供程序还可以管理同一类型的多个上下文,这些上下文以不同的 Java 类型为键。

4.3.1. 声明媒体类型能力

上下文提供程序实现可以使用 @Produces 注解限制它们支持的媒体类型。缺少此注解等同于包含媒体类型 (/*),即缺少意味着支持任何媒体类型。

选择上下文提供程序时,实现会根据它们声明支持的媒体类型对可用提供程序进行排序。媒体类型的排序遵循一般规则:x/y < x/* < /,即显式列出媒体类型的提供程序排在列出 / 的提供程序之前。

4.4. 异常映射提供程序

异常映射提供程序将已检查异常或运行时异常映射到 Response 实例。异常映射提供程序实现 ExceptionMapper<T> 接口,并可能使用 @Provider 注解以实现自动发现。

当资源类或提供程序方法抛出异常,并且存在该异常的异常映射提供程序时,将使用匹配的提供程序来获取 Response 实例。生成的 Response 将像 Web 资源方法返回 Response 一样进行处理(参见 返回类型)。特别是,映射的 Response 必须使用 Filters and Interceptors 章节中定义的 ContainerResponse 过滤器链进行处理。

选择异常映射提供程序来映射异常时,实现必须使用其通用类型是异常的最近超类的提供程序。如果两个或多个异常提供程序适用,则必须按照 优先级 部分所述选择优先级最高的那个。

为避免潜在的无限循环,在处理请求及其相应响应期间必须使用单个异常映射器。JAX-RS 实现不得尝试映射在处理先前从异常映射的响应时抛出的异常。相反,此异常必须按照 异常 部分的步骤 3 和 4 的描述进行处理。

请注意,异常映射提供程序 **不** 支持作为客户端 API 的一部分。

4.5. 异常

异常处理根据提供程序是客户端运行时还是服务器运行时的组成部分而有所不同。这将在接下来的两个部分中介绍。

4.5.1. 服务器运行时

当提供程序方法抛出异常时,JAX-RS 服务器运行时将尝试将异常映射到合适的 HTTP 响应,方式与 异常 部分中为方法和定位器描述的相同。如果在生成响应时抛出异常,JAX-RS 实现仅在响应尚未提交时才需要映射异常。

异常映射提供程序 所述,应用程序可以提供异常映射提供程序来定制此映射,但在处理 **已映射** 的响应时将忽略这些异常映射器,以避免进入潜在的无限循环。例如,假设消息正文读取器中的方法抛出异常,该异常通过异常映射提供程序映射到响应;如果在尝试写入映射响应时消息正文写入器抛出异常,JAX-RS 实现将不会再次尝试映射该异常。

4.5.2. 客户端运行时

当提供程序方法抛出异常时,JAX-RS 客户端运行时会在处理请求时抛出异常,将其映射到 ProcessingException 实例;在处理响应时抛出异常,则映射到 ResponseProcessingException

请注意,客户端运行时只会将 WebApplicationException(或其任何子类)的实例作为来自服务器的响应(状态码为 3xx、4xx 或 5xx)的结果抛出。

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 类型是可配置的:ClientClientBuilderWebTarget。配置方法是从所有这些类实现的 Configurable 接口继承的。此接口支持配置

属性

名称-值对,用于其他功能或 JAX-RS 实现的其他组件的附加配置。

功能

一种特殊类型的提供程序,实现 Feature 接口,并可用于配置 JAX-RS 实现。

提供程序

实现 Providers 章节中的一个或多个提供程序接口的类或类的实例。提供程序可以是消息正文读取器、过滤器、上下文解析器等。

上述任何类型的实例上定义的配置都将由从它创建的其他实例继承。例如,从 Client 创建的 WebTarget 实例将继承 Client 的配置。但是,对 WebTarget 实例的任何其他更改都不会影响 Client 的配置,反之亦然。因此,一旦继承了配置,它就会从父配置中分离(深拷贝),父配置和子配置的更改对彼此不可见。

5.6.1. 过滤器和实体拦截器

Filters and Interceptors 章节所述,过滤器和拦截器被定义为 JAX-RS 提供程序。因此,它们可以注册到上一节中列出的任何可配置类型。以下示例演示了如何将过滤器和拦截器注册到 ClientWebTarget 的实例

// 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 中为此目的提供了两个方法,即 executorServicescheduledExecutorService

在支持 Jakarta Concurrency [13] 的环境中(例如完整的 Jakarta EE 平台产品),实现必须分别使用 ManagedExecutorServiceManagedScheduledExecutorService。有关 executor 服务的信息,请参阅 ClientBuilder 的 Javadoc。

6. 过滤器和拦截器

过滤器和实体拦截器可以注册在 JAX-RS 实现中的明确定义的扩展点以供执行。它们用于扩展实现,以提供日志记录、保密性、身份验证、实体压缩等功能。

6.1. 引言

实体拦截器包装在特定扩展点的方法调用。过滤器在扩展点执行代码,但不包装方法调用。过滤器有四个扩展点:ClientRequestClientResponseContainerRequestContainerResponse。实体拦截器有两个扩展点:ReadFromWriteTo。对于每个扩展点,都有一个对应的接口

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;
}

**客户端** 过滤器是实现 ClientRequestFilterClientResponseFilter 或两者的类。 **容器** 过滤器是实现 ContainerRequestFilterContainerResponseFilter 或两者的类。实体拦截器是实现 ReaderInterceptorWriterInterceptor 或两者的类。过滤器和实体拦截器是提供程序,因此可以使用 @Provider 注解以实现自动发现。

在 Client API 中,ClientRequestFilter 在 HTTP 请求发送到网络之前作为调用管道的一部分执行;ClientResponseFilter 在收到服务器响应后,在控制权返回到应用程序之前执行。在 Server API 中,ContainerRequestFilter 在收到来自客户端的请求时执行;ContainerResponseFilter 在 HTTP 响应发送到网络之前,作为响应管道的一部分执行。

全局绑定的(参见 全局绑定ContainerRequestFilter 是在资源匹配后执行的容器过滤器,**除非** 它被注解了 @PreMatching。此注解在此类过滤器上的使用定义了一个新的扩展点供应用程序使用,即。某些 ContainerRequestContext 方法在此扩展点可能不可用。

实现 ReaderInterceptor 的实体拦截器包装对 MessageBodyReaderreadFrom 方法的调用。实现 WriterInterceptor 的实体拦截器包装对 MessageBodyWriterwriteTo 方法的调用。JAX-RS 实现被要求在将表示形式映射到 Java 类型及反之亦然时调用已注册的拦截器。有关实体提供程序的信息,请参阅 Entity Providers

有关客户端和服务器处理管道中过滤器和实体拦截器交互的图示,请参阅附录 Processing Pipeline

6.2. 过滤器

过滤器被分组到 **过滤器链** 中。对于上一节中介绍的每个扩展点,都有一个单独的过滤器链,即:ClientRequestClientResponseContainerRequestContainerResponsePreMatchContainerRequest。链中的过滤器根据其优先级(参见 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,它提供响应特定信息。

实现 ClientRequestFilterContainerRequestFilter 的请求过滤器可以通过在其相应的上下文对象中调用 abortWith(Response) 来停止其相应链的执行。如果调用此方法,JAX-RS 实现被要求中止链的执行,并将响应对象视为通过调用资源方法(Server API)或执行 HTTP 调用(Client API)生成的。例如,在缓存命中时,客户端 **缓存** 过滤器可能会调用 abortWith(Response) 来中止执行并优化网络访问。

如上所述,注解了 @PreMatchingContainerRequestFilter 在接收到客户端请求时执行,但在匹配资源方法 **之前** 执行。因此,此类型的过滤器能够修改匹配算法的输入(参见 Request Matching),并因此改变其结果。以下示例使用了一个注解了 @PreMatchingContainerRequestFilter,通过使用 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. 实体拦截器

实体拦截器实现 ReaderInterceptorWriterInterceptor 接口,或两者都实现。对于每种类型的实体拦截器,都存在一个 **拦截器链**。链中的实体拦截器根据其优先级(参见 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);
        }
    }
    ...
}

上下文类型 ReaderInterceptorContextWriterInterceptorContext 提供对相应被包装方法的参数的读写访问。在上面显示的示例中,在继续之前,输入和输出流被包装并更新在上下文对象中。JAX-RS 实现必须在调用被包装的 MessageBodyReader.readFromMessageBodyWriter.writeTo 方法时使用上下文中设置的最后参数值。

值得注意的是,直接从应用程序代码调用(例如,通过注入 Providers 实例)的 readFromwriteTo **不会** 触发任何实体拦截器的执行,因为它不是正常 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. 全局绑定

全局绑定是默认的绑定类型。没有注解的过滤器或拦截器假定为全局绑定,即它适用于应用程序中的所有资源方法。与其他提供程序一样,过滤器或拦截器可以手动注册(例如,通过 ApplicationConfiguration)或自动发现。请注意,为了自动发现过滤器或拦截器,它 **必须** 被注解为 @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 {
    ...
}

请注意,从应用程序子类中的 getClassesgetSingletons 方法返回过滤器或拦截器,只有在它们 **未** 被名称绑定注解修饰时,才会将它们全局绑定。如果它们被注解了至少一个名称绑定注解,则应用程序子类必须如上注解,以便这些过滤器或拦截器被全局绑定。有关 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 调用而不是注解来完成的。ClientInvocationInvocation.BuilderWebTarget 都是可配置类型:可以使用从 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 所述,应用程序可以提供异常映射提供程序。为避免潜在的无限循环,在单个请求处理周期中最多必须使用一个异常映射器。

从异常映射的响应必须使用 和 过滤器链以及 (如果映射响应中存在实体)拦截器链进行处理。这些链中的条目数取决于抛出异常时是否已匹配资源方法。有两种情况

  1. 如果在抛出异常之前已匹配 Web 资源,则 和 中的过滤器将包括已绑定到该方法以及全局绑定的所有内容;

  2. 否则,仅包括全局过滤器和拦截器。

请注意,在情况 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 注解对表单参数 firstNamelastNameemail 施加了附加约束。@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;
    }
    ...
}

请注意,在此版本中,firstNamelastName 是通过注入初始化的字段,而 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;
    }
    ...
}

@ValidateOnExecutiontype 属性的默认值是 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),遵循以下规则

  1. 如果异常类型为 javax.validation.ValidationException 或其任何子类(**不包括** javax.validation.ConstraintViolationException),则将其映射到状态码为 500(内部服务器错误)的响应。

  2. 如果异常是 javax.validation.ConstraintViolationException 的实例,则

    1. 如果异常是在验证方法返回类型时抛出的,则将其映射到状态码为 500(内部服务器错误)的响应 [13]

    2. 否则,将其映射到状态码为 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!");
                }
            });
    }
    ...
}

选择异步生成响应的资源方法必须使用特殊注解 @SuspendedAsyncResponse 类的实例作为方法参数注入。在上面的示例中,在收到 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 实现被要求生成一个 ServiceUnavailableExceptionWebApplicationException 的一个子类,状态码为 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 连接。

事件是结构化的,包含多个字段,即 eventdataidretrycomment。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。在打开源之前,客户端注册了一个事件消费者,该消费者仅打印每个事件。其他生命周期事件(如onCompleteonError)的处理器也得到支持,但为了简单起见,本示例仅显示了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方法用于添加新的SseEventSinkbroadcast方法用于将SSE事件发送给所有已注册的消费者。

9.5. 处理管道

来自SSE客户端的连接由可注入的SseEventSink实例表示。SSE与异步处理(参见异步处理章节)之间存在一些相似之处。异步响应最多可以恢复一次,而SseEventSink可以多次用于流式传输单个事件。

为了兼容性,实现必须在发送第一个消息或资源方法返回时启动SSE响应的处理,以两者中较早的为准。初始SSE响应(可能只包含HTTP头)使用标准的JAX-RS 管道进行处理,如附录处理管道中所述。每个后续的SSE事件可能包含不同的有效载荷,因此需要使用特定的消息体写入器。请注意,由于此用例与正常的JAX-RS 管道略有不同,实现不应在每个单独的事件上调用实体拦截器。[15]

9.6. 环境

SseEventSource类使用基于RuntimeDelegate的现有JAX-RS 机制,通过服务名称javax.ws.rs.sse.SseEventSource.Builder查找实现。javax.ws.rs.sse中的大多数类型都是线程安全的;读者可以参考Javadoc以获取有关线程安全的更多信息。

10. 上下文

JAX-RS 提供了用于获取和处理应用程序部署上下文以及单个请求上下文的信息的设施。这些信息可供Application子类(参见配置章节)、根资源类(参见资源章节)和提供者(参见提供者章节)使用。本章将介绍这些设施。

10.1. 并发

上下文特定于特定请求,但是某些JAX-RS 组件的实例(生命周期非按请求的提供者和资源类)可能需要支持多个并发请求。在注入上下文类型列表中类型的实例时,提供的实例必须能够为特定请求选择正确的上下文。使用线程局部代理是实现此目的的常用方法。

10.2. 上下文类型

本节介绍可用于提供者(客户端和服务器)以及资源类和Application子类(仅服务器端)的上下文类型。除了ConfigurationProviders(可在客户端和服务器端提供者中注入)之外,所有其他类型都是仅服务器端。

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() {...}
}

请注意,资源定位器findWidgetWidgetsResource中返回的实例在返回之前使用注入的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, HttpServletRequestHttpServletResponse

注入的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资源类或提供者方法抛出的异常必须被解包并按异常中所述进行处理。

有关异步EJB方法的信息,请参阅EJB资源类,有关EJBs的其他要求,请参阅附加要求

11.2.5. Bean Validation

在支持Bean Validation规范[16]的产品中,实现必须支持使用约束注解的资源验证,如验证章节中所述。否则,对资源验证的支持是可选的。

11.2.6. Java API for JSON Processing

在支持Java API for JSON Processing (JSON-P) [18]的产品中,实现必须支持JsonValue及其所有子类型:JsonStructure, JsonObject, JsonArray, JsonStringJsonNumber的实体提供者。

请注意,JSON-P API中的其他类型,如JsonParser, JsonGenerator, JsonReaderJsonWriter,也可以通过InputStreamStreamingOutput的实体提供者集成到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.2.8. 附加要求

当使用Managed Beans、CDI风格Beans或EJBs作为资源类、提供者或Application子类时,适用以下附加要求

  • JAX-RS 资源的字段和属性注入必须在容器调用任何@PostConstruct注解的方法之前执行。

  • 对构造函数注入JAX-RS 资源的支持是可选的。可移植的应用程序必须改用字段或Bean属性,并配合@PostConstruct注解的方法。实现应警告用户有关使用非可移植构造函数注入的问题。

  • 实现不得要求使用@Inject@Resource来触发JAX-RS 注解字段或属性的注入。实现可以支持此类用法,但应警告用户有关非可移植性的问题。

11.3. 其他

其他容器技术可以指定其自己的可注入资源集,但至少必须支持访问上下文类型中列出的类型。

12. Runtime Delegate

RuntimeDelegate 是一个抽象工厂类,它提供各种方法来创建实现JAX-RS API的对象。这些方法设计用于其他JAX-RS API类使用,不打算由应用程序直接调用。RuntimeDelegate允许标准的JAX-RS API类在不更改代码的情况下使用不同的JAX-RS 实现。

JAX-RS 的实现必须提供RuntimeDelegate的具体子类。使用提供的RuntimeDelegate,可以通过以下两种方式之一将其提供给JAX-RS

  1. 可以实例化RuntimeDelegate的实例并使用其静态方法setInstance注入。在这种情况下,实现负责创建实例;此选项旨在与基于IoC框架的实现一起使用。

  2. 可以配置要使用的类,请参见配置。在这种情况下,JAX-RS负责实例化该类的实例,并且已配置的类必须有一个不带参数的公共构造函数。

请注意,实现可以提供RuntimeDelegate API类的替代实现(只要它通过TCK签名测试并按照规范运行),以支持定位具体子类的替代方法。

JAX-RS 实现可能会依赖于特定的RuntimeDelegate 实现;应用程序不应使用应用程序提供的替代品覆盖提供的RuntimeDelegate 实例,这样做可能会导致意外的问题。

12.1. 配置

如果未通过注入提供,则提供的RuntimeDelegate API类使用以下算法获取具体实现类。下面列出的步骤按顺序执行,在每个步骤中,最多会产生一个候选实现类名。然后,实现将尝试使用当前上下文类加载器或java.lang.Class.forName(String)方法加载具有给定类名的类。一旦某个步骤成功加载了实现类,算法就会终止。

  1. 使用Java SE类java.util.ServiceLoader尝试从META-INF/services/javax.ws.rs.ext.RuntimeDelegate加载实现。请注意,为了尝试上下文类加载器和当前类加载器(如上所述),这可能需要多次调用ServiceLoader.load(Class, ClassLoader)方法。[16]

  2. 如果存在${java.home}/lib/jaxrs.properties文件,并且该文件可以被java.util.Properties.load(InputStream)方法读取,并且其中包含一个键为javax.ws.rs.ext.RuntimeDelegate的条目,则该条目的值将用作实现类的名称。

  3. 如果定义了名为javax.ws.rs.ext.RuntimeDelegate的系统属性,则其值将用作实现类的名称。

  4. 最后,使用默认的实现类名。

附录 A:注解摘要

注解

目标

描述

消耗

类型或方法

指定可以消耗的媒体类型列表。

产生

类型或方法

指定可以产生的媒体类型列表。

GET

方法

指定所注解方法处理HTTP GET请求。

POST

方法

指定所注解方法处理HTTP POST请求。

PUT

方法

指定所注解方法处理HTTP PUT请求。

DELETE

方法

指定所注解方法处理HTTP DELETE请求。

PATCH

方法

指定所注解方法处理HTTP PATCH请求。

HEAD

方法

指定所注解方法处理HTTP HEAD请求。请注意,HEAD可能会被自动处理,参见HEAD和OPTIONS

OPTIONS

方法

指定所注解方法处理HTTP OPTIONS请求。

ApplicationPath

类型

指定资源范围内的应用程序路径,该路径构成所有根资源类的基URI。

Path

类型或方法

为资源指定一个相对路径。当用于类时,此注解标识该类为根资源。当用于方法时,此注解标识一个子资源方法或定位器。

PathParam

参数、字段或方法

指定方法参数、类字段或Bean属性的值将从请求URI路径中提取。注解的值标识URI模板参数的名称。

QueryParam

参数、字段或方法

指定方法参数、类字段或Bean属性的值将从URI查询参数中提取。注解的值标识查询参数的名称。

FormParam

参数、字段或方法

指定方法参数的值将从请求实体体中的表单参数中提取。注解的值标识表单参数的名称。请注意,虽然注解目标允许用于字段和方法,但规范仅要求支持用于资源方法参数。

MatrixParam

参数、字段或方法

指定方法参数、类字段或Bean属性的值将从URI矩阵参数中提取。注解的值标识矩阵参数的名称。

CookieParam

参数、字段或方法

指定方法参数、类字段或Bean属性的值将从HTTP cookie中提取。注解的值标识cookie的名称。

HeaderParam

参数、字段或方法

指定方法参数、类字段或Bean属性的值将从HTTP头中提取。注解的值标识HTTP头的名称。

Encoded

类型、构造函数、方法、字段或参数

禁用对路径、查询、表单和矩阵参数的自动URI解码。

DefaultValue

参数、字段或方法

为用@QueryParam@MatrixParam@CookieParam@FormParam@HeaderParam注解的字段、属性或方法参数指定一个默认值。如果请求URI中不存在相应的查询或矩阵参数,请求实体体中不存在相应的表单参数,或者请求中不包含相应的HTTP头,则将使用指定的值。

Context

字段、方法或参数

标识上下文类型列表中类型或第环境章适用部分的注入目标。

HttpMethod

注解

为请求方法设计器注解指定HTTP方法。

提供者

类型

指定所注解的类实现了JAX-RS扩展接口。

自JAX-RS 2.0起

NameBinding

注解

元注解,用于创建注解以将过滤器或拦截器绑定到资源方法和应用程序。名称绑定仅支持作为Server API的一部分。

Suspended

参数

指示资源方法是异步的。即,它在返回时不会产生响应。JAX-RS实现将挂起传入连接,直到响应可用。

PreMatching

类型

全局绑定注解,可应用于容器过滤器,以指示它应全局应用并在资源方法匹配之前应用。

BeanParam

参数、字段或方法

可用于注入用户定义的Bean,其字段和属性可以被JAX-RS参数注解注解。

ConstrainedTo

类型

可用于将提供者的适用性限制为仅客户端API或仅服务器API。如果省略,提供者可以在任一上下文中用于。

ParamConverter.Lazy

类型

指示应仅在实际请求值时才发生对默认值委托给ParamConverter的转换。

附录 B:HTTP头支持

下表列出了直接支持的HTTP头,这些头可以通过JAX-RS 运行时自动支持,或由应用程序使用JAX-RS API支持。任何请求头都可以使用HttpHeaders获取,请参见;此处未列出的响应头可以使用ResponseBuilder.header方法设置。

Header

描述

Accept

运行时用于选择资源方法,与@Produces 注解的值进行比较,参见声明媒体类型能力

Accept-Charset

如果应用程序使用Request.selectVariant方法,运行时将处理该头,参见内容协商和先决条件

Accept-Encoding

如果应用程序使用Request.selectVariant方法,运行时将处理该头,参见内容协商和先决条件

Accept-Language

如果应用程序使用Request.selectVariant方法,运行时将处理该头,参见内容协商和先决条件

Allow

包含在自动生成的405错误响应(参见第[request_matching]节)和自动生成的OPTIONS请求响应(参见HEAD和OPTIONS)中。

授权

取决于容器,信息可通过SecurityContext获取,参见安全上下文

Cache-Control

参见CacheControl类和ResponseBuilder.cacheControl方法。

Content-Encoding

响应头由应用程序使用Response.okResponseBuilder.variant设置。

Content-Language

响应头由应用程序使用Response.okResponseBuilder.languageResponseBuilder.variant设置。

Content-Length

对于请求,运行时会自动处理;对于响应,如果值由用于序列化消息实体的MessageBodyWriter提供,则会自动设置。

Content-Type

请求头由运行时用于选择资源方法,与@Consumes 注解的值进行比较,参见声明媒体类型能力。响应头由应用程序使用Response.okResponseBuilder.typeResponseBuilder.variant设置,或者由运行时自动设置(参见确定响应的MediaType)。

Cookie

参见Cookie类和HttpHeaders.getCookies方法。

Date

根据HTTP/1.1自动包含在响应中。

ETag

参见EntityTag类、Response.notModified方法和ResponseBuilder.tag方法。

Expect

取决于底层容器。

Expires

由应用程序使用ResponseBuilder.expires方法设置。

If-Match

如果应用程序使用相应的Request.evaluatePreconditions方法,运行时将处理此头,参见第[conneg_and_preconditions]节。If-Modified-Since & 如果应用程序使用相应的Request.evaluatePreconditions方法,运行时将处理此头,参见内容协商和先决条件

If-None-Match

如果应用程序使用相应的Request.evaluatePreconditions方法,运行时将处理此头,参见第内容协商和先决条件节。

If-Unmodified-Since

如果应用程序使用相应的Request.evaluatePreconditions方法,运行时将处理此头,参见内容协商和先决条件

Last-Modified

由应用程序使用ResponseBuilder.lastModified方法设置。Location & 由应用程序使用适用的Response 方法或直接使用ResponseBuilder.location方法设置。

Set-Cookie

参见NewCookie类和ResponseBuilder.cookie方法。

Transfer-Encoding

参见Transfer Encoding

Vary

由应用程序使用Response.notAcceptable方法或ResponseBuilder.variants方法设置。

WWW-Authenticate

取决于容器。

附录 C:处理管道

pipeline server
图 1. JAX-RS 服务器处理管道
pipeline client
图 2. JAX-RS 客户端处理管道

附录 D:变更日志

D.1. 自2.2-SNAPSHOT以来的更改

  • 引用Jakarta EE而非Java EE

D.2. 自2.1公开审查以来的更改

D.3. 自2.1早期草案以来的更改

D.4. 自2.0最终发布以来的更改

  • 字段和Bean属性:澄清了用于将字符串转换为Param的5个步骤的异常处理。允许List<T>Set<T>SortedSet<T>ParamConverter的组合。

  • 请求匹配:定义了客户端和服务器媒体类型。

  • 响应式客户端:引入响应式客户端支持的新章节。

  • 服务器发送事件:描述服务器发送事件API的新章节。

D.5. 自2.0建议最终草案以来的更改

D.6. 自2.0公开审查草案以来的更改

  • 在Javadoc中:类MessageBodyWriter中的方法getSize已弃用。

  • 资源章和提供者章:用相应的子类替换了WebApplicationException,具体取决于HTTP状态码。

  • 字段和Bean属性:新增ParamConverter的步骤。

  • 标准实体提供者:零长度实体和原始类型的特殊情况。

  • 客户端API章:更新了客户端API类型的配置示例和文本。configuration方法已被移除,取而代之的是Configurable接口。

  • 客户端API章:ClientFactory已重命名为ClientBuilder

  • 客户端API章:取消了对@Uri注解的支持。

  • 实体拦截器:新增段落,阐明当应用程序代码直接调用readFromwriteTo方法时,不会调用实体拦截器。

  • 实体拦截器:改进了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.Booleanjava.lang.Characterjava.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注解现在仅对提供者的自动发现是必需的(通常通过类扫描)。对于手动在ApplicationConfiguration等类中注册的提供者,它不再是必需的。

  • 自动发现:关于提供者类的自动发现的新章节。只有被@Provider注解的才必须被自动发现。

  • 客户端API章:特性现在是提供者,可以这样注册。功能不再可以被禁用。

  • 客户端API章:类Target已重命名为WebTarget。删除了描述如何使用构建器工厂类的文本(不再支持)。其他一些小的更新和错别字已修复。

  • 过滤器和拦截器章:修订了过滤器的扩展点。客户端API中的新过滤器接口ClientRequestFilterClientResponseFilter,以及服务器API中的新过滤器接口ContainerRequestFilterContainerResponseFilter。相应的上下文类在本章中也已更新。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.11. 自1.0发布以来的更改

D.12. 自建议最终草案以来的更改

D.13. 自公开审查草案以来的更改

  • 应用程序:将ApplicationConfig类重命名为Application。

  • 资源章:UriBuilder被重构为始终对组件进行编码。

  • 构造函数构造函数:增加了在构造函数选择不明确时发出警告的要求。

  • 字段和Bean属性FormParam不再要求在字段或属性上支持。

  • 返回类型:添加了描述如何从方法返回类型和返回的实例确定原始类型和泛型类型的文本。

  • URI模板:模板参数可以指定构成其捕获组的正则表达式。

  • 请求预处理:使预处理后的URI可用,而不是原始请求URI。添加了URI标准化。

  • 请求预处理:删除了基于URI的内容协商。

  • 请求匹配:重新组织了请求匹配算法,以消除冗余和提高可读性,功能无变化。

  • 将URI模板转换为正则表达式:对正则表达式进行了更改,以消除边缘情况。

  • 实体提供者:增加了当找不到实体提供者时使用JavaBean Activation Framework的要求。

  • 标准实体提供者:要求标准JAXB实体提供者优先使用应用程序提供的JAXB上下文,而不是自己的上下文。

  • 上下文提供者:增加了对指定上下文提供者媒体类型能力的支持。

  • 上下文类型:从可注入资源列表中移除了ContextResolver

  • 提供者:名称更改为Providers,移除了特定于实体提供者的文本,以反映更通用的功能。

  • HTTP头支持:新附录描述了特定HTTP头的支持位置。

参考文献


1. 由于所有Java枚举都包含的内置valueOf方法的局限性,枚举编写者通常会定义一个fromString方法。因此,如果可用,首选fromString方法。
2. 或者,如果返回类型是Response或其子类,则为返回实例的Entity属性。
3. 如果资源类的URI模板不以 / 字符结尾,则在连接时会添加一个。
4. 注意:一些容器可能会在将请求传递给实现之前执行此功能。
5. 在此,“文字字符”是指那些不是由模板变量替换产生的字符。
6. 例如,\(\mbox{text/html;q=1.0;qs=0.7;d=0} \ge \mbox{application/xml;q=1.0;qs=0.7;d=0}\) 和 \(\mbox{application/xml;q=1.0;qs=0.7;d=0} \ge \mbox{text/html;q=1.0;qs=0.7;d=0}\)。
7. 如果这些类型或类型集中的任何一个未指定,则假定为\(\mbox{*/*}\)和\(\mbox{\{*/*\}}\)。
8. 步骤[filter_methods
9. 请注意,+?语法表示java.util.regex.Pattern类中定义的非贪婪量词。
10. 实现可以自由地优化其处理,前提是结果等同于如果遵循这些步骤所获得的结果。
11. 此示例中的Collections类是任意的,不对应任何特定实现。有许多Java集合库提供此类功能。
12. 此类不旨在成为此拦截器的完整实现。
13. ConstraintViolation的属性路径提供了有关异常来源的信息。有关更多信息,请参见Javadoc。
14. 请求线程的最大数量通常由管理员设置;如果达到该上限,后续请求将被拒绝。
15. 事实上,没有API可以将实体拦截器绑定到单个SSE事件。
16. JAX-RS 的早期版本并未强制使用ServiceLoader。此向后兼容的更改从JAX-RS 2.1开始,旨在确保与Java SE 9模块系统的向前兼容性。