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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 01:20:28  来源:igfitidea点击:

Spring HATEOAS embedded resource support

javaspringspring-hateoas

提问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 /profilethat 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 wrapperswe can create EmbeddedWrapperinstance 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 @JsonUnwrappedon 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> embeddedsis that you can put different resources in it and it will automatically group them by relations. For this we use annotation @Relationfrom org.springframework.hateoas.corepackage.

使用中有趣的部分Resources<EmbeddedWrapper> embeddeds是您可以将不同的资源放入其中,它会自动按关系对它们进行分组。为此,我们使用注解@Relationorg.springframework.hateoas.core包。

Also there is a good articleabout embedded resources in HAL

还有一篇关于 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, Resourcesand PagedResources, something essential which is is not covered by the documentation.

在这个答案中,核心开发人员指出了ResourceResources和的概念,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 Resourcesto 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 productResourcesyou 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 HALResourceintroduced 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 resWrapperaccepts ...of embeddedRescalls.
  • You may create another method that omits the relation String inside resWrapper.
  • First argument of embeddedResis Object, so you may also supply an instance of ResourceSupport
  • The result of the expression is of the type that extends Resource<DomainObjClass>. So, it will be processed by all Spring Data REST ResourceProcessor<Resource<DomainObjClass>>. You may create a collection of them and also wrap around new Resources<>().
  • 的第二个参数resWrapper接受...embeddedRes电话。
  • 您可以创建另一种省略内部关系 String 的方法resWrapper
  • 的第一个参数embeddedResis Object,因此您也可以提供一个实例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 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"
    }
}