I'm trying to do something I think should be really simple. I have a Question
object, setup with spring-boot, spring-data-rest and spring-hateoas. All the basics work fine. I would like to add a custom controller that returns a List<Question>
in exactly the same format that a GET to my Repository
's /questions
url does, so that the responses between the two are compatible.
Here is my controller:
@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); } }
Option 1: Rely on the provided SimplePagedResourceAssembler
The problem with this option is none of the necessary _links
are rendered. If there was a fix for this, it would be the easiest solution.
Option 2: Implement my open resource assembler
The problem with this option is that implementing QuestionResourceAssembler
according to the Spring-Hateoas documentation leads down a path where the QuestionResource
ends up being a near-duplicate of Question
, and then the assembler needs to manually copy data between the two objects, and I need to build all the relevant _links
by hand. This seems like a lot of wasted effort.
What to do?
I know Spring has already generated the code to do all this when it exports the QuestionRepository
. Is there any way I can tap into that code and use it, to ensure the output from my controller is seamless and interchangeable with the generated responses?
All the code you saw earlier in the controller can be moved into this class. And by applying Spring Framework’s @Component annotation, the assembler will be automatically created when the app starts. Spring HATEOAS’s abstract base class for all models is RepresentationModel.
Each section of this tutorial is managed as a separate subproject in a single github repo: rest — Spring MVC + Spring HATEOAS app with HAL representations of each resource evolution — REST app where a field is evolved but old data is retained for backward compatibility
The @RestController annotation from Spring Boot is basically a quick shortcut that saves us from always having to define @ResponseBody. Here's the previous example controller using this new annotation: 10. Conclusion
But this extra bit of server-side setup (made easy thanks to Spring HATEOAS) can ensure the clients you control (and more importantly, those you don’t) can upgrade with ease as you evolve your API. This concludes our tutorial on how to build RESTful services using Spring.
I've found a way to imitate the behavior of Spring Data Rest completely. The trick lies in using a combination of the PagedResourcesAssembler
and an argument-injected instance of PersistentEntityResourceAssembler
. Simply define your controller as follows...
@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); } }
This works thanks to the existence of PersistentEntityResourceAssemblerArgumentResolver
, which Spring uses to inject the PersistentEntityResourceAssembler
for you. The result is exactly what you'd expect from one of your repository query methods!
Updated answer on this old question: You can now do that with a PersistentEntityResourceAssembler
Inside your @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 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With