resource example java spring spring-hateoas

java - resource - spring hateoas example



Soporte de recursos integrados Spring HATEOAS (5)

Al combinar las respuestas anteriores, hice un enfoque mucho más sencillo:

return resWrapper(domainObj, embeddedRes(domainObj.getSettings(), "settings"))

Esta es una clase de utilidad personalizada (ver a continuación). Nota:

  • El segundo argumento de resWrapper acepta ... de llamadas embeddedRes .
  • Puede crear otro método que omita la relación String dentro de resWrapper .
  • El primer argumento de embeddedRes es Object , por lo que también puede proporcionar una instancia de ResourceSupport
  • El resultado de la expresión es del tipo que extiende Resource<DomainObjClass> . Por lo tanto, será procesado por todos los Spring Data REST ResourceProcessor<Resource<DomainObjClass>> . Puede crear una colección de ellos y también incluir new Resources<>() .

Crea la clase de utilidad:

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); } }

Solo necesita incluir import static package.ResourceWithEmbeddable.* su clase de servicio para usarlo.

JSON se ve así:

{ "myField1": "1field", "myField2": "2field", "_embedded": { "settings": [ { "settingName": "mySetting", "value": "1337", "description": "umh" }, { "settingName": "other", "value": "1488", "description": "a" },... ] } }

Quiero usar el formato HAL para que mi API REST incluya recursos incorporados . Estoy usando Spring HATEOAS para mis API y Spring HATEOAS parece ser compatible con recursos integrados; sin embargo, no hay documentación o ejemplos sobre cómo usar esto.

¿Alguien puede dar un ejemplo de cómo usar Spring HATEOAS para incluir recursos incrustados?


Asegúrese de leer la documentación de Spring sobre HATEOAS , ayuda a obtener los conceptos básicos.

En esta respuesta, un desarrollador principal señala el concepto de Resource , Resources y PagedResources , algo esencial que no está cubierto por la documentación.

Me tomó algo de tiempo entender cómo funciona, así que veamos algunos ejemplos para dejarlo claro.

Devolviendo un único recurso

el recurso

import org.springframework.hateoas.ResourceSupport; public class ProductResource extends ResourceSupport{ final String name; public ProductResource(String name) { this.name = name; } }

el controlador

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); } }

la respuesta

{ "name": "Apfelstrudel", "_links": { "self": { "href": "http://example.com/products/1" } } }

Devolución de múltiples recursos

Spring HATEOAS viene con soporte incrustado, que es utilizado por Resources para reflejar una respuesta con múltiples recursos.

@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); }

la respuesta

{ "_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" } } }] } }

Si desea cambiar el producto clave productResources , debe anotar su recurso:

@Relation(collectionRelation = "items") class ProductResource ...

Devolución de un recurso con recursos integrados

Aquí es cuando necesitas comenzar a proxeneta Spring. El HALResource presentado por @ chris-damour en otra respuesta se adapta perfectamente.

public class OrderResource extends HalResource { final float totalPrice; public OrderResource(float totalPrice) { this.totalPrice = totalPrice; } }

el controlador

@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); }

la respuesta

{ "_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" } } }] } } } }


No pude encontrar una manera oficial de hacer esto ... esto es lo que hicimos

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); } }

luego hizo que nuestros recursos amplíen HALResource


Por lo general, HATEOAS requiere crear un POJO que represente la salida REST y amplíe HATEOAS provisto ResourceSupport. Es posible hacer esto sin crear el POJO adicional y usar las clases de Recursos, Recursos y Vínculo directamente como se muestra en el siguiente código:

@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; } }


aquí hay un pequeño ejemplo de lo que hemos encontrado. Antes que nada, usamos spring-hateoas-0.16

Imaging tenemos GET /profile que debe devolver el perfil del usuario con la lista de correos electrónicos incrustados.

Tenemos un recurso de correo electrónico.

@Data @JsonIgnoreProperties(ignoreUnknown = true) @Relation(value = "email", collectionRelation = "emails") public class EmailResource { private final String email; private final String type; }

dos correos electrónicos que queremos incrustar en la respuesta del perfil

Resource primary = new Resource(new Email("[email protected]", "primary")); Resource home = new Resource(new Email("[email protected]", "home"));

Para indicar que estos recursos están integrados, necesitamos una instancia de EmbeddedWrappers:

import org.springframework.hateoas.core.EmbeddedWrappers EmbeddedWrappers wrappers = new EmbeddedWrappers(true);

Con la ayuda de los wrappers , podemos crear una instancia de EmbeddedWrapper para cada correo electrónico y ponerlos en una lista.

List<EmbeddedWrapper> embeddeds = Arrays.asList(wrappers.wrap(primary), wrappers.wrap(home))

Lo único que queda por hacer es construir nuestro recurso de perfil con estos incrustados. En el siguiente ejemplo, uso lombok para abreviar el código.

@Data @Relation(value = "profile") public class ProfileResource { private final String firstName; private final String lastName; @JsonUnwrapped private final Resources<EmbeddedWrapper> embeddeds; }

Tenga en cuenta la anotación @JsonUnwrapped on embeddeds field

Y estamos listos para devolver todo esto desde el controlador

... Resources<EmbeddedWrapper> embeddedEmails = new Resources(embeddeds, linkTo(EmailAddressController.class).withSelfRel()); return ResponseEntity.ok(new Resource(new ProfileResource("Thomas", "Anderson", embeddedEmails), linkTo(ProfileController.class).withSelfRel())); }

Ahora en la respuesta tendremos

{ "firstName": "Thomas", "lastName": "Anderson", "_links": { "self": { "href": "http://localhost:8080/profile" } }, "_embedded": { "emails": [ { "email": "[email protected]", "type": "primary" }, { "email": "[email protected]", "type": "home" } ] } }

Una parte interesante del uso de los elementos Resources<EmbeddedWrapper> embeddeds es que puede poner diferentes recursos en él y los agrupará automáticamente por relaciones. Para esto, utilizamos la anotación @Relation del paquete org.springframework.hateoas.core .

También hay un buen artículo sobre recursos integrados en HAL