4. 媒体类型

4.1. HAL – 超文本应用程序语言

JSON 超文本应用程序语言或 HAL 是最简单的语言之一 以及在不讨论特定 Web 堆栈时采用的最广泛采用的超媒体媒体类型。spring-doc.cadn.net.cn

这是 Spring HATEOAS 采用的第一个基于规范的媒体类型。spring-doc.cadn.net.cn

4.1.1. 构建 HAL 表示模型

从 Spring HATEOAS 1.1 开始,我们发布了专用的HalModelBuilder这允许创建RepresentationModel实例。 以下是其基本假设:spring-doc.cadn.net.cn

  1. HAL 表示可以由任意对象(实体)提供支持,该对象构建了表示中包含的域字段。spring-doc.cadn.net.cn

  2. 表示可以通过各种嵌入文档来丰富,这些文档可以是任意对象或 HAL 表示本身(即包含嵌套的嵌入和链接)。spring-doc.cadn.net.cn

  3. 某些特定于 HAL 的模式(例如预览)可以直接在 API 中使用,因此设置表示的代码读起来就像您按照这些习语描述 HAL 表示一样。spring-doc.cadn.net.cn

以下是使用的 API 示例:spring-doc.cadn.net.cn

// An order
var order = new Order(…); (1)

// The customer who placed the order
var customer = customer.findById(order.getCustomerId());

var customerLink = Link.of("/orders/{id}/customer") (2)
  .expand(order.getId())
  .withRel("customer");

var additional = …

var model = HalModelBuilder.halModelOf(order)
  .preview(new CustomerSummary(customer)) (3)
  .forLink(customerLink) (4)
  .embed(additional) (5)
  .link(Link.of(…, IanaLinkRelations.SELF));
  .build();
1 我们设置了一些域类型。在本例中,是指与下达该订单的客户有关系的订单。
2 我们准备了一个指向资源的链接,该链接将公开客户详细信息
3 我们通过提供应该在_embeddable第。
4 我们通过提供 target 链接来结束该预览。它以透明方式添加到_linksObject 及其链接关系用作上一步中提供的对象的键。
5 可以添加其他对象以显示在_embedded. 它们列出的键派生自 objects 关系设置。它们可以通过@Relation或专用的LinkRelationProvider(参见使用LinkRelationProvider应用程序接口了解详情)。
{
  "_links" : {
    "self" : { "href" : "…" }, (1)
    "customer" : { "href" : "/orders/4711/customer" } (2)
  },
  "_embedded" : {
    "customer" : { … }, (3)
    "additional" : { … } (4)
  }
}
1 self链接。
2 customer链接通过….preview(…).forLink(…).
3 提供的 preview 对象。
4 通过 explicit 添加的其他元素….embed(…).

在 HAL 中_embedded也用于表示 top collections。 它们通常分组在从对象类型派生的链接关系下。 即,在 HAL 中,订单列表将如下所示:spring-doc.cadn.net.cn

{
  "_embedded" : {
    "order : [
      … (1)
    ]
  }
}
1 单个订单文件请见此处。

创建这样的表示法非常简单:spring-doc.cadn.net.cn

Collection<Order> orders = …;

HalModelBuilder.emptyHalDocument()
  .embed(orders);

也就是说,如果 order 为空,则无法派生出 link 关系以出现在_embedded,以便在集合为空时文档将保持为空。spring-doc.cadn.net.cn

如果你更喜欢显式地传达一个空集合,可以将一个类型交给….embed(…)方法采用Collection. 如果交给方法的集合为空,这将导致一个字段,其链接关系派生自给定类型。spring-doc.cadn.net.cn

HalModelBuilder.emptyHalModel()
  .embed(Collections.emptyList(), Order.class);
  // or
  .embed(Collections.emptyList(), LinkRelation.of("orders"));

将创建以下更明确的表示形式。spring-doc.cadn.net.cn

{
  "_embedded" : {
    "orders" : []
  }
}

4.1.2. 配置链接渲染

在 HAL 中,_linksentry 是一个 JSON 对象。属性名称是链接关系和 每个值要么是一个 Link 对象,要么是一个 Link 对象的数组spring-doc.cadn.net.cn

对于具有两个或多个链接的给定链接关系,规范对表示形式很明确:spring-doc.cadn.net.cn

例 24.具有两个链接与一个关系关联的 HAL 文档
{
  "_links": {
    "item": [
      { "href": "https://myhost/cart/42" },
      { "href": "https://myhost/inventory/12" }
    ]
  },
  "customer": "Dave Matthews"
}

但是,如果给定关系只有一个链接,则 spec 是模棱两可的。您可以将其渲染为单个对象 或作为单项数组。spring-doc.cadn.net.cn

