proyecto example data crear spring spring-mvc spring-data spring-data-rest spring-hateoas

example - spring boot data rest api



¿Puedo hacer que un controlador personalizado refleje el formato de las clases generadas por Spring-Data-Rest/Spring-Hateoas? (3)

Estoy tratando de hacer algo que creo que debería ser realmente simple. Tengo un objeto Question , configurado con spring-boot, spring-data-rest y spring-hateoas. Todos los conceptos básicos funcionan bien. Me gustaría agregar un controlador personalizado que devuelva List<Question> exactamente en el mismo formato que un GET a la URL de mi Repository /questions , para que las respuestas entre los dos sean compatibles.

Aquí está mi controlador:

@Controller public class QuestionListController { @Autowired private QuestionRepository questionRepository; @Autowired private PagedResourcesAssembler<Question> pagedResourcesAssembler; @Autowired private QuestionResourceAssembler questionResourceAssembler; @RequestMapping( value = "/api/questions/filter", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public @ResponseBody PagedResources<QuestionResource> filter( @RequestParam(value = "filter", required = false) String filter, Pageable p) { // Using queryDSL here to get a paged list of Questions Page<Question> page = questionRepository.findAll( QuestionPredicate.findWithFilter(filter), p); // Option 1 - default resource assembler return pagedResourcesAssembler.toResource(page); // Option 2 - custom resource assembler return pagedResourcesAssembler.toResource(page, questionResourceAssembler); } }

Opción 1: Confíe en el SimplePagedResourceAssembler proporcionado

El problema con esta opción no es necesario _links Se _links . Si hay una solución para esto, sería la solución más fácil.

Opción 2: Implementar mi ensamblador de recursos abiertos

El problema con esta opción es que la implementación de QuestionResourceAssembler acuerdo con la documentación de Spring-Hateoas lleva a una ruta donde QuestionResource termina siendo casi un duplicado de Question , y luego el ensamblador necesita copiar datos manualmente entre los dos objetos, y necesito para construir todos los _links relevantes a mano. Esto parece un montón de esfuerzo perdido.

¿Qué hacer?

Sé que Spring ya ha generado el código para hacer todo esto cuando exporta el QuestionRepository . ¿Hay alguna forma de que pueda acceder a ese código y usarlo, para asegurar que la salida de mi controlador sea transparente e intercambiable con las respuestas generadas?


Creo que he resuelto este problema de una manera bastante directa, aunque podría haber sido mejor documentado.

Después de leer la implementación de SimplePagedResourceAssembler me di cuenta de que una solución híbrida podría funcionar. La clase de Resource<?> proporcionada Resource<?> Representa entidades correctamente, pero no incluye enlaces, por lo que todo lo que necesita hacer es agregarlos.

Mi implementación de QuestionResourceAssembler ve así:

@Component public class QuestionResourceAssembler implements ResourceAssembler<Question, Resource<Question>> { @Autowired EntityLinks entityLinks; @Override public Resource<Question> toResource(Question question) { Resource<Question> resource = new Resource<Question>(question); final LinkBuilder lb = entityLinks.linkForSingleResource(Question.class, question.getId()); resource.add(lb.withSelfRel()); resource.add(lb.slash("answers").withRel("answers")); // other links return resource; } }

Una vez hecho esto, en mi controlador utilicé la Opción 2 anterior:

return pagedResourcesAssembler.toResource(page, questionResourceAssembler);

Esto funciona bien, y no es demasiado código. La única molestia es que necesita agregar enlaces manualmente para cada referencia que necesita.


He encontrado una manera de imitar el comportamiento de Spring Data Rest por completo. El truco radica en utilizar una combinación de PagedResourcesAssembler y una instancia con argumento inyectado de PersistentEntityResourceAssembler . Simplemente defina su controlador de la siguiente manera ...

@RepositoryRestController @RequestMapping("...") public class ThingController { @Autowired private PagedResourcesAssembler pagedResourcesAssembler; @SuppressWarnings("unchecked") // optional - ignores warning on return statement below... @RequestMapping(value = "...", method = RequestMethod.GET) @ResponseBody public PagedResources<PersistentEntityResource> customMethod( ..., Pageable pageable, // this gets automatically injected by Spring... PersistentEntityResourceAssembler resourceAssembler) { Page<MyEntity> page = ...; ... return pagedResourcesAssembler.toResource(page, resourceAssembler); } }

Esto funciona gracias a la existencia de PersistentEntityResourceAssemblerArgumentResolver , que Spring utiliza para inyectar PersistentEntityResourceAssembler por usted. ¡El resultado es exactamente lo que esperarías de uno de tus métodos de consulta de repositorio!


Respuesta actualizada a esta vieja pregunta: ahora puede hacer eso con un PersistentEntityResourceAssembler

Dentro de tu @RepositoryRestController:

@RequestMapping(value = "somePath", method = POST) public @ResponseBody PersistentEntityResource postEntity(@RequestBody Resource<EntityModel> newEntityResource, PersistentEntityResourceAssembler resourceAssembler) { EntityModel newEntity = newEntityResource.getContent(); // ... do something additional with new Entity if you want here ... EntityModel savedEntity = entityRepo.save(newEntity); return resourceAssembler.toResource(savedEntity); // this will create the complete HATEOAS response }