Могу ли я сделать настраиваемый контроллер зеркальным отображением Spring -Data-Rest/Spring -Hateoas сгенерированных классов?
Я пытаюсь сделать что-то, что, по-моему, должно быть очень простым. У меня есть объект Question
, настройка с помощью spring -boot, spring -data-rest и spring -hateoas. Все основы прекрасно работают. Я хотел бы добавить пользовательский контроллер, который возвращает List<Question>
в точно таком же формате, что и GET для моего Repository
/questions
url, так что ответы между ними совместимы.
Вот мой контроллер:
@Controller
public class QuestionListController {
@Autowired private QuestionRepository questionRepository;
@Autowired private PagedResourcesAssembler<Question> pagedResourcesAssembler;
@Autowired private QuestionResourceAssembler questionResourceAssembler;
@RequestMapping(
value = "/api/info/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);
}
}
Вариант 1: Положитесь на предоставленный SimplePagedResourceAssembler
Проблема с этой опцией заключается в том, что ни один из необходимых _links
не отображается. Если бы это было исправление, это было бы самым простым решением.
Вариант 2. Реализовать мой открытый ассемблер ресурсов
Проблема с этой опцией заключается в том, что реализация QuestionResourceAssembler
в соответствии с Spring -Hateoas documentation ведет по пути, где QuestionResource
заканчивается как почти дубликат Question
, а затем ассемблеру необходимо вручную скопировать данные между двумя объектами, и мне нужно собрать все соответствующие _links
вручную. Похоже, это сильно расходуется.
Что делать?
Я знаю, что Spring уже сгенерировал код, чтобы сделать все это, когда он экспортирует QuestionRepository
. Есть ли способ, которым я могу использовать этот код и использовать его, чтобы гарантировать, что выходные данные моего контроллера являются бесшовными и взаимозаменяемыми с генерируемыми ответами?
Ответы
Ответ 1
Я нашел способ полностью имитировать поведение Spring Data Rest. Хитрость заключается в использовании комбинации PagedResourcesAssembler
и инъецированного аргументом экземпляра PersistentEntityResourceAssembler
. Просто определите свой контроллер следующим образом:
@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);
}
}
Это работает благодаря существованию PersistentEntityResourceAssemblerArgumentResolver
, который Spring использует для ввода PersistentEntityResourceAssembler
для вас. Результат - это то, что вы ожидаете от одного из методов запроса репозитория!
Ответ 2
Я считаю, что я решил эту проблему довольно просто, хотя это могло быть лучше документировано.
После прочтения реализации SimplePagedResourceAssembler
я понял, что гибридное решение может работать. Предоставленный класс Resource<?>
корректно отображает объекты, но не включает ссылки, поэтому все, что вам нужно сделать, это добавить их.
Моя реализация QuestionResourceAssembler
выглядит следующим образом:
@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;
}
}
После этого в моем контроллере я использовал Вариант 2 выше:
return pagedResourcesAssembler.toResource(page, questionResourceAssembler);
Это хорошо работает и не слишком много кода. Единственная проблема - вам нужно вручную добавлять ссылки для каждой требуемой ссылки.
Ответ 3
Обновленный ответ на этот старый вопрос: теперь вы можете сделать это с помощью PersistentEntityResourceAssembler
Внутри @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
}