默认情况下, Spring HATEOAS 使用最简洁的方法并呈现一个单链接关系,如下所示:spring-doc.cadn.net.cn

例 25.将单个链接呈现为对象的 HAL 文档
{
  "_links": {
    "item": { "href": "https://myhost/inventory/12" }
  },
  "customer": "Dave Matthews"
}

一些用户在使用 HAL 时不喜欢在数组和对象之间切换。他们更喜欢这种类型的渲染:spring-doc.cadn.net.cn

例 26.将单个链接渲染为数组的 HAL
{
  "_links": {
    "item": [{ "href": "https://myhost/inventory/12" }]
  },
  "customer": "Dave Matthews"
}

如果您希望自定义此策略,只需注入一个HalConfigurationbean 添加到应用程序配置中。 有多种选择。spring-doc.cadn.net.cn

例 27.全局 HAL 单链接渲染策略
@Bean
public HalConfiguration globalPolicy() {
  return new HalConfiguration() //
      .withRenderSingleLinks(RenderSingleLinks.AS_ARRAY); (1)
}
1 通过将 ALL 单链接关系渲染为数组来覆盖 Spring HATEOAS 的默认值。

如果您只想覆盖某些特定的链接关系,则可以创建一个HalConfigurationbean 的 like this:spring-doc.cadn.net.cn

例 28.基于链接关系的 HAL 单链接渲染策略
@Bean
public HalConfiguration linkRelationBasedPolicy() {
  return new HalConfiguration() //
      .withRenderSingleLinksFor( //
          IanaLinkRelations.ITEM, RenderSingleLinks.AS_ARRAY) (1)
      .withRenderSingleLinksFor( //
          LinkRelation.of("prev"), RenderSingleLinks.AS_SINGLE); (2)
}
1 始终渲染item链接关系作为数组。
2 呈现prev链接关系作为对象(当只有一个链接时)。

如果这些都不符合您的需求,则可以使用 Ant 样式的路径模式:spring-doc.cadn.net.cn

例 29.基于模式的 HAL 单链接渲染策略
@Bean
public HalConfiguration patternBasedPolicy() {
  return new HalConfiguration() //
      .withRenderSingleLinksFor( //
          "http*", RenderSingleLinks.AS_ARRAY); (1)
}
1 渲染所有以http作为数组。
基于模式的方法使用 Spring 的AntPathMatcher.

所有这些HalConfiguration凋灵可以组合成一个全面的策略。请务必测试您的 API 广泛以避免意外。spring-doc.cadn.net.cn

4.1.3. 链接标题国际化

HAL 定义了一个title属性。 可以使用 Spring 的资源包抽象和名为rest-messages以便客户端可以直接在其 UI 中使用它们。 此捆绑包将自动设置,并在 HAL 链接序列化期间使用。spring-doc.cadn.net.cn

要定义链接的标题,请使用 key 模板_links.$relationName.title如下:spring-doc.cadn.net.cn

例 30.示例rest-messages.properties
_links.cancel.title=Cancel order
_links.payment.title=Proceed to checkout

这将产生以下 HAL 表示形式:spring-doc.cadn.net.cn

例 31.定义了链接标题的示例 HAL 文档
{
  "_links" : {
    "cancel" : {
      "href" : "…"
      "title" : "Cancel order"
    },
    "payment" : {
      "href" : "…"
      "title" : "Proceed to checkout"
    }
  }
}

4.1.4. 使用CurieProvider应用程序接口

Web 链接 RFC 描述了已注册和扩展链接关系类型。Registered rels 是在链接关系类型的 IANA 注册机构中注册的已知字符串。外延relURI 可以由不希望注册关系类型的应用程序使用。每个 URI 都是唯一标识关系类型的 URI。这relURI 可以序列化为压缩 URI 或 Curie。例如,curie 的ex:persons代表 Link Relation Typeexample.com/rels/persons如果ex定义为example.com/rels/{rel}.如果使用 curies,则响应范围中必须存在基 URI。spring-doc.cadn.net.cn

rel默认值RelProvider是扩展关系类型,因此必须是 URI,这可能会导致大量开销。这CurieProviderAPI 负责处理这个问题:它允许您将基本 URI 定义为 URI 模板和代表该基本 URI 的前缀。如果CurieProvider存在时,RelProvider全部前置rel带有 curie 前缀的值。此外,一个curies链接会自动添加到 HAL 资源中。spring-doc.cadn.net.cn

以下配置定义了默认 curie 提供程序:spring-doc.cadn.net.cn

@Configuration
@EnableWebMvc
@EnableHypermediaSupport(type= {HypermediaType.HAL})
public class Config {

