4. 媒体类型
4.1. HAL – 超文本应用程序语言
JSON 超文本应用程序语言或 HAL 是最简单的语言之一 以及在不讨论特定 Web 堆栈时采用的最广泛采用的超媒体媒体类型。
这是 Spring HATEOAS 采用的第一个基于规范的媒体类型。
4.1.1. 构建 HAL 表示模型
从 Spring HATEOAS 1.1 开始,我们提供了一个专用的,允许通过 HAL 惯用的 API 创建实例。
以下是其基本假设:HalModelBuilder
RepresentationModel
-
HAL 表示可以由任意对象(实体)提供支持,该对象构建了表示中包含的域字段。
-
表示可以通过各种嵌入文档来丰富,这些文档可以是任意对象或 HAL 表示本身(即包含嵌套的嵌入和链接)。
-
某些特定于 HAL 的模式(例如预览)可以直接在 API 中使用,因此设置表示的代码读起来就像您按照这些习语描述 HAL 表示一样。
以下是使用的 API 示例:
// 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();
{
"_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 中,也用于表示顶级集合。
它们通常分组在从对象类型派生的链接关系下。
即,在 HAL 中,订单列表将如下所示:_embedded
{
"_embedded" : {
"orders : [
… (1)
]
}
}
1 | 单个订单文件请见此处。 |
创建这样的表示法非常简单:
Collection<Order> orders = …;
HalModelBuilder.emptyHalDocument()
.embed(orders);
也就是说,如果 order 为空,则无法派生 link 关系以显示在 内部,因此如果集合为空,则文档将保持为空。_embedded
如果你更喜欢显式地传达一个空集合,则可以将类型交给采用 .
如果交给方法的集合为空,这将导致一个字段,其链接关系派生自给定类型。….embed(…)
Collection
HalModelBuilder.emptyHalModel()
.embed(Collections.emptyList(), Order.class);
// or
.embed(Collections.emptyList(), LinkRelation.of("orders"));
将创建以下更明确的表示形式。
{
"_embedded" : {
"orders" : []
}
}
4.1.2. 配置链接渲染
在 HAL 中,该条目是一个 JSON 对象。属性名称是链接关系和
每个值要么是一个 Link 对象,要么是一个 Link 对象的数组。_links
对于具有两个或多个链接的给定链接关系,规范对表示形式很明确:
{
"_links": {
"item": [
{ "href": "https://myhost/cart/42" },
{ "href": "https://myhost/inventory/12" }
]
},
"customer": "Dave Matthews"
}
但是,如果给定关系只有一个链接,则 spec 是模棱两可的。您可以将其渲染为单个对象 或作为单项数组。
默认情况下, Spring HATEOAS 使用最简洁的方法并呈现一个单链接关系,如下所示:
{
"_links": {
"item": { "href": "https://myhost/inventory/12" }
},
"customer": "Dave Matthews"
}
一些用户在使用 HAL 时不喜欢在数组和对象之间切换。他们更喜欢这种类型的渲染:
{
"_links": {
"item": [{ "href": "https://myhost/inventory/12" }]
},
"customer": "Dave Matthews"
}
如果您希望自定义此策略,则只需将 bean 注入到应用程序配置中即可。
有多种选择。HalConfiguration
@Bean
public HalConfiguration globalPolicy() {
return new HalConfiguration() //
.withRenderSingleLinks(RenderSingleLinks.AS_ARRAY); (1)
}
1 | 通过将 ALL 单链接关系渲染为数组来覆盖 Spring HATEOAS 的默认值。 |
如果你只想覆盖一些特定的链接关系,你可以创建一个这样的 bean:HalConfiguration
@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 样式的路径模式:
@Bean
public HalConfiguration patternBasedPolicy() {
return new HalConfiguration() //
.withRenderSingleLinksFor( //
"http*", RenderSingleLinks.AS_ARRAY); (1)
}
1 | 将所有以 开头的链接关系渲染为数组。http |
基于模式的方法使用 Spring 的 .AntPathMatcher |
所有这些 withers 都可以组合起来形成一个全面的策略。请务必测试您的 API
广泛以避免意外。HalConfiguration
4.1.3. 链接标题国际化
HAL 为其 link 对象定义一个属性。
可以使用 Spring 的资源包抽象和名为 的资源包来填充这些标题,以便客户端可以直接在其 UI 中使用它们。
此捆绑包将自动设置,并在 HAL 链接序列化期间使用。title
rest-messages
要定义链接的标题,请使用键模板,如下所示:_links.$relationName.title
rest-messages.properties
_links.cancel.title=Cancel order
_links.payment.title=Proceed to checkout
这将产生以下 HAL 表示形式:
{
"_links" : {
"cancel" : {
"href" : "…"
"title" : "Cancel order"
},
"payment" : {
"href" : "…"
"title" : "Proceed to checkout"
}
}
}
4.1.4. 使用 APICurieProvider
Web 链接 RFC 描述了已注册和扩展链接关系类型。Registered rels 是在链接关系类型的 IANA 注册机构中注册的已知字符串。不希望注册关系类型的应用程序可以使用扩展 URI。每个 URI 都是唯一标识关系类型的 URI。URI 可以序列化为压缩 URI 或 Curie。例如,curie 表示链接关系类型 if 定义为 。如果使用 curies,则响应范围中必须存在基 URI。rel
rel
ex:persons
example.com/rels/persons
ex
example.com/rels/{rel}
默认情况下创建的值是扩展关系类型,因此必须是 URI,这可能会导致大量开销。API 负责处理这些:它允许您将基本 URI 定义为 URI 模板和代表该基本 URI 的前缀。如果存在 a,则所有值前面都会加上 curie 前缀。此外,系统会自动向 HAL 资源添加一个链接。rel
RelProvider
CurieProvider
CurieProvider
RelProvider
rel
curies
以下配置定义了默认 curie 提供程序:
@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}"));
}
}
请注意,现在前缀会自动显示在未向 IANA 注册的所有 rel 值之前,如 。客户端可以使用该链接将 curie 解析为其完整形式。
以下示例显示了如何执行此操作:ex:
ex:orders
curies
{
"_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"
}
由于 API 的目的是允许自动创建 curie,因此每个应用程序范围只能定义一个 Bean。CurieProvider
CurieProvider
4.2. HAL 形式
HAL-FORMS “看起来像 HAL”。但是,重要的是要记住,HAL-FORMS 与 HAL 不同 — 两者 不应以任何方式被视为可互换。
HAL-FORMS 规格
要启用此媒体类型,请在代码中放入以下配置:
@Configuration
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
public class HalFormsApplication {
}
每当客户端提供 标头时,您都可以期待如下内容:Accept
application/prs.hal-forms+json
{
"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 HATEOAS 会渲染它们
与 HAL 文档相同。EntityModel
CollectionModel
4.2.1. 定义 HAL-FORMS 元数据
HAL-FORMS 允许描述每个表单字段的标准。 Spring HATEOAS 允许通过为 Importing 和 Output 类型塑造模型类型并在其上使用 Comments 来自定义它们。
每个模板都将定义以下属性:
属性 | 描述 |
---|---|
|
服务器预期接收的媒体类型。仅当控制器方法指向公开属性,或者在设置功能时显式定义了媒体类型时,才包含该选项。 |
|
提交模板时使用的 HTTP 方法。 |
|
要将表单提交到的目标 URI。仅当提供目标与声明它时所在的链接不同时,才会呈现。 |
|
显示模板时的人类可读标题。 |
|
所有属性都要随表格一起提交(见下文)。 |
每个属性都将定义以下属性:
属性 | 描述 |
---|---|
|
如果属性没有 setter 方法,则设置为 。如果存在,请在访问器或字段上显式使用 Jackson's。默认情况下不呈现,因此默认为 . |
|
可以通过在字段或类型上使用 JSR-303 的 Comments 进行自定义。在后者的情况下,该模式将用于声明为该特定类型的每个属性。默认情况下不渲染。 |
|
可以使用 JSR-303 的 .默认情况下不渲染,因此默认为 .使用 as 方法的模板会自动将所有属性设置为 not required。 |
|
属性允许的最大值。源自 Hibernate Validator 或 JSR-303 和注解。 |
|
属性允许的最大长度值。源自 Hibernate Validator 的注解。 |
|
属性允许的最小值。源自 Hibernate Validator 或 JSR-303 和注解。 |
|
属性允许的最小长度值。源自 Hibernate Validator 的注解。 |
|
提交表单时要从中选择值的选项。有关详细信息,请参阅定义属性的 HAL-FORMS 选项。 |
|
渲染表单输入时使用的用户可读提示。有关详细信息,请参阅 属性提示。 |
|
用户可读的占位符,用于为预期格式提供示例。定义这些内容的方式遵循 Property prompts,但使用后缀 。 |
|
从显式注释、JSR-303 验证注释或属性类型派生的 HTML 输入类型。 |
对于无法手动注释的类型,可以通过应用程序上下文中的 bean 注册自定义模式。HalFormsConfiguration
@Configuration
class CustomConfiguration {
@Bean
HalFormsConfiguration halFormsConfiguration() {
HalFormsConfiguration configuration = new HalFormsConfiguration();
configuration.registerPatternFor(CreditCardNumber.class, "[0-9]{16}");
}
}
此设置将导致 类型为的制图表达模型属性的 HAL-FORMS 模板属性声明值为 的字段。CreditCardNumber
regex
[0-9]{16}
定义属性的 HAL-FORMS 选项
对于其值应与某个值超集匹配的属性,HAL-FORMS 在属性定义中定义子文档。
某个属性的可用选项可以通过 获取指向类型属性的指针和 creator 函数将 a 转换为实例来描述。options
HalFormsConfiguration
withOptions(…)
PropertyMetadata
HalFormsOptions
@Configuration
class CustomConfiguration {
@Bean
HalFormsConfiguration halFormsConfiguration() {
HalFormsConfiguration configuration = new HalFormsConfiguration();
configuration.withOptions(Order.class, "shippingMethod" metadata ->
HalFormsOptions.inline("FedEx", "DHL"));
}
}
了解我们如何设置选项值以及为属性选择的选项。
或者,可以指向动态提供值的远程资源。
有关选项设置的更多约束,请参阅 spec 或 的 Javadoc。FedEx
DHL
Order.shippingMethod
HalFormsOptions.remote(…)
HalFormsOptions
4.2.2. 表单属性的国际化
HAL-FORMS 包含用于人工解释的属性,例如模板的标题或属性提示。
默认情况下,可以使用 Spring 的资源包支持和 Spring HATEOAS 配置的资源包来定义和国际化这些。rest-messages
模板标题
要定义模板标题,请使用以下模式:。请注意,在 HAL-FORMS 中,模板的名称是唯一的模板。
这意味着您通常必须使用功能描述的本地或完全限定的输入类型名称来限定键。_templates.$affordanceName.title
default
_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 | 使用 as 键的标题的全局定义。default |
2 | 使用实际提供名称作为键的游戏的全局定义。除非在创建功能时明确定义,否则默认为创建功能时指向的方法的名称。 |
3 | 要应用于名为 .Employee |
4 | 使用完全限定类型名称的标题定义。 |
使用实际提示名称的键优先于默认的键。 |
属性提示
属性提示也可以通过 Spring HATEOAS 自动配置的资源包来解决。
键可以是全局定义的、本地的或完全限定的,并且需要连接到实际的属性键:rest-messages
._prompt
email
firstName._prompt=Firstname (1)
Employee.firstName._prompt=Firstname (2)
com.acme.Employee.firstName._prompt=Firstname (3)
1 | 所有命名的属性都将呈现 “Firstname”,与它们声明的类型无关。firstName |
2 | 类型的 named 中的属性将提示 “Firstname”。firstName Employee |
3 | 属性 of 将获得分配 “Firstname” 的提示。firstName com.acme.Employee |
4.2.3. 一个完整的例子
让我们看一下一些示例代码,它结合了上述所有定义和自定义属性。
A for a customer 可能如下所示:RepresentationModel
class CustomerRepresentation
extends RepresentationModel<CustomerRepresentation> {
String name;
LocalDate birthdate; (1)
@Pattern(regex = "[0-9]{16}") String ccn; (2)
@Email String email; (3)
}
1 | 我们定义了一个 类型的属性。birthdate LocalDate |
2 | 我们希望遵守正则表达式。ccn |
3 | 我们使用 JSR-303 注解定义为一封电子邮件。email @Email |
请注意,此类型不是域类型。 它被有意设计为捕获各种可能无效的 input,以便可以立即拒绝字段的潜在错误值。
让我们继续看看控制器如何使用该模型:
@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 | 声明控制器方法使用上面定义的表示模型将请求正文绑定到 (如果 a 被发送到)。POST /customers |
2 | 准备模型的请求,添加指向该模型的链接,并在该链接上声明一个指向映射到的控制器方法的功能。
这将导致构建一个可供模型,该模型(取决于最终呈现的媒体类型)将被转换为特定于媒体类型的格式。GET /customers self POST |
接下来,让我们添加一些额外的元数据,使表单更易于人类访问:
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 |
如果客户端现在使用 的标头发出请求,则响应 HAL 文档将扩展为 HAL-FORMS 文档,以包含以下定义:GET
/customers
Accept
application/prs.hal-forms+json
_templates
{
…,
"_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 | 将公开名为 的模板。它的名称是因为它是唯一定义的模板,并且 spec 要求使用该名称。
如果附加了多个模板(通过声明其他功能),则每个模板都将以它们指向的方法命名。default default |
2 | 模板标题派生自 Resource Bundle 中定义的值。请注意,根据随请求发送的标头和可用性,可能会返回不同的值。Accept-Language |
3 | 该属性的值派生自获取功能的方法的映射。method |
4 | 属性的值派生自属性的 type 。
这同样适用于属性,但会导致 。type text String birthdate date |
5 | 该属性的提示和占位符也派生自资源包。ccn |
6 | 属性的声明作为模板属性的属性公开。@Pattern ccn regex |
7 | 属性上的注释已转换为相应的值。@Email email type |
HAL Explorer 会考虑 HAL-FORMS 模板,它会自动从这些描述中呈现 HTML 表单。
4.3. HTTP 问题详情
HTTP API 的问题详细信息是一种媒体类型,用于在 HTTP 响应中携带错误的机器可读详细信息,以避免需要为 HTTP API 定义新的错误响应格式。
HTTP 问题详细信息定义了一组 JSON 属性,这些属性包含其他信息,用于向 HTTP 客户端描述错误详细信息。 在 RFC 文档的相关部分中找到有关这些属性的更多详细信息。
你可以通过在 Spring MVC 控制器中使用媒体类型域类型来创建这样的 JSON 响应:Problem
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 | 自定义属性可以通过 或 explicit 对象添加(请参阅下文)。Map |
要将专用对象用于自定义属性,请声明一个类型,创建并填充该类型的实例,然后通过 或在创建实例时将其传递到实例中。Problem
….withProperties(…)
Problem.create(…)
class AccountDetails {
int balance;
List<URI> accounts;
}
problem.withProperties(result.getDetails());
// or
Problem.create(result.getDetails());
这将产生如下所示的响应:
{
"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
Collection+JSON 是一种基于 JSON 的读/写超媒体类型,旨在支持 简单集合的管理和查询。
集合 + JSON 规范
Collection+JSON 提供了一种统一的方式来表示单个项目资源和集合。 要启用此媒体类型,请在代码中放入以下配置:
@Configuration
@EnableHypermediaSupport(type = HypermediaType.COLLECTION_JSON)
public class CollectionJsonApplication {
}
此配置将使您的应用程序响应标头如下所示的请求。Accept
application/vnd.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 渲染一个 时,它将:
|
在渲染资源集合时,文档几乎相同,只是里面会有多个条目
JSON 数组,每个条目一个。items
更具体地说,Spring HATEOAS 将:
-
将整个集合的链接放入 top-level 属性中。
self
href
-
链接 (减号 ) 将放入顶级 .
CollectionModel
self
links
-
每个项级别都将包含集合中每个条目的相应链接。
href
self
CollectionModel.content
-
每个项级将包含 .
links
CollectionModel.content
4.5. UBER - 交换表示的统一基础
UBER 是一个实验性的 JSON 规范
UBER 文档格式是一种最小读/写超媒体类型,旨在支持简单的状态传输和临时 基于超媒体的过渡。
UBER 规格
UBER 提供了一种统一的方式来表示单个项目资源和集合。要启用此媒体类型,请在代码中放入以下配置:
@Configuration
@EnableHypermediaSupport(type = HypermediaType.UBER)
public class UberApplication {
}
此配置将使您的应用程序使用 header 响应请求,如下所示:Accept
application/vnd.amundsen-uber+json
{
"uber" : {
"version" : "1.0",
"data" : [ {
"rel" : [ "self" ],
"url" : "/employees/1"
}, {
"name" : "employee",
"data" : [ {
"name" : "role",
"value" : "ring bearer"
}, {
"name" : "name",
"value" : "Frodo"
} ]
} ]
}
}
这种媒体类型仍在开发中,规范本身也是如此。如果您在使用它时遇到问题,请随时打开一个票证。
UBER 媒体类型与拼车公司 Uber Technologies Inc. 没有任何关联。 |
4.6. ALPS - 应用程序级配置文件语义
ALPS 是一种介质类型,用于提供 有关其他资源的基于配置文件的元数据。
ALPS 文档可以用作配置文件 使用应用程序解释文档的应用程序语义 - 不可知的媒体类型(如 HTML、HAL、Collection+JSON、Siren、 等)。这提高了配置文件文档的可重用性 media 类型。
ALPS 规格
ALPS 不需要特殊激活。相反,你 “构建” 一条记录并从 Spring MVC 或 Spring WebFlux Web 方法返回它,如下所示:Alps
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()
此 fragment 插入了测试数据。它生成如下所示的 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
4.7. 基于社区的媒体类型
由于能够创建自己的媒体类型,因此有几项由社区主导的工作来构建其他媒体类型。
4.7.1. JSON:API
<dependency>
<groupId>com.toedter</groupId>
<artifactId>spring-hateoas-jsonapi</artifactId>
<version>{see project page for current version}</version>
</dependency>
implementation 'com.toedter:spring-hateoas-jsonapi:{see project page for current version}'
如果您需要快照发布,请访问项目页面了解更多详细信息。
4.7.2. 警笛
-
介质类型名称:
application/vnd.siren+json
-
项目负责人:Ingo Griebsch
<dependency>
<groupId>de.ingogriebsch.hateoas</groupId>
<artifactId>spring-hateoas-siren</artifactId>
<version>{see project page for current version}</version>
<scope>compile</scope>
</dependency>
implementation 'de.ingogriebsch.hateoas:spring-hateoas-siren:{see project page for current version}'
4.8. 注册自定义媒体类型
Spring HATEOAS 允许您通过 SPI 集成自定义媒体类型。 这种实现的构建块是:
-
某种形式的 Jackson 定制。在最简单的情况下,这是 Jackson 实现。
ObjectMapper
Module
-
一种实现,以便客户端支持能够检测表示中的链接。
LinkDiscoverer
-
少量的基础设施配置,将允许 Spring HATEOAS 找到自定义实现并选取它。
4.8.1. 自定义媒体类型配置
Spring HATEOAS 通过扫描应用程序上下文以查找接口的任何实现来选取自定义媒体类型实现。
每种媒体类型都必须实现此接口,以便:HypermediaMappingInformation
-
应用于
WebClient
、WebTestClient
或RestTemplate
实例。 -
支持从 Spring Web MVC 和 Spring WebFlux 控制器提供该媒体类型。
定义您自己的媒体类型可能看起来很简单,如下所示:
@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 | 它还声明了一个自定义实现,以提供进一步的客户端支持。LinkDiscoverer |
Jackson 模块通常为表示模型类型 、 、 和 声明和实现。
如果您需要进一步自定义 Jackson (如自定义 ),您也可以替代 覆盖 .Serializer
Deserializer
RepresentationModel
EntityModel
CollectionModel
PagedModel
ObjectMapper
HandlerInstantiator
configureObjectMapper(…)
以前版本的参考文档提到了实现接口并将其注册到 。
这不是必需的。
此 SPI 仅用于 Spring HATEOAS 提供的开箱即用的媒体类型。
只需实现接口并将其注册为 Spring bean 即可。 |
4.8.2. 建议
实现媒体类型表示的首选方法是提供与预期格式匹配的类型层次结构,并且可以由 Jackson 按原样序列化。
在注册的 和 实现中,将实例转换为特定于媒体类型的模型类型,然后在 Jackson 序列化器中查找这些实例。Serializer
Deserializer
RepresentationModel
默认情况下支持的媒体类型使用与第三方实现相同的配置机制。
因此,值得研究 mediatype
包中的实现。
请注意,内置的媒体类型实现保持其配置类 package 的私有性,因为它们是通过 .
自定义实现可能应该将这些 public 化,以确保用户可以从他们的应用程序包中导入这些配置类。@EnableHypermediaSupport