Can I make a custom controller mirror the formatting of Spring-Data-Rest / Spring-Hateoas generated classes?

北城以北 提交于 2019-11-26 10:23:48

问题


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?


回答1:


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!




回答2:


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
}



回答3:


I believe I've solved this problem in a fairly straightforward way, although it could have been better documented.

After reading the implementation of SimplePagedResourceAssembler I realized a hybrid solution might work. The provided Resource<?> class renders entities correctly, but doesn't include links, so all you need to do is add them.

My QuestionResourceAssembler implementation looks like this:

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

Once that's done, in my controller I used Option 2 above:

    return pagedResourcesAssembler.toResource(page, questionResourceAssembler);

This works well, and isn't too much code. The only hassle is you need to manually add links for each reference you need.



来源:https://stackoverflow.com/questions/26538156/can-i-make-a-custom-controller-mirror-the-formatting-of-spring-data-rest-sprin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!