  @Bean
  public CurieProvider curieProvider() {
    return new DefaultCurieProvider("ex", new UriTemplate("https://www.example.com/rels/{rel}"));
  }
}

请注意,现在ex:prefix 自动出现在所有未向 IANA 注册的 rel 值之前,如ex:orders.客户端可以使用curies链接将 curie 解析为其完整形式。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

{
  "_links": {
    "self": {
      "href": "https://myhost/person/1"
    },
    "curies": {
      "name": "ex",
      "href": "https://example.com/rels/{rel}",
      "templated": true
    },
    "ex:orders": {
      "href": "https://myhost/person/1/orders"
    }
  },
  "firstname": "Dave",
  "lastname": "Matthews"
}

由于CurieProviderAPI 是允许自动创建 curie,您只能定义一个CurieProvider每个应用程序范围的 bean。spring-doc.cadn.net.cn

4.2. HAL 形式

HAL-FORMS 旨在向 HAL 媒体类型添加运行时 FORM 支持。spring-doc.cadn.net.cn

HAL-FORMS “看起来像 HAL”。但是,重要的是要记住,HAL-FORMS 与 HAL 不同 — 两者 不应以任何方式被视为可互换。spring-doc.cadn.net.cn

— Mike Amundsen
HAL-FORMS 规格

要启用此媒体类型,请在代码中放入以下配置:spring-doc.cadn.net.cn

例 32.支持 HAL-FORMS 的应用程序
@Configuration
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
public class HalFormsApplication {

}

每当客户提供Accept标头替换为application/prs.hal-forms+json,你可以期待这样的结果:spring-doc.cadn.net.cn

例 33.HAL-FORMS 示例文档
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "role" : "ring bearer",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/employees/1"
    }
  },
  "_templates" : {
    "default" : {
      "method" : "put",
      "properties" : [ {
        "name" : "firstName",
        "required" : true
      }, {
        "name" : "lastName",
        "required" : true
      }, {
        "name" : "role",
        "required" : true
      } ]
    },
    "partiallyUpdateEmployee" : {
      "method" : "patch",
      "properties" : [ {
        "name" : "firstName",
        "required" : false
      }, {
        "name" : "lastName",
        "required" : false
      }, {
        "name" : "role",
        "required" : false
      } ]
    }
  }
}

查看 HAL-FORMS 规范以了解 _templates 属性的详细信息。 阅读 Affordances API 以使用这些额外的元数据来增强您的控制器。spring-doc.cadn.net.cn

至于单项 (EntityModel) 和聚合根集合 (CollectionModel),Spring HATEOAS 会渲染它们 与 HAL 文档相同。spring-doc.cadn.net.cn

4.2.1. 定义 HAL-FORMS 元数据

HAL-FORMS 允许描述每个表单字段的标准。 Spring HATEOAS 允许通过为 Importing 和 Output 类型塑造模型类型并在其上使用 Comments 来自定义它们。spring-doc.cadn.net.cn

每个模板都将定义以下属性:spring-doc.cadn.net.cn

表 1.模板属性
属性 描述

contentTypespring-doc.cadn.net.cn

服务器预期接收的媒体类型。仅当控制器方法指向公开@RequestMapping(consumes = "…")属性,或者在设置提供时显式定义了媒体类型。spring-doc.cadn.net.cn

methodspring-doc.cadn.net.cn

提交模板时使用的 HTTP 方法。spring-doc.cadn.net.cn

targetspring-doc.cadn.net.cn

要将表单提交到的目标 URI。仅当提供目标与声明它时所在的链接不同时,才会呈现。spring-doc.cadn.net.cn

titlespring-doc.cadn.net.cn

显示模板时的人类可读标题。spring-doc.cadn.net.cn

propertiesspring-doc.cadn.net.cn

所有属性都要随表格一起提交(见下文)。spring-doc.cadn.net.cn

每个属性都将定义以下属性:spring-doc.cadn.net.cn

表 2.属性属性
属性 描述

readOnlyspring-doc.cadn.net.cn

设置为true如果属性没有 setter 方法。如果存在,请使用 Jackson 的@JsonProperty(Access.READ_ONLY)在访问器或字段上。默认情况下不渲染,因此默认为false.spring-doc.cadn.net.cn

regexspring-doc.cadn.net.cn

可以使用 JSR-303 的@Pattern注解。在后者的情况下,该模式将用于声明为该特定类型的每个属性。默认情况下不渲染。spring-doc.cadn.net.cn

requiredspring-doc.cadn.net.cn

可以使用 JSR-303 的@NotNull.默认情况下不渲染,因此默认为false.模板使用PATCHAS 方法会自动将所有属性设置为 Not Required。spring-doc.cadn.net.cn

