【问题标题】:Spring MVC, REST, and HATEOASSpring MVC、REST 和 HATEOAS
【发布时间】:2011-11-02 07:16:34
【问题描述】:

我正在努力使用HATEOAS 实现 Spring MVC 3.x RESTful 服务的正确方法。考虑以下约束:

  • 我不希望我的域实体被 web/rest 结构污染。
  • 我不希望我的控制器被视图结构污染。
  • 我想支持多视图。

目前我有一个很好的 MVC 应用程序,没有 HATEOAS。域实体是纯 POJO,没有嵌入任何视图或 web/rest 概念。例如:

class User {
   public String getName() {...}
   public String setName(String name) {...}
   ...
}

我的控制器也很简单。它们提供路由和状态,并委托给 Spring 的视图解析框架。请注意,我的应用程序支持 JSON、XML 和 HTML,但没有域实体或控制器具有嵌入的视图信息:

@Controller
@RequestMapping("/users")
class UserController {

  @RequestMapping
  public ModelAndView getAllUsers() {
    List<User> users = userRepository.findAll();
    return new ModelAndView("users/index", "users", users);
  }

  @RequestMapping("/{id}")
  public ModelAndView getUser(@PathVariable Long id) {
    User user = userRepository.findById(id);
    return new ModelAndView("users/show", "user", user);
  }
}

所以,现在我的问题 - 我不确定支持 HATEOAS 的干净方式。这是一个例子。假设当客户端请求一个 JSON 格式的 User 时,结果如下:

{
  firstName: "John",
  lastName: "Smith"
}

假设当我支持 HATEOAS 时,我希望 JSON 包含一个简单的“自我”链接,然后客户端可以使用该链接来刷新对象、删除对象或进行其他操作。它还可能有一个“朋友”链接,指示如何获取用户的朋友列表:

{
  firstName: "John",
  lastName: "Smith",
  links: [
    {
      rel: "self",
      ref: "http://myserver/users/1"
    },
    {
      rel: "friends",
      ref: "http://myserver/users/1/friends"
    }
  ]
}

不知何故,我想将链接附加到我的对象。我觉得这样做的正确位置是在控制器层,因为控制器都知道正确的 URL。此外,由于我支持多个视图,我觉得正确的做法是在控制器中以某种方式装饰我的域实体,然后再将它们转换为 JSON/XML/Spring 的视图解析框架中的任何内容。一种方法可能是使用包含链接列表的通用 Resource 类来包装有问题的 POJO。需要进行一些视图调整才能将其压缩成我想要的格式,但它是可行的。不幸的是,嵌套资源不能以这种方式包装。想到的其他事情包括添加到 ModelAndView 的链接,然后自定义 Spring 的每个开箱即用的视图解析器以将链接填充到生成的 JSON/XML/等中。我不想要的是不断手工制作 JSON/XML/等。以适应开发过程中来来去去的各种链接。

想法?

【问题讨论】:

标签: spring rest spring-mvc hateoas


【解决方案1】:

要在 REST api 中创建链接,您可以使用 Spring 框架的 HAETOAS 项目。

org.springframework.hateoas.mvc.ControllerLinkBuilder 类有一组方法,您可以使用这些方法来构建链接,例如 -

Link link=linkTo(PersonControllerImpl.class).slash(null).withSelfRel();

另外,如果你有一个控制器方法,它带有带有一些 URI 值的 @RequestMapping 注释 -

@RequestMapping(value = "/customer", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<?> getCustomer() {

//...
}

然后您可以使用方法 URI 值创建链接 -

linkTo(methodOn(PersonControllerImpl.class).getCustomer()).toUri().toString()

它将返回 String 值 (http://www.urhost.com/customer),您可以在 entity Object 中设置该值。

【讨论】:

    【解决方案2】:

    在 GitHub 上有一个名为 Spring HATEOAS 的有用项目,其描述如下:

    “这个项目提供了一些 API 来简化创建 REST 表示 在使用 Spring 时遵循 HATEOAS 原则 尤其是 Spring MVC”

    如果您返回的资源类扩展了“ResourceSupport”,您可以轻松地为其添加链接,并且您可以使用“ControllerLinkBuilder”构建链接,例如添加自链接:

    import static org.sfw.hateoas.mvc.ControllerLinkBuilder.*;
    
    Link link = linkTo(YourController.class).slash(resource.getName()).withSelfRel();
    resource.add(link);
    

    这是一个相当新的项目,但如果需要,它可以从公共 Maven 存储库中获得:

    <dependency>
        <groupId>org.springframework.hateoas</groupId>
        <artifactId>spring-hateoas</artifactId>
        <version>0.3.0.RELEASE</version>
    </dependency>
    

    如果你使用 maven 工件:

    org.sfw.hateoas.mvc.ControllerLinkBuilder
    

    变成:

    org.springframework.hateoas.mvc.ControllerLinkBuilder
    

    【讨论】:

    • 有趣。我更喜欢在我的应用程序外围导入 Spring 和其他基础设施代码,但在这种情况下,所有扩展 ResourceSupport 的资源类似乎都是一个不错的权衡。谢谢!
    • 哦,它来自 Spring Data 的主要人员 Oliver Gierke。酷。
    • 我写了一篇与HATEOAS using Spring Framework相关的博文
    【解决方案3】:

    在寻找其他内容时偶然发现了这一点,并认为您应该考虑使用 Link 标头而不是 JSON 正文中的内容,这实际上只会污染您的资源表示。

    查看IETFs memo on web linkingIANA registry of link relations

    【讨论】:

      【解决方案4】:

      我的想法:

      • 使用某种命名约定,例如,可以根据对象的类名构造自引用 url。
      • 我不认为添加链接的东西应该由控制器添加(顺便说一句,你自己写了“我不希望我的控制器被视图结构污染”。我会尝试寻找一种方法来扩展 JSON 序列化以便它自动添加额外的东西。您可能需要为您的实体添加一些注释,即使这会污染它们。

      【讨论】:

      • 谢谢。如果我想要的只是一个“自我”链接,命名约定可以工作,但是,在实际应用程序中,对象上会有多个特定于工作流的链接(类似于有些著名的"how to get a cup of coffee" 文章),这些链接无法按约定创建.
      • 关于生成链接 - 我需要想出一些通用的东西,所以当我添加一个新链接时,我不必去编辑我的 3 个不同的视图解析器 (JSON/XML/HTML)。嗯。也许具有这种技术堆栈的 RESTful 架构的本质要求域实体被链接“污染”,也许这还不错……
      • “命名约定”是个坏主意。具体来说,它似乎完全反对 HATEOAS。 HATEOAS 的重点是客户端不需要“了解”您的 API,而是可以简单地按照链接到达另一个实体或状态转换。
      猜你喜欢
      • 2014-03-07
      • 1970-01-01
      • 2013-10-31
      • 2016-06-19
      • 2014-08-16
      • 2016-05-23
      • 2014-07-05
      • 2018-07-23
      • 1970-01-01
      相关资源
      最近更新 更多