4. 媒体类型
4.1. HAL – 超文本应用程序语言
JSON 超文本应用程序语言或 HAL 是最简单的语言之一 以及在不讨论特定 Web 堆栈时采用的最广泛采用的超媒体媒体类型。
这是 Spring HATEOAS 采用的第一个基于规范的媒体类型。
4.1.1. 构建 HAL 表示模型
从 Spring HATEOAS 1.1 开始,我们发布了专用的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();
1 | 我们设置了一些域类型。在本例中,是指与下达该订单的客户有关系的订单。 |
2 | 我们准备了一个指向资源的链接,该链接将公开客户详细信息 |
3 | 我们通过提供应该在_embeddable 第。 |
4 | 我们通过提供 target 链接来结束该预览。它以透明方式添加到_links Object 及其链接关系用作上一步中提供的对象的键。 |
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 中,订单列表将如下所示:
{
"_embedded" : {
"order : [
… (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 中,_links
entry 是一个 JSON 对象。属性名称是链接关系和
每个值要么是一个 Link 对象,要么是一个 Link 对象的数组。
对于具有两个或多个链接的给定链接关系,规范对表示形式很明确:
{
"_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"
}
如果您希望自定义此策略,只需注入一个HalConfiguration
bean 添加到应用程序配置中。
有多种选择。
@Bean
public HalConfiguration globalPolicy() {
return new HalConfiguration() //
.withRenderSingleLinks(RenderSingleLinks.AS_ARRAY); (1)
}
1 | 通过将 ALL 单链接关系渲染为数组来覆盖 Spring HATEOAS 的默认值。 |
如果您只想覆盖某些特定的链接关系,则可以创建一个HalConfiguration
bean 的 like this:
@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 . |
所有这些HalConfiguration
凋灵可以组合成一个全面的策略。请务必测试您的 API
广泛以避免意外。
4.1.3. 链接标题国际化
HAL 定义了一个title
属性。
可以使用 Spring 的资源包抽象和名为rest-messages
以便客户端可以直接在其 UI 中使用它们。
此捆绑包将自动设置,并在 HAL 链接序列化期间使用。
要定义链接的标题,请使用 key 模板_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. 使用CurieProvider
应用程序接口
Web 链接 RFC 描述了已注册和扩展链接关系类型。Registered rels 是在链接关系类型的 IANA 注册机构中注册的已知字符串。外延rel
URI 可以由不希望注册关系类型的应用程序使用。每个 URI 都是唯一标识关系类型的 URI。这rel
URI 可以序列化为压缩 URI 或 Curie。例如,curie 的ex:persons
代表 Link Relation Typeexample.com/rels/persons
如果ex
定义为example.com/rels/{rel}
.如果使用 curies,则响应范围中必须存在基 URI。
这rel
默认值RelProvider
是扩展关系类型,因此必须是 URI,这可能会导致大量开销。这CurieProvider
API 负责处理这个问题:它允许您将基本 URI 定义为 URI 模板和代表该基本 URI 的前缀。如果CurieProvider
存在时,RelProvider
全部前置rel
带有 curie 前缀的值。此外,一个curies
链接会自动添加到 HAL 资源中。
以下配置定义了默认 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}"));
}
}
请注意,现在ex:
prefix 自动出现在所有未向 IANA 注册的 rel 值之前,如ex:orders
.客户端可以使用curies
链接将 curie 解析为其完整形式。
以下示例显示了如何执行此作:
{
"_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"
}
由于CurieProvider
API 是允许自动创建 curie,您只能定义一个CurieProvider
每个应用程序范围的 bean。
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 以使用这些额外的元数据来增强您的控制器。
至于单项 (EntityModel
) 和聚合根集合 (CollectionModel
),Spring HATEOAS 会渲染它们
与 HAL 文档相同。
4.2.1. 定义 HAL-FORMS 元数据
HAL-FORMS 允许描述每个表单字段的标准。 Spring HATEOAS 允许通过为 Importing 和 Output 类型塑造模型类型并在其上使用 Comments 来自定义它们。
每个模板都将定义以下属性:
属性 | 描述 |
---|---|
|
服务器预期接收的媒体类型。仅当控制器方法指向公开 |
|
提交模板时使用的 HTTP 方法。 |
|
要将表单提交到的目标 URI。仅当提供目标与声明它时所在的链接不同时,才会呈现。 |
|
显示模板时的人类可读标题。 |
|
所有属性都要随表格一起提交(见下文)。 |
每个属性都将定义以下属性:
属性 | 描述 |
---|---|
|
设置为 |
|
可以使用 JSR-303 的 |
|
可以使用 JSR-303 的 |
|
属性允许的最大值。派生自 JSR-303 的 |
|
属性允许的最大长度值。源自 Hibernate Validator 的 |
|
属性允许的最小值。源自 JSR-303 的 |
|
属性允许的最小长度值。源自 Hibernate Validator 的 |
|
提交表单时要从中选择值的选项。有关详细信息,请参阅定义属性的 HAL-FORMS 选项。 |
|
渲染表单输入时使用的用户可读提示。有关详细信息,请参阅 属性提示。 |
|
用户可读的占位符,用于为预期格式提供示例。定义这些内容的方式遵循 Property 提示符,但使用后缀 |
|
从显式 |
对于无法手动注释的类型,您可以通过HalFormsConfiguration
bean 存在于应用程序上下文中。
@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 定义options
属性定义中的 sub-document 的 sub-document 中。
特定属性的可用选项可以通过以下方式进行描述HalFormsConfiguration
的withOptions(…)
将指针指向类型的属性并使用 Creator 函数将PropertyMetadata
转换为HalFormsOptions
实例。
@Configuration
class CustomConfiguration {
@Bean
HalFormsConfiguration halFormsConfiguration() {
HalFormsConfiguration configuration = new HalFormsConfiguration();
configuration.withOptions(Order.class, "shippingMethod" metadata ->
HalFormsOptions.inline("FedEx", "DHL"));
}
}
了解我们如何设置选项值FedEx
和DHL
作为Order.shippingMethod
财产。
或者HalFormsOptions.remote(…)
可以指向动态提供值的远程资源。
有关选项设置的更多约束,请参阅 spec 或 JavadocHalFormsOptions
.
4.2.2. 表单属性的国际化
HAL-FORMS 包含用于人工解释的属性,例如模板的标题或属性提示。
这些可以使用 Spring 的资源包支持和rest-messages
由 Spring HATEOAS 默认配置的资源包。
模板标题
要定义模板标题,请使用以下模式:_templates.$affordanceName.title
.请注意,在 HAL-FORMS 中,模板的名称为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 | 标题的全局定义 usingdefault 作为键。 |
2 | 使用实际提供名称作为键的游戏的全局定义。除非在创建功能时明确定义,否则默认为创建功能时指向的方法的名称。 |
3 | 本地定义的标题,将应用于名为Employee . |
4 | 使用完全限定类型名称的标题定义。 |
使用实际提示名称的键优先于默认的键。 |
属性提示
属性提示也可以通过rest-messages
由 Spring HATEOAS 自动配置的资源包。
键可以全局定义、本地定义或完全限定,并且需要一个._prompt
连接到 actual 属性键:
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. 一个完整的例子
让我们看一下一些示例代码,它结合了上述所有定义和自定义属性。
一个RepresentationModel
for a customer 可能如下所示:
class CustomerRepresentation
extends RepresentationModel<CustomerRepresentation> {
String name;
LocalDate birthdate; (1)
@Pattern(regex = "[0-9]{16}") String ccn; (2)
@Email String email; (3)
}
1 | 我们定义了一个birthdate type 为LocalDate . |
2 | 我们期望ccn 以遵循正则表达式。 |
3 | 我们定义email 作为使用 JSR-303 的电子邮件@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 | 声明控制器方法以使用上面定义的表示模型将请求正文绑定到POST 颁发给/customers . |
2 | 一个GET request 添加到/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 型。 |
如果客户端现在发出GET
request 添加到/customers
使用Accept
的标头application/prs.hal-forms+json
,响应 HAL 文档将扩展为 HAL-FORMS 文档,以包括以下内容_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 | 名为default 已公开。它的名字是default 因为它是定义的唯一模板,并且 spec 要求使用该名称。
如果附加了多个模板(通过声明其他功能),则每个模板都将以它们指向的方法命名。 |
2 | 模板标题派生自 Resource Bundle 中定义的值。请注意,根据Accept-Language 标头和可用性不同的值可能会返回。 |
3 | 这method attribute 的值派生自获取功能的方法的映射。 |
4 | 这type 属性的值text 派生自属性的类型String .
这同样适用于birthdate 属性,但会导致date . |
5 | 的ccn property 也派生自资源包。 |
6 | 这@Pattern 声明ccn 属性公开为regex template 属性的属性。 |
7 | 这@Email 注解email property 已转换为相应的type 价值。 |
HAL Explorer 会考虑 HAL-FORMS 模板,它会自动从这些描述中呈现 HTML 表单。
4.3. HTTP 问题详情
HTTP API 的问题详细信息是一种媒体类型,用于在 HTTP 响应中携带错误的机器可读详细信息,以避免需要为 HTTP API 定义新的错误响应格式。
HTTP 问题详细信息定义了一组 JSON 属性,这些属性包含其他信息,用于向 HTTP 客户端描述错误详细信息。 在 RFC 文档的相关部分中找到有关这些属性的更多详细信息。
您可以使用Problem
media 类型的域类型:
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(…)
.
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 渲染
|
在渲染资源集合时,文档几乎相同,只是里面会有多个条目
这items
JSON 数组,每个条目一个。
更具体地说,Spring HATEOAS 将:
-
将整个集合的
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 {
}
此配置将使您的应用程序使用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 不需要特殊激活。相反,您 “构建” 了一个Alps
从 Spring MVC 或 Spring WebFlux Web 方法记录并返回它,如下所示:
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 对象属性的元数据。
此 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
ObjectMapper
定制。在最简单的例子中,那就是 JacksonModule
实现。 -
一个
LinkDiscoverer
实现,以便客户端支持能够检测表示中的链接。 -
少量的基础设施配置,将允许 Spring HATEOAS 找到自定义实现并选取它。
4.8.1. 自定义媒体类型配置
Spring HATEOAS 通过扫描应用程序上下文以查找HypermediaMappingInformation
接口。
每种媒体类型都必须实现此接口,以便:
-
支持从 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 implementation 以获得进一步的客户端支持。 |
Jackson 模块通常声明Serializer
和Deserializer
制图表达模型类型的实现RepresentationModel
,EntityModel
,CollectionModel
和PagedModel
.
如果您需要进一步定制 JacksonObjectMapper
(就像一个自定义HandlerInstantiator
),您也可以覆盖configureObjectMapper(…)
.
以前版本的参考文档提到了实现 |
4.8.2. 建议
实现媒体类型表示的首选方法是提供与预期格式匹配的类型层次结构,并且可以由 Jackson 按原样序列化。
在Serializer
和Deserializer
注册的 implementationsRepresentationModel
,将实例转换为特定于媒体类型的模型类型,然后在 Jackson 序列化器中查找这些类型。
默认情况下支持的媒体类型使用与第三方实现相同的配置机制。
因此,值得研究这mediatype
包.
请注意,内置的 media type 实现保持其配置类 package 的私有性,因为它们是通过@EnableHypermediaSupport
.
自定义实现可能应该将这些 public 化,以确保用户可以从他们的应用程序包中导入这些配置类。