maxspring-doc.cadn.net.cn

属性允许的最大值。派生自 JSR-303 的@Size、Hibernate Validator 的@Range或 JSR-303 的@Max@DecimalMax附注。spring-doc.cadn.net.cn

maxLengthspring-doc.cadn.net.cn

属性允许的最大长度值。源自 Hibernate Validator 的@Length注解。spring-doc.cadn.net.cn

minspring-doc.cadn.net.cn

属性允许的最小值。源自 JSR-303 的@Size、Hibernate Validator 的@Range或 JSR-303 的@Min@DecimalMin附注。spring-doc.cadn.net.cn

minLengthspring-doc.cadn.net.cn

属性允许的最小长度值。源自 Hibernate Validator 的@Length注解。spring-doc.cadn.net.cn

optionsspring-doc.cadn.net.cn

提交表单时要从中选择值的选项。有关详细信息,请参阅定义属性的 HAL-FORMS 选项spring-doc.cadn.net.cn

promptspring-doc.cadn.net.cn

渲染表单输入时使用的用户可读提示。有关详细信息,请参阅 属性提示。spring-doc.cadn.net.cn

placeholderspring-doc.cadn.net.cn

用户可读的占位符,用于为预期格式提供示例。定义这些内容的方式遵循 Property 提示符,但使用后缀_placeholder.spring-doc.cadn.net.cn

typespring-doc.cadn.net.cn

从显式@InputTypeannotation、JSR-303 验证 annotation 或属性的类型。spring-doc.cadn.net.cn

对于无法手动注释的类型,您可以通过HalFormsConfigurationbean 存在于应用程序上下文中。spring-doc.cadn.net.cn

@Configuration
class CustomConfiguration {

  @Bean
  HalFormsConfiguration halFormsConfiguration() {

    HalFormsConfiguration configuration = new HalFormsConfiguration();
    configuration.registerPatternFor(CreditCardNumber.class, "[0-9]{16}");
  }
}

此设置将导致 HAL-FORMS 模板属性为以下类型的制图表达模型属性CreditCardNumber要声明regex值为[0-9]{16}.spring-doc.cadn.net.cn

定义属性的 HAL-FORMS 选项

对于其值应该与某个超值集匹配的属性,HAL-FORMS 定义options属性定义中的 sub-document 的 sub-document 中。 特定属性的可用选项可以通过以下方式进行描述HalFormsConfigurationwithOptions(…)将指针指向类型的属性并使用 Creator 函数将PropertyMetadata转换为HalFormsOptions实例。spring-doc.cadn.net.cn

@Configuration
class CustomConfiguration {

  @Bean
  HalFormsConfiguration halFormsConfiguration() {

    HalFormsConfiguration configuration = new HalFormsConfiguration();
    configuration.withOptions(Order.class, "shippingMethod" metadata ->
      HalFormsOptions.inline("FedEx", "DHL"));
  }
}

了解我们如何设置选项值FedExDHL作为Order.shippingMethod财产。 或者HalFormsOptions.remote(…)可以指向动态提供值的远程资源。 有关选项设置的更多约束,请参阅 spec 或 JavadocHalFormsOptions.spring-doc.cadn.net.cn

4.2.2. 表单属性的国际化

HAL-FORMS 包含用于人工解释的属性,例如模板的标题或属性提示。 这些可以使用 Spring 的资源包支持和rest-messages由 Spring HATEOAS 默认配置的资源包。spring-doc.cadn.net.cn

模板标题

要定义模板标题,请使用以下模式:_templates.$affordanceName.title.请注意,在 HAL-FORMS 中,模板的名称为default如果它是唯一的。 这意味着您通常必须使用功能描述的本地或完全限定的输入类型名称来限定键。spring-doc.cadn.net.cn

例 34.定义 HAL-FORMS 模板标题
_templates.default.title=Some title (1)
_templates.putEmployee.title=Create employee (2)
Employee._templates.default.title=Create employee (3)
com.acme.Employee._templates.default.title=Create employee (4)
1 标题的全局定义 usingdefault作为键。
2 使用实际提供名称作为键的游戏的全局定义。除非在创建功能时明确定义,否则默认为创建功能时指向的方法的名称。
3 本地定义的标题,将应用于名为Employee.
4 使用完全限定类型名称的标题定义。
使用实际提示名称的键优先于默认的键。
属性提示

属性提示也可以通过rest-messages由 Spring HATEOAS 自动配置的资源包。 键可以全局定义、本地定义或完全限定,并且需要一个._prompt连接到 actual 属性键:spring-doc.cadn.net.cn

