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 llamadasembeddedRes
. - Puede crear otro método que omita la relación String dentro de
resWrapper
. - El primer argumento de
embeddedRes
esObject
, por lo que también puede proporcionar una instancia deResourceSupport
- El resultado de la expresión es del tipo que extiende
Resource<DomainObjClass>
. Por lo tanto, será procesado por todos los Spring Data RESTResourceProcessor<Resource<DomainObjClass>>
. Puede crear una colección de ellos y también incluirnew 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