我能否使自定义控制器镜像Spring-Data-Rest / Spring-Hateoas生成的类的格式?

27

我试图做一件我认为非常简单的事情。我有一个使用spring-boot、spring-data-rest和spring-hateoas设置的Question对象。所有基本功能都正常工作。我想添加一个自定义控制器,返回一个List<Question>,其格式与对Repository/questions URL进行GET请求的响应完全相同,使得两者之间的响应兼容。

以下是我的控制器:

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

}

选项1:依赖于提供的 SimplePagedResourceAssembler

这个选项的问题在于没有渲染任何必要的 _links。如果有解决方法,那么这将是最简单的解决方案。

选项2:实现自己的资源汇编器

这个选项的问题在于按照Spring-Hateoas文档实现QuestionResourceAssembler会导致QuestionResource几乎成为Question的副本,然后汇编器需要手动在两个对象之间复制数据,我需要手动构建所有相关的_links。这似乎浪费了很多精力。

该怎么办?

我知道当Spring导出QuestionRepository时已经生成了完成所有这些操作的代码。是否有任何方法可以利用那些代码并使用它们,以确保我的控制器输出与生成的响应无缝交换?

3个回答

26

我找到了一种完全模拟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。结果与您的存储库查询方法之一完全相同!


好的,我会试一下。 - JBCP
请参见https://dev59.com/DFwZ5IYBdhLWcg3wLdsF。 - user41871
3
除了在找不到结果时,这种方法就无法使用了。 在这种情况下,REST响应中没有_embedded键,不像自动生成的REST/HATEOAS控制器。 我必须手动调用pagedResourcesAssembler.toEmptyResource来处理这种情况。 - alalonde
1
@alalonde,也许Spring Data REST的行为已经改变了,但是在生成的HATEOAS存储库中,如果集合为空,我也无法获得_embedded - Hubert Grzeskowiak
SDR在Spring Boot 2.1.0中注入PersistentEntityResourceAssembler失败。它会报错java.lang.IllegalArgumentException: entities is marked @NonNull but is null - Jefferson Lima
显示剩余2条评论

9

这是关于IT技术的翻译内容:

更新了这个旧问题的答案:现在您可以使用 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
}

我是指自定义控制器。Spring Data Rest可以做到,我知道。问题是,我如何编写一个能够实现相同功能的控制器? - aycanadal
1
如何处理空值?...“ PersistentEntity 不得为空!” - Rafael
@Rafael 在我看来,一个 HATEOAS REST WebService 不应该只简单地返回 null。它应该至少返回一个空的 JSON 数组:{ }。 - Robert
我的工作示例已在此处检查: https://github.com/Doogiemuc/liquido-backend-spring/blob/master/src/main/java/org/doogie/liquido/rest/BallotRestController.java - Robert
@Robert 我完全同意...即使集合不为null或空,我也会遇到这个错误...也许我做错了什么。谢谢。 - Rafael
显示剩余5条评论

4

我相信我已经用一种相当直接的方式解决了这个问题,尽管它可能需要更好的文档记录。

在阅读了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);

这个方法很有效,代码量也不多。唯一的麻烦在于需要手动为每个引用添加链接。


看起来Spring本可以让这个过程更简单一些。你必须添加自己的链接,这是否会导致潜在的不连贯API(就像你在其他地方指出的那样)?而且你必须在所有地方都使用页面吗?当我尝试返回一个List<Resource<MyObj>>时,由于双向Hibernate映射,我得到了递归。所以我要么添加@JsonIgnore,要么使用PagedResourcesAssembler。 - gyoder
我认为每次返回多个对象时,您都应该使用PagedResources和Pageable,这样更安全。虽然可能会更容易,但是如果依赖于Spring自己的linkbuilder,您不会真正得到一个不连贯的API。您可能不会获得指向有用目标的链接,但是由于相关内容取决于控制器,所以这有一定道理。确实,Spring可以根据返回的对象类型提供有用的链接。 - JBCP
1
在2016年,当使用控制器/自定义存储库时,这仍然是获得类似于spring-data-rest行为的最佳/唯一方法吗?这真的很烦人。 - Casey
抱歉,我不再使用Spring,所以无法回答最新的技术是什么。 - JBCP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接