例 35.定义email财产
firstName._prompt=Firstname (1)
Employee.firstName._prompt=Firstname (2)
com.acme.Employee.firstName._prompt=Firstname (3)
1 所有名为firstName将呈现 “Firstname”,与它们声明的类型无关。
2 firstName名为Employee将提示 “Firstname”。
3 firstName的属性com.acme.Employee将分配 “Firstname” 的提示。

4.2.3. 一个完整的例子

让我们看一下一些示例代码,它结合了上述所有定义和自定义属性。 一个RepresentationModelfor a customer 可能如下所示:spring-doc.cadn.net.cn

class CustomerRepresentation
  extends RepresentationModel<CustomerRepresentation> {

  String name;
  LocalDate birthdate; (1)
  @Pattern(regex = "[0-9]{16}") String ccn; (2)
  @Email String email; (3)
}
1 我们定义了一个birthdatetype 为LocalDate.
2 我们期望ccn以遵循正则表达式。
3 我们定义email作为使用 JSR-303 的电子邮件@Email注解。

请注意,此类型不是域类型。 它被有意设计为捕获各种可能无效的 input,以便可以立即拒绝字段的潜在错误值。spring-doc.cadn.net.cn

让我们继续看看控制器如何使用该模型:spring-doc.cadn.net.cn

@Controller
class CustomerController {

  @PostMapping("/customers")
  EntityModel<?> createCustomer(@RequestBody CustomerRepresentation payload) { (1)
    // …
  }

  @GetMapping("/customers")
  CollectionModel<?> getCustomers() {

    CollectionModel<?> model = …;

    CustomerController controller = methodOn(CustomerController.class);

    model.add(linkTo(controller.getCustomers()).withSelfRel() (2)
      .andAfford(controller.createCustomer(null)));

    return ResponseEntity.ok(model);
  }
}
1 声明控制器方法以使用上面定义的表示模型将请求正文绑定到POST颁发给/customers.
2 一个GETrequest 添加到/customers准备模型,添加self链接,并在该链接上声明一个指向映射到POST. 这将导致构建一个可供模型,该模型(取决于最终呈现的媒体类型)将被转换为特定于媒体类型的格式。

接下来,让我们添加一些额外的元数据,使表单更易于人类访问:spring-doc.cadn.net.cn

在 中声明的其他属性rest-messages.properties.
CustomerRepresentation._template.createCustomer.title=Create customer (1)
CustomerRepresentation.ccn._prompt=Credit card number (2)
CustomerRepresentation.ccn._placeholder=1234123412341234 (2)
1 我们通过指向createCustomer(…)方法。
2 我们显式地为ccn属性的CustomerRepresentation型。

如果客户端现在发出GETrequest 添加到/customers使用Accept的标头application/prs.hal-forms+json,响应 HAL 文档将扩展为 HAL-FORMS 文档,以包括以下内容_templates定义:spring-doc.cadn.net.cn

{
  …,
  "_templates" : {
    "default" : { (1)
      "title" : "Create customer", (2)
      "method" : "post", (3)
      "properties" : [ {
        "name" : "name",
        "required" : true,
        "type" : "text" (4)
      } , {
        "name" : "birthdate",
        "required" : true,
        "type" : "date" (4)
      } , {
        "name" : "ccn",
        "prompt" : "Credit card number", (5)
        "placeholder" : "1234123412341234" (5)
        "required" : true,
        "regex" : "[0-9]{16}", (6)
        "type" : "text"
      } , {
        "name" : "email",
        "prompt" : "Email",
        "required" : true,
        "type" : "email" (7)
      } ]
    }
  }
}
1 名为default已公开。它的名字是default因为它是定义的唯一模板,并且 spec 要求使用该名称。 如果附加了多个模板(通过声明其他功能),则每个模板都将以它们指向的方法命名。
2 模板标题派生自 Resource Bundle 中定义的值。请注意,根据Accept-Language标头和可用性不同的值可能会返回。
3 methodattribute 的值派生自获取功能的方法的映射。
4 type属性的值text派生自属性的类型String. 这同样适用于birthdate属性,但会导致date.
5 ccnproperty 也派生自资源包。
6 @Pattern声明ccn属性公开为regextemplate 属性的属性。
7 @Email注解emailproperty 已转换为相应的type价值。

HAL Explorer 会考虑 HAL-FORMS 模板,它会自动从这些描述中呈现 HTML 表单。spring-doc.cadn.net.cn

4.3. HTTP 问题详情

HTTP API 的问题详细信息是一种媒体类型,用于在 HTTP 响应中携带错误的机器可读详细信息,以避免需要为 HTTP API 定义新的错误响应格式。spring-doc.cadn.net.cn

