4. 介质类型
4.1. HAL – 超文本应用语言
JSON 超文本应用程序语言或 HAL 是最简单的语言之一 以及最广泛采用的超媒体媒体类型,在不讨论特定的网络堆栈时采用。
这是 Spring HATEOAS 采用的第一个基于规范的介质类型。
4.1.1. 构建HAL表示模型
从 Spring HATEOAS 1.1 开始,我们发布了一个专用的HalModelBuilder允许创建RepresentationModel实例通过 HAL 惯用 API 进行。
以下是其基本假设:
-
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 | 我们通过提供目标链接来结束预览。它透明地添加到_links对象及其链接关系用作上一步中提供的对象的键。 |
| 5 | 可以添加其他对象以显示在_embedded.
列出它们的键派生自对象关系设置。它们可以通过以下方式进行定制@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 | 提供的预览对象。 |
| 4 | 通过显式添加的其他元素….embed(…). |
在 HAL 中_embedded也用于表示顶级集合。
它们通常分组在从对象类型派生的链接关系下。
即,订单列表在 HAL 中如下所示:
{
"_embedded" : {
"order : [
… (1)
]
}
}
| 1 | 个人订单文件请点击此处。 |
创建这样的表示非常简单:
Collection<Order> orders = …;
HalModelBuilder.emptyHalDocument()
.embed(orders);
也就是说,如果订单为空,则无法派生链接关系以显示在_embedded,以便在集合为空时文档将保持为空。
如果您更喜欢显式传达空集合,则可以将类型传递到….embed(…)方法Collection.
如果传递到方法中的集合为空,这将导致使用其从给定类型派生的链接关系呈现一个字段。
HalModelBuilder.emptyHalModel()
.embed(Collections.emptyList(), Order.class);
// or
.embed(Collections.emptyList(), LinkRelation.of("orders"));
将创建以下更明确的表示形式。
{
"_embedded" : {
"orders" : []
}
}
4.1.2. 配置链接渲染
在 HAL 中,_linksentry 是一个 JSON 对象。属性名称是链接关系和
每个值要么是链接对象,要么是链接对象的数组。
对于具有两个或多个链接的给定链接关系,规范在表示上是明确的:
{
"_links": {
"item": [
{ "href": "https://myhost/cart/42" },
{ "href": "https://myhost/inventory/12" }
]
},
"customer": "Dave Matthews"
}
但是,如果给定关系只有一个链接,则规范是模棱两可的。您可以将其渲染为单个对象 或作为单项数组。
默认情况下,Spring HATEOAS 使用最简洁的方法并呈现如下所示的单链接关系:
{
"_links": {
"item": { "href": "https://myhost/inventory/12" }
},
"customer": "Dave Matthews"
}
一些用户在使用 HAL 时不喜欢在数组和对象之间切换。他们更喜欢这种类型的渲染:
{
"_links": {
"item": [{ "href": "https://myhost/inventory/12" }]
},
"customer": "Dave Matthews"
}
如果您希望自定义此策略,您所要做的就是注入一个HalConfigurationbean 到您的应用程序配置中。
有多种选择。
@Bean
public HalConfiguration globalPolicy() {
return new HalConfiguration() //
.withRenderSingleLinks(RenderSingleLinks.AS_ARRAY); (1)
}
| 1 | 通过将所有单链接关系渲染为数组来覆盖 Spring HATEOAS 的默认值。 |
如果您只希望覆盖某些特定的链接关系,您可以创建一个HalConfigurationbean 像这样:
@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 链接序列化期间使用。
要定义链接的标题,请使用键模板_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 描述了注册链接和扩展链接关系类型。已注册的 rel 是在链接关系类型的 IANA 注册表中注册的已知字符串。外延relURI 可供不希望注册关系类型的应用程序使用。每个 URI 都是唯一标识关系类型的 URI。这relURI 可以序列化为紧凑 URI 或 Curie。例如,居里ex:persons代表链接关系类型example.com/rels/persons如果ex定义为example.com/rels/{rel}.如果使用 curies,则基本 URI 必须存在于响应范围内。
这rel默认创建的值RelProvider是扩展关系类型,因此必须是 URI,这可能会导致大量开销。这CurieProviderAPI 可以解决这个问题:它允许您将基本 URI 定义为 URI 模板,并定义代表该基本 URI 的前缀。如果CurieProvider存在,则RelProvider在前面添加所有rel带有居里前缀的值。此外,一个curies链接会自动添加到 HAL 资源中。
以下配置定义了默认居里提供程序:
@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:前缀自动出现在所有未向 IANA 注册的 rel 值之前,如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"
}
由于CurieProviderAPI 是允许自动创建居里,您只能定义一个CurieProvider每个应用程序范围的 bean。
4.2. HAL 形式
HAL-FORMS“看起来像 HAL”。但是,重要的是要记住,HAL-FORMS 与 HAL 不同——两者 不应以任何方式被视为可互换。
HAL-FORMS 规格
若要启用此媒体类型,请在代码中放置以下配置:
@Configuration
@EnableHypermediaSupport(type = HypermediaType.HAL_FORMS)
public class HalFormsApplication {
}
每当客户端提供Acceptheader 与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 允许通过塑造输入和输出类型的模型类型并使用注释来自定义它们。
每个模板都将定义以下属性:
| 属性 | 描述 |
|---|---|
|
服务器预期接收的介质类型。仅当指向的控制器方法公开 |
|
提交模板时要使用的 HTTP 方法。 |
|
要将表单提交到的目标 URI。仅当可供性目标与声明的链接不同时,才会呈现。 |
|
显示模板时人类可读的标题。 |
|
所有房产均需随表格提交(见下文)。 |
每个属性将获得以下定义的属性:
| 属性 | 描述 |
|---|---|
|
设置为 |
|
可以使用 JSR-303 进行定制 |
|
可以使用 JSR-303 进行定制 |
|
属性允许的最大值。源自 Derived from JSR-303 的 |
|
属性允许的最大长度值。派生自 Hibernate 验证器的 |
|
属性允许的最小值。源自 JSR-303 |
|
属性允许的最小长度值。派生自 Hibernate 验证器的 |
|
提交表单时要从中选择值的选项。有关详细信息,请参阅定义属性的 HAL-FORMS 选项。 |
|
呈现表单输入时要使用的用户可读提示。有关详细信息,请参阅属性提示。 |
|
用户可读的占位符,用于给出预期格式的示例。定义这些内容的方式遵循属性提示,但使用后缀 |
|
从显式 |
对于无法手动注释的类型,可以通过HalFormsConfigurationbean 存在于应用程序上下文中。
@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属性定义中的子文档。
可以通过以下方式描述特定属性的可用选项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(…)可以指向动态提供值的远程资源。
有关选项设置的更多限制,请参阅规范或 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 | 标题的全局定义default作为关键。 |
| 2 | 使用实际可供性名称作为键的标题的全局定义。除非在创建可供性时显式定义,否则默认为创建可供性时指向的方法的名称。 |
| 3 | 要应用于所有名为Employee. |
| 4 | 使用完全限定类型名称的标题定义。 |
| 使用实际可供性名称的键优先于默认的键。 |
属性提示
属性提示也可以通过rest-messages资源包由 Spring HATEOAS 自动配置。
这些键可以全局、本地或完全限定定义,并且需要一个._prompt连接到实际属性键:
email属性firstName._prompt=Firstname (1)
Employee.firstName._prompt=Firstname (2)
com.acme.Employee.firstName._prompt=Firstname (3)
| 1 | 所有名为firstName将呈现“Firstname”,与声明它们的类型无关。 |
| 2 | 这firstName名为Employee将提示“名字”。 |
| 3 | 这firstName属性com.acme.Employee将获得分配的“名字”提示。 |
4.2.3. 一个完整的例子
让我们看一些示例代码,它结合了上述所有定义和自定义属性。
一个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 | 我们定义email成为使用 JSR-303 的电子邮件@Email注解。 |
请注意,此类型不是域类型。 它旨在捕获各种潜在无效的输入,以便可以立即拒绝字段的潜在错误值。
让我们继续看看控制器如何使用该模型:
@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 | 控制器方法被声明为使用上面定义的表示模型将请求正文绑定到 ifPOST发给/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型。 |
如果客户端现在向GET请求/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因为它是定义的唯一模板,并且规范要求使用该名称。
如果附加了多个模板(通过声明其他可供性),则每个模板都将以它们所指向的方法命名。 |
| 2 | 模板标题派生自资源包中定义的值。请注意,根据Accept-Language与请求一起发送的标头,并且可用性可能会返回不同的值。 |
| 3 | 这method属性的值派生自可供性派生方法的映射。 |
| 4 | 这type属性的值text派生自属性的类型String.
这同样适用于birthdate属性,但会导致date. |
| 5 | 的提示符和占位符ccn属性也派生自资源包。 |
| 6 | 这@Pattern声明ccn属性公开为regex属性。 |
| 7 | 这@Email注释email属性已转换为相应的type价值。 |
HAL-FORMS 模板由 HAL Explorer 等考虑,它会自动根据这些描述呈现 HTML 表单。
4.3. HTTP 问题详细信息
HTTP API 的问题详细信息是一种媒体类型,用于在 HTTP 响应中携带机器可读的错误详细信息,以避免需要为 HTTP API 定义新的错误响应格式。
HTTP 问题详细信息定义了一组 JSON 属性,这些属性携带附加信息以向 HTTP 客户端描述错误详细信息。 有关这些属性的更多详细信息,特别是在 RFC 文档的相关部分。
您可以使用Problemmedia type 域类型:
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 的读/写超媒体类型,旨在支持 简单集合的管理和查询。
Collection+JSON 规范
集合+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 渲染
|
渲染资源集合时,文档几乎相同,只是里面会有多个条目
这itemsJSON 数组,每个条目一个。
Spring HATEOAS 更具体地说将:
-
将整个集合的
self链接到顶层href属性。 -
这
CollectionModel链接(减号self)将放入顶层links. -
每个项目级别
href将包含相应的self每个条目的链接CollectionModel.content收集。 -
每个项目级别
links将包含每个条目的所有其他链接CollectionModel.content.
4.5. 优步 - 交换陈述的统一依据
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、 等)。这提高了配置文件文档的可重用性 媒体类型。
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()以提取有关域对象属性的元数据。
此片段插入了测试数据。它生成如下所示的 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 | 配置类返回它支持的媒体类型。这适用于服务器端和客户端方案。 |
| 2 | 它覆盖getJacksonModule()提供自定义序列化程序来创建特定于媒体类型的表示形式。 |
| 3 | 它还声明了自定义LinkDiscoverer实现以进一步支持客户端。 |
Jackson 模块通常声明Serializer和Deserializer表示模型类型的实现RepresentationModel,EntityModel,CollectionModel和PagedModel.
如果您需要进一步定制JacksonObjectMapper(像风俗一样HandlerInstantiator),您也可以覆盖configureObjectMapper(…).
|
参考文档的先前版本提到实现 |
4.8.2. 建议
实现媒体类型表示的首选方法是提供与预期格式匹配的类型层次结构,并且可以由 Jackson 按原样序列化。
在Serializer和Deserializer注册的实现RepresentationModel,将实例转换为特定于媒体类型的模型类型,然后在 Jackson 序列化程序中查找这些实例。
默认情况下支持的媒体类型使用与第三方实现相同的配置机制。
因此,值得研究这mediatype包.
请注意,内置的媒体类型实现会将其配置类包保密,因为它们是通过@EnableHypermediaSupport.
自定义实现可能应该公开这些,以确保用户可以从他们的应用程序包中导入这些配置类。