Java Spring HATEOAS 嵌入式资源支持
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25858698/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring HATEOAS embedded resource support
提问by Glide
I want to use the HAL format for my REST API to include embedded resources. I'm using Spring HATEOAS for my APIs and Spring HATEOAS seems to support embedded resources; however, there's no documentation or example on how to use this.
我想对我的 REST API 使用 HAL 格式以包含嵌入式资源。我为我的 API 使用 Spring HATEOAS,并且 Spring HATEOAS 似乎支持嵌入式资源;但是,没有关于如何使用它的文档或示例。
Can someone provide an example how to use Spring HATEOAS to include embedded resources?
有人可以提供一个如何使用 Spring HATEOAS 来包含嵌入式资源的示例吗?
回答by Chris DaMour
Pre HATEOAS 1.0.0M1: I couldn't find an official way to do this...here's what we did
Pre HATEOAS 1.0.0M1:我找不到官方的方法来做到这一点......这就是我们所做的
public abstract class HALResource extends ResourceSupport {
private final Map<String, ResourceSupport> embedded = new HashMap<String, ResourceSupport>();
@JsonInclude(Include.NON_EMPTY)
@JsonProperty("_embedded")
public Map<String, ResourceSupport> getEmbeddedResources() {
return embedded;
}
public void embedResource(String relationship, ResourceSupport resource) {
embedded.put(relationship, resource);
}
}
then made our resources extend HALResource
然后让我们的资源扩展 HALResource
UPDATE: in HATEOAS 1.0.0M1 the EntityModel (and really anything extending RepresentationalModel) this is natively supported now as long as the embedded resource is exposed via a getContent (or however you make Hymanson serialize a content property). like:
更新:在 HATEOAS 1.0.0M1 中,只要通过 getContent 公开嵌入的资源(或者您让 Hymanson 序列化内容属性),现在本机支持 EntityModel(以及任何扩展 RepresentationalModel 的内容)。喜欢:
public class Result extends RepresentationalModel<Result> {
private final List<Object> content;
public Result(
List<Object> content
){
this.content = content;
}
public List<Object> getContent() {
return content;
}
};
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
List<Object> elements = new ArrayList<>();
elements.add(wrappers.wrap(new Product("Product1a"), LinkRelation.of("all")));
elements.add(wrappers.wrap(new Product("Product2a"), LinkRelation.of("purchased")));
elements.add(wrappers.wrap(new Product("Product1b"), LinkRelation.of("all")));
return new Result(elements);
you'll get
你会得到
{
_embedded: {
purchased: {
name: "Product2a"
},
all: [
{
name: "Product1a"
},
{
name: "Product1b"
}
]
}
}
回答by Dmitriy Mayboroda
here is a small example what we've found. First of all we use spring-hateoas-0.16
这是我们发现的一个小例子。首先我们使用 spring-hateoas-0.16
Imaging we have GET /profile
that should return user profile with embedded emails list.
我们拥有的图像GET /profile
应该返回带有嵌入式电子邮件列表的用户配置文件。
We have email resource.
我们有电子邮件资源。
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Relation(value = "email", collectionRelation = "emails")
public class EmailResource {
private final String email;
private final String type;
}
two emails that we want to embedded into profile response
我们想要嵌入到个人资料回复中的两封电子邮件
Resource primary = new Resource(new Email("[email protected]", "primary"));
Resource home = new Resource(new Email("[email protected]", "home"));
To indicate that these resources are embedded we need an instance of EmbeddedWrappers:
为了表明这些资源是嵌入的,我们需要一个 EmbeddedWrappers 的实例:
import org.springframework.hateoas.core.EmbeddedWrappers
EmbeddedWrappers wrappers = new EmbeddedWrappers(true);
With the help of wrappers
we can create EmbeddedWrapper
instance for each email and put them into a list.
在wrappers
我们的帮助下,我们可以EmbeddedWrapper
为每封电子邮件创建实例并将它们放入一个列表中。
List<EmbeddedWrapper> embeddeds = Arrays.asList(wrappers.wrap(primary), wrappers.wrap(home))
The only thing is left to do is to construct our profile resource with these embeddeds. In the example below I use lombok to short the code.
剩下要做的就是用这些嵌入构建我们的配置文件资源。在下面的示例中,我使用 lombok 来缩短代码。
@Data
@Relation(value = "profile")
public class ProfileResource {
private final String firstName;
private final String lastName;
@JsonUnwrapped
private final Resources<EmbeddedWrapper> embeddeds;
}
Keep in mind annotation @JsonUnwrapped
on embeddeds field
记住@JsonUnwrapped
嵌入字段的注释
And we are ready to return all this from controller
我们准备好从控制器返回所有这些
...
Resources<EmbeddedWrapper> embeddedEmails = new Resources(embeddeds, linkTo(EmailAddressController.class).withSelfRel());
return ResponseEntity.ok(new Resource(new ProfileResource("Thomas", "Anderson", embeddedEmails), linkTo(ProfileController.class).withSelfRel()));
}
Now in the response we'll have
现在在响应中我们将有
{
"firstName": "Thomas",
"lastName": "Anderson",
"_links": {
"self": {
"href": "http://localhost:8080/profile"
}
},
"_embedded": {
"emails": [
{
"email": "[email protected]",
"type": "primary"
},
{
"email": "[email protected]",
"type": "home"
}
]
}
}
Interesting part in using Resources<EmbeddedWrapper> embeddeds
is that you can put different resources in it and it will automatically group them by relations. For this we use annotation @Relation
from org.springframework.hateoas.core
package.
使用中有趣的部分Resources<EmbeddedWrapper> embeddeds
是您可以将不同的资源放入其中,它会自动按关系对它们进行分组。为此,我们使用注解@Relation
从org.springframework.hateoas.core
包。
Also there is a good articleabout embedded resources in HAL
回答by Peter Szanto
Usually HATEOAS requires to create a POJO that represents the REST output and extends HATEOAS provided ResourceSupport. It is possible do this without creating the extra POJO and use the Resource, Resources and Link classes directly as shown in the code below :
通常 HATEOAS 需要创建一个代表 REST 输出的 POJO,并扩展 HATEOAS 提供的 ResourceSupport。可以在不创建额外 POJO 的情况下直接使用 Resource、Resources 和 Link 类,如下面的代码所示:
@RestController
class CustomerController {
List<Customer> customers;
public CustomerController() {
customers = new LinkedList<>();
customers.add(new Customer(1, "Peter", "Test"));
customers.add(new Customer(2, "Peter", "Test2"));
}
@RequestMapping(value = "/customers", method = RequestMethod.GET, produces = "application/hal+json")
public Resources<Resource> getCustomers() {
List<Link> links = new LinkedList<>();
links.add(linkTo(methodOn(CustomerController.class).getCustomers()).withSelfRel());
List<Resource> resources = customerToResource(customers.toArray(new Customer[0]));
return new Resources<>(resources, links);
}
@RequestMapping(value = "/customer/{id}", method = RequestMethod.GET, produces = "application/hal+json")
public Resources<Resource> getCustomer(@PathVariable int id) {
Link link = linkTo(methodOn(CustomerController.class).getCustomer(id)).withSelfRel();
Optional<Customer> customer = customers.stream().filter(customer1 -> customer1.getId() == id).findFirst();
List<Resource> resources = customerToResource(customer.get());
return new Resources<Resource>(resources, link);
}
private List<Resource> customerToResource(Customer... customers) {
List<Resource> resources = new ArrayList<>(customers.length);
for (Customer customer : customers) {
Link selfLink = linkTo(methodOn(CustomerController.class).getCustomer(customer.getId())).withSelfRel();
resources.add(new Resource<Customer>(customer, selfLink));
}
return resources;
}
}
回答by linqu
Make sure to read Spring's documentation about HATEOAS, it helps to get the basics.
请务必阅读 Spring关于 HATEOAS的文档,它有助于了解基础知识。
In this answera core developer points out the concept of Resource
, Resources
and PagedResources
, something essential which is is not covered by the documentation.
在这个答案中,核心开发人员指出了Resource
、Resources
和的概念,PagedResources
这是文档中未涵盖的重要内容。
It took me some time to understand how it works, so let's step through some examples to make it crystal-clear.
我花了一些时间来理解它是如何工作的,所以让我们逐步通过一些示例使其清晰。
Returning a Single Resource
返回单个资源
the resource
资源
import org.springframework.hateoas.ResourceSupport;
public class ProductResource extends ResourceSupport{
final String name;
public ProductResource(String name) {
this.name = name;
}
}
the controller
控制器
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("products/{id}", method = RequestMethod.GET)
ResponseEntity<Resource<ProductResource>> get(@PathVariable Long id) {
ProductResource productResource = new ProductResource("Apfelstrudel");
Resource<ProductResource> resource = new Resource<>(productResource, new Link("http://example.com/products/1"));
return ResponseEntity.ok(resource);
}
}
the response
响应
{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}
}
Returning Multiple Resources
返回多个资源
Spring HATEOAS comes with embedded support, which is used by Resources
to reflect a response with multiple resources.
Spring HATEOAS 带有嵌入式支持,用于Resources
反映具有多个资源的响应。
@RequestMapping("products/", method = RequestMethod.GET)
ResponseEntity<Resources<Resource<ProductResource>>> getAll() {
ProductResource p1 = new ProductResource("Apfelstrudel");
ProductResource p2 = new ProductResource("Schnitzel");
Resource<ProductResource> r1 = new Resource<>(p1, new Link("http://example.com/products/1"));
Resource<ProductResource> r2 = new Resource<>(p2, new Link("http://example.com/products/2"));
Link link = new Link("http://example.com/products/");
Resources<Resource<ProductResource>> resources = new Resources<>(Arrays.asList(r1, r2), link);
return ResponseEntity.ok(resources);
}
the response
响应
{
"_links": {
"self": { "href": "http://example.com/products/" }
},
"_embedded": {
"productResources": [{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}, {
"name": "Schnitzel",
"_links": {
"self": { "href": "http://example.com/products/2" }
}
}]
}
}
If you want to change the key productResources
you need to annotate your resource:
如果要更改密钥productResources
,则需要对资源进行注释:
@Relation(collectionRelation = "items")
class ProductResource ...
Returning a Resource with Embedded Resources
返回带有嵌入资源的资源
This is when you need to start to pimp Spring. The HALResource
introduced by @chris-damour in another answersuits perfectly.
这是你需要开始拉皮条春天的时候。在HALResource
通过@克里斯-达摩在推出另一个答案完全适合。
public class OrderResource extends HalResource {
final float totalPrice;
public OrderResource(float totalPrice) {
this.totalPrice = totalPrice;
}
}
the controller
控制器
@RequestMapping(name = "orders/{id}", method = RequestMethod.GET)
ResponseEntity<OrderResource> getOrder(@PathVariable Long id) {
ProductResource p1 = new ProductResource("Apfelstrudel");
ProductResource p2 = new ProductResource("Schnitzel");
Resource<ProductResource> r1 = new Resource<>(p1, new Link("http://example.com/products/1"));
Resource<ProductResource> r2 = new Resource<>(p2, new Link("http://example.com/products/2"));
Link link = new Link("http://example.com/order/1/products/");
OrderResource resource = new OrderResource(12.34f);
resource.add(new Link("http://example.com/orders/1"));
resource.embed("products", new Resources<>(Arrays.asList(r1, r2), link));
return ResponseEntity.ok(resource);
}
the response
响应
{
"_links": {
"self": { "href": "http://example.com/products/1" }
},
"totalPrice": 12.34,
"_embedded": {
"products": {
"_links": {
"self": { "href": "http://example.com/orders/1/products/" }
},
"_embedded": {
"items": [{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}, {
"name": "Schnitzel",
"_links": {
"self": { "href": "http://example.com/products/2" }
}
}]
}
}
}
}
回答by Sam
Combining the answers above I've made a much easier approach:
结合上面的答案,我做了一个更简单的方法:
return resWrapper(domainObj, embeddedRes(domainObj.getSettings(), "settings"))
This is a custom utility class (see below). Note:
这是一个自定义实用程序类(见下文)。笔记:
- Second argument of
resWrapper
accepts...
ofembeddedRes
calls. - You may create another method that omits the relation String inside
resWrapper
. - First argument of
embeddedRes
isObject
, so you may also supply an instance ofResourceSupport
- The result of the expression is of the type that extends
Resource<DomainObjClass>
. So, it will be processed by all Spring Data RESTResourceProcessor<Resource<DomainObjClass>>
. You may create a collection of them and also wrap aroundnew Resources<>()
.
- 的第二个参数
resWrapper
接受...
的embeddedRes
电话。 - 您可以创建另一种省略内部关系 String 的方法
resWrapper
。 - 的第一个参数
embeddedRes
isObject
,因此您也可以提供一个实例ResourceSupport
- 表达式的结果属于 extends 类型
Resource<DomainObjClass>
。因此,它将由所有 Spring Data REST 处理ResourceProcessor<Resource<DomainObjClass>>
。您可以创建它们的集合并环绕new Resources<>()
.
Create the utility class:
创建实用程序类:
import com.fasterxml.Hymanson.annotation.JsonUnwrapped;
import java.util.Arrays;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.core.EmbeddedWrapper;
import org.springframework.hateoas.core.EmbeddedWrappers;
public class ResourceWithEmbeddable<T> extends Resource<T> {
@SuppressWarnings("FieldCanBeLocal")
@JsonUnwrapped
private Resources<EmbeddedWrapper> wrappers;
private ResourceWithEmbeddable(final T content, final Iterable<EmbeddedWrapper> wrappers, final Link... links) {
super(content, links);
this.wrappers = new Resources<>(wrappers);
}
public static <T> ResourceWithEmbeddable<T> resWrapper(final T content,
final EmbeddedWrapper... wrappers) {
return new ResourceWithEmbeddable<>(content, Arrays.asList(wrappers));
}
public static EmbeddedWrapper embeddedRes(final Object source, final String rel) {
return new EmbeddedWrappers(false).wrap(source, rel);
}
}
You only need to include import static package.ResourceWithEmbeddable.*
to your service class to use it.
您只需要包含import static package.ResourceWithEmbeddable.*
到您的服务类中即可使用它。
JSON looks like this:
JSON 看起来像这样:
{
"myField1": "1field",
"myField2": "2field",
"_embedded": {
"settings": [
{
"settingName": "mySetting",
"value": "1337",
"description": "umh"
},
{
"settingName": "other",
"value": "1488",
"description": "a"
},...
]
}
}
回答by Sam
Spring will provide a builder https://github.com/spring-projects/spring-hateoas/issues/864
Spring 将提供一个构建器https://github.com/spring-projects/spring-hateoas/issues/864
回答by Eugene
This is how I've built such json with spring-boot-starter-hateoas 2.1.1:
这就是我使用 spring-boot-starter-hateoas 2.1.1 构建此类 json 的方式:
{
"total": 2,
"count": 2,
"_embedded": {
"contacts": [
{
"id": "1-1CW-303",
"role": "ASP",
"_links": {
"self": {
"href": "http://localhost:8080/accounts/2700098669/contacts/1-1CW-303"
}
}
},
{
"id": "1-1D0-267",
"role": "HSP",
"_links": {
"self": {
"href": "http://localhost:8080/accounts/2700098669/contacts/1-1D0-267"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/accounts/2700098669/contacts?limit=2&page=1"
},
"first": {
"href": "http://localhost:8080/accounts/2700098669/contacts?limit=2&page=1"
},
"last": {
"href": "http://localhost:8080/accounts/2700098669/contacts?limit=2&page=1"
}
}
}
The main class that encapsulates all this fields is
封装所有这些字段的主类是
public class ContactsResource extends ResourceSupport{
private long count;
private long total;
private final Resources<Resource<SimpleContact>> contacts;
public long getTotal() {
return total;
}
public ContactsResource(long total, long count, Resources<Resource<SimpleContact>> contacts){
this.contacts = contacts;
this.total = total;
this.count = count;
}
public long getCount() {
return count;
}
@JsonUnwrapped
public Resources<Resource<SimpleContact>> getContacts() {
return contacts;
}
}
SimpleContact has info about single contact and it's just pojo
SimpleContact 有关于单个联系人的信息,它只是 pojo
@Relation(value = "contact", collectionRelation = "contacts")
public class SimpleContact {
private String id;
private String role;
public String getId() {
return id;
}
public SimpleContact id(String id) {
this.id = id;
return this;
}
public String getRole() {
return role;
}
public SimpleContact role(String role) {
this.role = role;
return this;
}
}
And creating ContactsResource:
并创建 ContactsResource:
public class ContactsResourceConverter {
public static ContactsResource toResources(Page<SimpleContact> simpleContacts, Long accountId){
List<Resource<SimpleContact>> embeddeds = simpleContacts.stream().map(contact -> {
Link self = linkTo(methodOn(AccountController.class).getContactById(accountId, contact.getId())).
withSelfRel();
return new Resource<>(contact, self);
}
).collect(Collectors.toList());
List<Link> listOfLinks = new ArrayList<>();
//self link
Link selfLink = linkTo(methodOn(AccountController.class).getContactsForAccount(
accountId,
simpleContacts.getPageable().getPageSize(),
simpleContacts.getPageable().getPageNumber() + 1)) // +1 because of 0 first index
.withSelfRel();
listOfLinks.add(selfLink);
... another links
Resources<Resource<SimpleContact>> resources = new Resources<>(embeddeds);
ContactsResource contactsResource = new ContactsResource(simpleContacts.getTotalElements(), simpleContacts.getNumberOfElements(), resources);
contactsResource.add(listOfLinks);
return contactsResource;
}
}
And I'm just calling this in this way from controller:
我只是从控制器以这种方式调用它:
return new ResponseEntity<>(ContactsResourceConverter.toResources(simpleContacts, accountId), HttpStatus.OK);
回答by aditya lath
Add this dependency in your pom. Check this link: https://www.baeldung.com/spring-rest-hal
在您的 pom.xml 文件中添加此依赖项。检查此链接:https: //www.baeldung.com/spring-rest-hal
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
It will change your response like this.
它会像这样改变你的反应。
"_links": {
"next": {
"href": "http://localhost:8082/mbill/user/listUser?extra=ok&page=11"
}
}