HTTP 问题详细信息定义了一组 JSON 属性,这些属性包含其他信息,用于向 HTTP 客户端描述错误详细信息。 在 RFC 文档的相关部分中找到有关这些属性的更多详细信息。spring-doc.cadn.net.cn

您可以使用Problemmedia 类型的域类型:spring-doc.cadn.net.cn

使用 Spring HATEOAS 报告问题详细信息Problem类型
@RestController
class PaymentController {

  @PutMapping
  ResponseEntity<?> issuePayment(@RequestBody PaymentRequest request) {

    PaymentResult result = payments.issuePayment(request.orderId, request.amount);

    if (result.isSuccess()) {
      return ResponseEntity.ok(result);
    }

    String title = messages.getMessage("payment.out-of-credit");
    String detail = messages.getMessage("payment.out-of-credit.details", //
        new Object[] { result.getBalance(), result.getCost() });

    Problem problem = Problem.create() (1)
        .withType(OUT_OF_CREDIT_URI) //
        .withTitle(title) (2)
        .withDetail(detail) //
        .withInstance(PAYMENT_ERROR_INSTANCE.expand(result.getPaymentId())) //
        .withProperties(map -> { (3)
          map.put("balance", result.getBalance());
          map.put("accounts", Arrays.asList( //
              ACCOUNTS.expand(result.getSourceAccountId()), //
              ACCOUNTS.expand(result.getTargetAccountId()) //
          ));
        });

    return ResponseEntity.status(HttpStatus.FORBIDDEN) //
        .body(problem);
  }
}
1 首先,创建Problem使用公开的工厂方法。
2 您可以使用 Spring 的国际化功能定义由媒体类型定义的默认属性的值,例如类型 URI、标题和详细信息(见上文)。
3 自定义属性可以通过Map或显式对象(见下文)。

要将专用对象用于自定义属性,请声明一个类型,创建并填充它的实例,并将其交给Problem实例通过….withProperties(…)或在实例创建时通过Problem.create(…).spring-doc.cadn.net.cn

使用专用类型捕获扩展问题属性
class AccountDetails {
  int balance;
  List<URI> accounts;
}

problem.withProperties(result.getDetails());

// or

Problem.create(result.getDetails());

这将产生如下所示的响应:spring-doc.cadn.net.cn

HTTP Problem Details 响应示例
{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "detail": "Your current balance is 30, but that costs 50.",
  "instance": "/account/12345/msgs/abc",
  "balance": 30,
  "accounts": ["/account/12345",
               "/account/67890"]
}

4.4. 集合 + JSON

Collection+JSON 是使用 IANA 批准的媒体类型注册的 JSON 规范application/vnd.collection+json.spring-doc.cadn.net.cn

Collection+JSON 是一种基于 JSON 的读/写超媒体类型,旨在支持 简单集合的管理和查询。spring-doc.cadn.net.cn

— Mike Amundsen
集合 + JSON 规范

Collection+JSON 提供了一种统一的方式来表示单个项目资源和集合。 要启用此媒体类型,请在代码中放入以下配置:spring-doc.cadn.net.cn

例 36.集合 + 支持 JSON 的应用程序
@Configuration
@EnableHypermediaSupport(type = HypermediaType.COLLECTION_JSON)
public class CollectionJsonApplication {

}

此配置将使应用程序响应具有Accept的标头application/vnd.collection+json如下所示。spring-doc.cadn.net.cn

规范中的以下示例显示了单个项目:spring-doc.cadn.net.cn

例 37.Collection+JSON 单项示例
{
  "collection": {
    "version": "1.0",
    "href": "https://example.org/friends/", (1)
    "links": [   (2)
      {
        "rel": "feed",
        "href": "https://example.org/friends/rss"
      },
      {
        "rel": "queries",
        "href": "https://example.org/friends/?queries"
      },
      {
        "rel": "template",
        "href": "https://example.org/friends/?template"
      }
    ],
    "items": [  (3)
      {
        "href": "https://example.org/friends/jdoe",
        "data": [  (4)
          {
            "name": "fullname",
            "value": "J. Doe",
            "prompt": "Full Name"
          },
          {
            "name": "email",
            "value": "[email protected]",
            "prompt": "Email"
          }
        ],
        "links": [ (5)
          {
            "rel": "blog",
            "href": "https://examples.org/blogs/jdoe",
            "prompt": "Blog"
          },
          {
            "rel": "avatar",
            "href": "https://examples.org/images/jdoe",
            "prompt": "Avatar",
            "render": "image"
          }
        ]
      }
    ]
  }
}
1 self链接存储在文档的href属性。
2 文档的顶部links部分包含集合级链接(减去self链接)。
3 items部分包含一组数据。由于这是一个单项文档,因此它只有一个条目。
4 data部分包含实际内容。它由属性组成。
5 项目的单个links.

