我可以让自定义控制器镜像 Spring-Data-Rest / Spring-Hateoas 生成的类的格式吗?

Posted

技术标签:

【中文标题】我可以让自定义控制器镜像 Spring-Data-Rest / Spring-Hateoas 生成的类的格式吗?【英文标题】:Can I make a custom controller mirror the formatting of Spring-Data-Rest / Spring-Hateoas generated classes? 【发布时间】:2014-12-19 17:10:03 【问题描述】:

我正在尝试做一些我认为应该非常简单的事情。我有一个Question 对象,使用 spring-boot、spring-data-rest 和 spring-hateoas 设置。所有的基础工作都很好。我想添加一个自定义控制器,它返回一个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 documentation 实现QuestionResourceAssembler 会导致QuestionResource 最终成为Question 的近重复项,然后汇编程序需要手动复制数据在这两个对象之间,我需要手动构建所有相关的_links。这似乎浪费了很多精力。

怎么办?

我知道 Spring 在导出 QuestionRepository 时已经生成了执行所有这些操作的代码。有什么方法可以利用该代码并使用它,以确保控制器的输出与生成的响应无缝且可互换?

【问题讨论】:

【参考方案1】:

我找到了一种完全模仿 Spring Data Rest 行为的方法。诀窍在于使用PagedResourcesAssemblerPersistentEntityResourceAssembler 的参数注入实例的组合。只需按如下方式定义您的控制器...

@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。结果正是您对存储库查询方法之一的期望!

【讨论】:

很好,我试试看。 另见***.com/questions/31758862/…。 小补充:可以防止未经检查的警告,当你的方法直接返回PagedResource&lt;MyEntity&gt;这是(paged)resourceAssembler.toResource返回的 这有效,除非没有找到结果。在这种情况下,REST 响应中没有 _embedded 键,这与自动生成的 REST/HATEOAS 控制器不同。在这种情况下,我不得不手动调用pagedResourcesAssembler.toEmptyResource @alalonde 可能 Spring Data REST 行为已更改,但如果生成的 HATEOAS 存储库中的集合为空,我也不会得到 _embedded【参考方案2】:

更新了这个老问题的答案:您现在可以通过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

【讨论】:

是否可以在控制器方法参数中接受指向现有资源的链接并自动将其转换为实体? 是的,如果您的实体链接到另一个“子”实体,那么您可以简单地发布一个 URI 来创建该链接。例如 HTTP POST /path/to/parent/entity Payload 用于 ecample someAttr: "example Value", linkToChildEntity: "/path/to/child/entity/&lt;id&gt;" 希望这会有所帮助。有关更多详细信息,请参阅this *** question 我的意思是在自定义控制器中。我知道,Spring data rest 做到了。问题是,我怎样才能编写一个可以做同样事情的控制器? 如何处理空值? ... "PersistentEntity 不能为空!" 我也不确定如何处理集合,例如GET/findAll 操作。请您提供一些有效的例子吗?【参考方案3】:

我相信我已经以一种相当直接的方式解决了这个问题,尽管它本可以得到更好的记录。

阅读SimplePagedResourceAssembler 的实现后,我意识到混合解决方案可能有效。提供的Resource&lt;?&gt; 类可以正确呈现实体,但不包含链接,因此您只需添加它们即可。

我的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 可能脱节(就像您在其他地方指出的那样)?你必须在任何地方都使用 Pages 吗?当我尝试返回 List> 时,由于双向休眠映射,我得到了递归。所以我要么必须添加@JsonIgnore,要么使用 PagedResourcesAssembler。 我认为你想在任何时候返回多个对象时都使用 PagedResources 和 Pageable ,只是为了安全。当然它可能会容易得多,但如果您依赖 Spring 自己的链接构建器,您并不会真正以脱节的 API 告终。有可能您最终不会得到指向有用目的地的链接,但由于相关内容取决于控制器,因此它是有道理的。它真正的 Spring 可能会根据返回的对象类型提供有用的链接。 在 2016 年,这仍然是使用控制器/自定义存储库时获得 spring-data-rest esque 行为的最佳/唯一方法吗?这很烦人。 抱歉,我不再使用 Spring,所以我无法回答最新的技术是什么。

以上是关于我可以让自定义控制器镜像 Spring-Data-Rest / Spring-Hateoas 生成的类的格式吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何让自定义导航控制器为每个故事板提供相同的自定义导航栏按钮

让自定义视图仅向下移动

无法让自定义 RKBlockValueTransformer 与 RestKit 一起正常工作

如何让自定义 UIButton 仅响应不透明部分的点击?

在 NextJS 中是不是可以让自定义 _app.js 读取 slug、getInitialProps 并将这些道具传递给每个组件,包括所有页面?

如何让自定义 UIControl 作为推送操作参与 segue?