Java Spring HATEOAS嵌入式资源支持
我想使用HAL格式来包含RESTAPI。我在API中使用SpringHateoas,SpringHateoas似乎支持嵌入式资源;但是,没有关于如何使用此功能的文档或示例Java Spring HATEOAS嵌入式资源支持,java,spring,spring-hateoas,Java,Spring,Spring Hateoas,我想使用HAL格式来包含RESTAPI。我在API中使用SpringHateoas,SpringHateoas似乎支持嵌入式资源;但是,没有关于如何使用此功能的文档或示例 有人能举例说明如何使用Spring HATEOAS来包含嵌入式资源吗?Pre-HATEOAS 1.0.0M1:我找不到一个正式的方法来实现这一点……下面是我们所做的 public abstract class HALResource extends ResourceSupport { private final Ma
有人能举例说明如何使用Spring HATEOAS来包含嵌入式资源吗?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);
}
}
下面是我们发现的一个小例子。 首先,我们使用spring-hateoas-0.16 我们有
GET/profile
,它应该返回带有嵌入电子邮件列表的用户配置文件
我们有电子邮件资源
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@Relation(value = "email", collectionRelation = "emails")
public class EmailResource {
private final String email;
private final String type;
}
我们希望嵌入到个人资料响应中的两封电子邮件
Resource primary = new Resource(new Email("neo@matrix.net", "primary"));
Resource home = new Resource(new Email("t.anderson@matrix.net", "home"));
{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}
}
{
"_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" }
}
}]
}
}
{
"_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" }
}
}]
}
}
}
}
为了表明这些资源是嵌入式的,我们需要一个EmbeddedWrappers实例:
import org.springframework.hateoas.core.EmbeddedWrappers
EmbeddedWrappers wrappers = new EmbeddedWrappers(true);
在wrappers
的帮助下,我们可以为每封电子邮件创建EmbeddedWrapper
实例,并将它们放入列表中
List<EmbeddedWrapper> embeddeds = Arrays.asList(wrappers.wrap(primary), wrappers.wrap(home))
现在在回答中,我们将
{
"firstName": "Thomas",
"lastName": "Anderson",
"_links": {
"self": {
"href": "http://localhost:8080/profile"
}
},
"_embedded": {
"emails": [
{
"email": "neo@matrix.net",
"type": "primary"
},
{
"email": "t.anderson@matrix.net",
"type": "home"
}
]
}
}
使用嵌入的资源
的有趣之处在于,您可以将不同的资源放入其中,它将根据关系自动对它们进行分组。为此,我们使用org.springframework.hateoas.core
包中的注释@Relation
此外,HAL中还有一个关于嵌入式资源的问题,HATEOAS通常需要创建一个表示REST输出的POJO,并扩展HATEOAS提供的资源支持。可以在不创建额外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;
}
}
@RestController
类客户控制器{
列出客户名单;
公共CustomerController(){
客户=新的LinkedList();
添加(新客户(1,“彼得”,“测试”));
添加(新客户(2,“Peter”,“Test2”));
}
@RequestMapping(value=“/customers”,method=RequestMethod.GET,products=“application/hal+json”)
公共资源{
列表链接=新建链接列表();
add(linkTo(methodOn(CustomerController.class).getCustomers()).withSelfRel());
列表资源=customerToResource(customers.toArray(新客户[0]);
返回新资源(资源、链接);
}
@RequestMapping(value=“/customer/{id}”,method=RequestMethod.GET,products=“application/hal+json”)
公共资源getCustomer(@PathVariable int-id){
Link=linkTo(methodOn(CustomerController.class).getCustomer(id)).withSelfRel();
可选customer=customers.stream().filter(customer1->customer1.getId()==id.findFirst();
列表资源=customerToResource(customer.get());
返回新资源(资源、链接);
}
私有列表客户来源(客户…客户){
列表资源=新的ArrayList(customers.length);
用于(客户:客户){
Link selfLink=linkTo(methodOn(CustomerController.class).getCustomer(customer.getId()).withSelfRel();
添加(新资源(客户、自链接));
}
归还资源;
}
}
确保阅读Spring的,它有助于获得基本知识
核心开发人员指出了资源
、资源
和页面资源
的概念,这是文档中未涉及的基本概念
我花了一些时间来理解它是如何工作的,所以让我们通过一些示例来清楚地了解它
返回单个资源
资源
import org.springframework.hateoas.ResourceSupport;
public class ProductResource extends ResourceSupport{
final String name;
public ProductResource(String name) {
this.name = name;
}
}
控制器
...
Resources<EmbeddedWrapper> embeddedEmails = new Resources(embeddeds, linkTo(EmailAddressController.class).withSelfRel());
return ResponseEntity.ok(new Resource(new ProfileResource("Thomas", "Anderson", embeddedEmails), linkTo(ProfileController.class).withSelfRel()));
}
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);
}
}
@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);
}
返回多个资源
SpringHateOAS提供了嵌入式支持,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);
}
如果要更改键productResources
,则需要为资源添加注释:
@Relation(collectionRelation = "items")
class ProductResource ...
返回包含嵌入式资源的资源
这是你需要开始拉皮条的时候了。由穿着西装的@chris damour介绍的HALResource
public class OrderResource extends HalResource {
final float totalPrice;
public OrderResource(float totalPrice) {
this.totalPrice = totalPrice;
}
}
控制器
...
Resources<EmbeddedWrapper> embeddedEmails = new Resources(embeddeds, linkTo(EmailAddressController.class).withSelfRel());
return ResponseEntity.ok(new Resource(new ProfileResource("Thomas", "Anderson", embeddedEmails), linkTo(ProfileController.class).withSelfRel()));
}
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);
}
}
@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);
}
结合以上答案,我提出了一个更简单的方法:
return resWrapper(domainObj, embeddedRes(domainObj.getSettings(), "settings"))
这是一个自定义实用程序类(请参见下文)。注:
的第二个参数接受resWrapper
调用的embeddedRes
..
- 您可以创建另一个方法,在
中省略关系字符串resWrapper
的第一个参数是embeddedRes
,因此您还可以提供Object
ResourceSupport
- 表达式的结果属于扩展
资源的类型。因此,它将被所有Spring数据REST
处理。您可以创建它们的集合,还可以环绕ResourceProcessor
新资源()
import com.fasterxml.jackson.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);
}
}
Spring将提供一个构建器这就是我如何使用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"
}
}
}
封装所有这些字段的主类是
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;
}
}
和创建联系人资源:
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;
}
}
公共类ContactsResourceConverter{
公共静态联系人资源到资源(页面simpleContacts,长帐户ID){
List embeddeds=simpleContacts.stream().map(联系人->{
Link self=linkTo(methodOn(AccountController.class).getContactById(accountId,contact.getId())。
with selfrel();
返回新资源(联系人、自身);
}
).collect(Collectors.toList());
List listOfLinks=new ArrayList();
//自链接
Link selfLink=linkTo(methodOn(AccountController.class).getContactsForAccount(
帐户ID,
simpleContacts.getPageable().getPageSize(),
simpleContacts.getPageable().getPageNumber()+1))//+1,因为第一个索引为0
.with selfrel();
添加(自链接);
…另一个链接
资源=新资源(embe)
@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;
}
}
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;
}
}
return new ResponseEntity<>(ContactsResourceConverter.toResources(simpleContacts, accountId), HttpStatus.OK);
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
"_links": {
"next": {
"href": "http://localhost:8082/mbill/user/listUser?extra=ok&page=11"
}
}