上一个片段是从规范中提取的。当 Spring HATEOAS 渲染EntityModel,它将:spring-doc.cadn.net.cn

在渲染资源集合时,文档几乎相同,只是里面会有多个条目 这itemsJSON 数组,每个条目一个。spring-doc.cadn.net.cn

更具体地说,Spring HATEOAS 将:spring-doc.cadn.net.cn

4.5. UBER - 交换表示的统一基础

UBER 是一个实验性的 JSON 规范spring-doc.cadn.net.cn

UBER 文档格式是一种最小读/写超媒体类型,旨在支持简单的状态传输和临时 基于超媒体的过渡。spring-doc.cadn.net.cn

— Mike Amundsen
UBER 规格

UBER 提供了一种统一的方式来表示单个项目资源和集合。要启用此媒体类型,请在代码中放入以下配置:spring-doc.cadn.net.cn

例 38.支持 UBER+JSON 的应用程序
@Configuration
@EnableHypermediaSupport(type = HypermediaType.UBER)
public class UberApplication {

}

此配置将使您的应用程序使用Accept页眉application/vnd.amundsen-uber+json如下所示:spring-doc.cadn.net.cn

例 39.UBER 样本文档
{
  "uber" : {
    "version" : "1.0",
    "data" : [ {
      "rel" : [ "self" ],
      "url" : "/employees/1"
    }, {
      "name" : "employee",
      "data" : [ {
        "name" : "role",
        "value" : "ring bearer"
      }, {
        "name" : "name",
        "value" : "Frodo"
      } ]
    } ]
  }
}

这种媒体类型仍在开发中,规范本身也是如此。如果您在使用它时遇到问题,请随时打开一个票证spring-doc.cadn.net.cn

UBER 媒体类型与拼车公司 Uber Technologies Inc. 没有任何关联。

4.6. ALPS - 应用程序级配置文件语义

ALPS 是一种介质类型,用于提供 有关其他资源的基于配置文件的元数据。spring-doc.cadn.net.cn

ALPS 文档可以用作配置文件 使用应用程序解释文档的应用程序语义 - 不可知的媒体类型(如 HTML、HAL、Collection+JSON、Siren、 等)。这提高了配置文件文档的可重用性 media 类型。spring-doc.cadn.net.cn

— 迈克·阿蒙森
ALPS 规格

ALPS 不需要特殊激活。相反,您 “构建” 了一个Alps从 Spring MVC 或 Spring WebFlux Web 方法记录并返回它,如下所示:spring-doc.cadn.net.cn

例 40.构建一个Alps记录
@GetMapping(value = "/profile", produces = ALPS_JSON_VALUE)
Alps profile() {

  return Alps.alps() //
      .doc(doc() //
          .href("https://example.org/samples/full/doc.html") //
          .value("value goes here") //
          .format(Format.TEXT) //
          .build()) //
      .descriptor(getExposedProperties(Employee.class).stream() //
          .map(property -> Descriptor.builder() //
              .id("class field [" + property.getName() + "]") //
              .name(property.getName()) //
              .type(Type.SEMANTIC) //
              .ext(Ext.builder() //
                  .id("ext [" + property.getName() + "]") //
                  .href("https://example.org/samples/ext/" + property.getName()) //
                  .value("value goes here") //
                  .build()) //
              .rt("rt for [" + property.getName() + "]") //
              .descriptor(Collections.singletonList(Descriptor.builder().id("embedded").build())) //
              .build()) //
          .collect(Collectors.toList()))
      .build();
}
  • 此示例利用PropertyUtils.getExposedProperties()提取有关 Domain 对象属性的元数据。spring-doc.cadn.net.cn

此 fragment 插入了测试数据。它生成如下所示的 JSON:spring-doc.cadn.net.cn

例 41.阿尔卑斯 JSON
{
  "version": "1.0",
  "doc": {
    "format": "TEXT",
    "href": "https://example.org/samples/full/doc.html",
    "value": "value goes here"
  },
  "descriptor": [
    {
      "id": "class field [name]",
      "name": "name",
      "type": "SEMANTIC",
      "descriptor": [
        {
          "id": "embedded"
        }
      ],
      "ext": {
        "id": "ext [name]",
        "href": "https://example.org/samples/ext/name",
        "value": "value goes here"
      },
      "rt": "rt for [name]"
    },
    {
      "id": "class field [role]",
      "name": "role",
      "type": "SEMANTIC",
      "descriptor": [
        {
          "id": "embedded"
        }
      ],
      "ext": {
        "id": "ext [role]",
        "href": "https://example.org/samples/ext/role",
        "value": "value goes here"
      },
      "rt": "rt for [role]"
    }
  ]
}

如果您愿意,您可以手动编写它们,而不是“自动”将每个字段链接到域对象的字段。这也是可能的 要使用 Spring Framework 的消息包和MessageSource接口。这使您能够将这些值委托给 特定于区域设置的消息捆绑,甚至国际化元数据。spring-doc.cadn.net.cn

4.7. 基于社区的媒体类型

由于能够创建自己的媒体类型,因此有几项由社区主导的工作来构建其他媒体类型。spring-doc.cadn.net.cn

4.7.1. JSON:API

Maven 坐标
<dependency>
    <groupId>com.toedter</groupId>
    <artifactId>spring-hateoas-jsonapi</artifactId>
    <version>{see project page for current version}</version>
</dependency>
Gradle 坐标
implementation 'com.toedter:spring-hateoas-jsonapi:{see project page for current version}'

如果您需要快照发布,请访问项目页面了解更多详细信息。spring-doc.cadn.net.cn

4.7.2. 警笛

Maven 坐标
<dependency>
    <groupId>de.ingogriebsch.hateoas</groupId>
    <artifactId>spring-hateoas-siren</artifactId>
    <version>{see project page for current version}</version>
    <scope>compile</scope>
</dependency>
Gradle 坐标
implementation 'de.ingogriebsch.hateoas:spring-hateoas-siren:{see project page for current version}'

4.8. 注册自定义媒体类型

Spring HATEOAS 允许您通过 SPI 集成自定义媒体类型。 这种实现的构建块是:spring-doc.cadn.net.cn

  1. 某种形式的JacksonObjectMapper定制。在最简单的例子中,那就是 JacksonModule实现。spring-doc.cadn.net.cn

  2. 一个LinkDiscoverer实现,以便客户端支持能够检测表示中的链接。spring-doc.cadn.net.cn

  3. 少量的基础设施配置,将允许 Spring HATEOAS 找到自定义实现并选取它。spring-doc.cadn.net.cn

4.8.1. 自定义媒体类型配置

Spring HATEOAS 通过扫描应用程序上下文以查找HypermediaMappingInformation接口。 每种媒体类型都必须实现此接口,以便:spring-doc.cadn.net.cn

定义您自己的媒体类型可能看起来很简单,如下所示:spring-doc.cadn.net.cn

@Configuration
public class MyMediaTypeConfiguration implements HypermediaMappingInformation {

  @Override
  public List<MediaType> getMediaTypes() {
    return Collections.singletonList(MediaType.parseMediaType("application/vnd-acme-media-type")); (1)
  }

  @Override
  public Module getJacksonModule() {
    return new Jackson2MyMediaTypeModule(); (2)
  }

  @Bean
  MyLinkDiscoverer myLinkDiscoverer() {
    return new MyLinkDiscoverer(); (3)
  }
}
1 configuration 类返回它支持的媒体类型。这适用于服务器端和客户端方案。
2 它会覆盖getJacksonModule()提供自定义序列化程序来创建特定于媒体类型的表示形式。
3 它还声明了一个自定义LinkDiscovererimplementation 以获得进一步的客户端支持。

Jackson 模块通常声明SerializerDeserializer制图表达模型类型的实现RepresentationModel,EntityModel,CollectionModelPagedModel. 如果您需要进一步定制 JacksonObjectMapper(就像一个自定义HandlerInstantiator),您也可以覆盖configureObjectMapper(…).spring-doc.cadn.net.cn

以前版本的参考文档提到了实现MediaTypeConfigurationProvider接口并将其注册到spring.factories. 这不是必需的。 此 SPI 仅用于 Spring HATEOAS 提供的开箱即用的媒体类型。 只需实现HypermediaMappingInformation接口并将其注册为 Spring bean 就足够了。spring-doc.cadn.net.cn

4.8.2. 建议

实现媒体类型表示的首选方法是提供与预期格式匹配的类型层次结构,并且可以由 Jackson 按原样序列化。 在SerializerDeserializer注册的 implementationsRepresentationModel,将实例转换为特定于媒体类型的模型类型,然后在 Jackson 序列化器中查找这些类型。spring-doc.cadn.net.cn

默认情况下支持的媒体类型使用与第三方实现相同的配置机制。 因此,值得研究mediatype. 请注意,内置的 media type 实现保持其配置类 package 的私有性,因为它们是通过@EnableHypermediaSupport. 自定义实现可能应该将这些 public 化,以确保用户可以从他们的应用程序包中导入这些配置类。spring-doc.cadn.net.cn


APP信息