导读:本期聚焦于夏天宇创作的《Spring Boot 如何整合 Spring HATEOAS 构建超媒体 RESTful API?》,敬请观看详情。REST 接口返回的 JSON 数据里只有字段值,客户端想知道下一步该调用什么接口,往往只能靠翻文档,接口一变就容易出错。超媒体(HATEOAS)给出了另一种思路:服务端在响应中直接带上相关资源的链接,客户端根据链接动态导航,实现接口自描述。本文介绍 Spring HATEOAS 的核心概念,讲解在 Spring Boot 项目中引入依赖、使用 RepresentationModel 和 EntityModel 构建带链接的响应、通过 WebMvcLinkBuilder 生成链接、以及 Controller 上如何组织资源的完整流程,并附上可直接运行的代码示例,帮助你快速搭建出结构清晰、可演进性强的超媒体 API。

传统的 REST 接口通常只返回裸数据,客户端需要根据文档硬编码各种 URL 才能完成业务流程。一旦服务端调整了路由,客户端就得跟着改代码,联调成本很高。超媒体作为 REST 成熟度模型中的最高层级,要求响应中不仅包含数据本身,还要包含指向相关操作的链接,让客户端能够根据链接自主发现下一步动作。Spring HATEOAS 正是 Spring 官方提供的超媒体支持库,它与 Spring Boot 无缝集成,本文将完整演示整合过程。

Spring Boot 如何整合 Spring HATEOAS 构建超媒体 RESTful API?

一、超媒体的核心概念与 Spring HATEOAS 的作用

HATEOAS 是 Hypermedia as the Engine of Application State 的缩写,中文一般译作超媒体作为应用状态引擎。它的核心思想是:客户端与服务端的交互不依赖固定的 URL 约定,而是依赖响应中携带的链接。举个例子,客户端请求一个订单资源时,如果订单状态是待支付,响应中会出现支付链接;如果订单已发货,响应中则出现确认收货链接。客户端不需要了解业务规则,只需要判断链接是否存在即可决定行为。

Spring HATEOAS 提供了一组 API 用来构建这种带链接的响应模型,主要包括 RepresentationModelEntityModelCollectionModelLinkWebMvcLinkBuilder 等核心类。它默认输出 HAL(Hypertext Application Language)格式的 JSON,链接统一放在 _links 字段中,这是目前使用最广泛的超媒体媒体类型。理解这些类的职责分工,是写好超媒体接口的基础。

需要注意版本对应关系。Spring Boot 2.x 对应 Spring HATEOAS 1.x,Spring Boot 3.x 对应 Spring HATEOAS 2.x。如果你使用的是 Spring Boot 3,那么核心包名已经从 org.springframework.hateoas 迁移为 org.springframework.hateoas.server 下的相关包,编码方式略有调整,下文示例会说明。

二、引入依赖并搭建基础项目

在 Spring Boot 项目中整合 Spring HATEOAS 非常简单,只需要在 pom.xml 中添加一个 starter 依赖。Spring Boot 已经帮我们管理好了版本号,不需要手动指定。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

这个 starter 会自动引入 spring-hateoas 核心库以及相关配置。引入后 Spring Boot 的自动配置会将默认的媒体类型设置为 application/hal+json,也就是说,只要你的 Controller 返回的是 RepresentationModel 的子类,输出的 JSON 就会自动带上 _links 结构,无需额外配置。

接下来准备一个简单的实体类和模拟的数据访问层,方便后续演示。这里用一个订单实体作为例子:

// 订单实体,只是一个普通 POJO
public class Order {
    private Long id;
    private String status;   // 待支付 / 已支付 / 已发货
    private Double amount;

    public Order(Long id, String status, Double amount) {
        this.id = id;
        this.status = status;
        this.amount = amount;
    }

    // 省略 getter 和 setter
}

// 模拟数据仓库
@Repository
public class OrderRepository {
    private static final Map<Long, Order> DATA = new ConcurrentHashMap<>();

    static {
        DATA.put(1L, new Order(1L, "待支付", 199.0));
        DATA.put(2L, new Order(2L, "已发货", 88.0));
    }

    public Order findById(Long id) {
        return DATA.get(id);
    }
}

三、使用 EntityModel 与 WebMvcLinkBuilder 构建带链接的响应

EntityModel 用于包装单个资源对象,它继承自 RepresentationModel,可以在包装的同时附加若干个 Link。构建链接时强烈建议使用 WebMvcLinkBuilder 而不是手写字符串 URL,因为它基于 Controller 的方法签名生成链接,方法重命名或路径调整时链接会自动跟着变,编译期就能发现问题。

下面编写订单 Controller,重点演示如何根据订单状态动态添加不同的操作链接:

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @Autowired
    private OrderRepository orderRepository;

    @GetMapping("/{id}")
    public EntityModel<Order> getOrder(@PathVariable Long id) {
        Order order = orderRepository.findById(id);
        if (order == null) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }

        // 生成指向自身的链接,指向当前类的 getOrder 方法
        Link selfLink = linkTo(methodOn(OrderController.class).getOrder(id))
                .withSelfRel();

        EntityModel<Order> model = EntityModel.of(order, selfLink);

        // 根据订单状态动态添加操作链接
        if ("待支付".equals(order.getStatus())) {
            Link payLink = linkTo(methodOn(OrderController.class).payOrder(id))
                    .withRel("pay");
            model.add(payLink);
        } else if ("已发货".equals(order.getStatus())) {
            Link confirmLink = linkTo(methodOn(OrderController.class)
                    .confirmReceipt(id)).withRel("confirm");
            model.add(confirmLink);
        }
        return model;
    }

    @PutMapping("/{id}/pay")
    public ResponseEntity<?> payOrder(@PathVariable Long id) {
        // 支付业务逻辑
        return ResponseEntity.ok().build();
    }

    @PutMapping("/{id}/confirm")
    public ResponseEntity<?> confirmReceipt(@PathVariable Long id) {
        // 确认收货业务逻辑
        return ResponseEntity.ok().build();
    }
}

请求 GET /api/orders/1 时,返回的 JSON 大致如下:

{
  "id": 1,
  "status": "待支付",
  "amount": 199.0,
  "_links": {
    "self": { "href": "http://localhost:8080/api/orders/1" },
    "pay": { "href": "http://localhost:8080/api/orders/1/pay" }
  }
}

可以看到,客户端拿到这个响应后,只需检查 _links 中是否存在 pay 关系的链接,就能判断当前订单是否可以支付,完全不需要在客户端写死 URL 或业务判断逻辑。这就是超媒体驱动的交互方式。当订单状态流转后,同一个接口返回的可用操作也会随之变化,接口的自描述性大大增强。

四、集合资源与 RepresentationModelProcessor 进阶用法

处理列表类资源时要使用 CollectionModel。直接把 EntityModel 列表塞进 CollectionModel.of(),每个元素都会保留自己的链接,同时集合本身还可以附加 self 链接:

@GetMapping
public CollectionModel<EntityModel<Order>> allOrders() {
    List<EntityModel<Order>> orders = orderRepository.findAll().stream()
            .map(order -> EntityModel.of(order,
                    linkTo(methodOn(OrderController.class)
                            .getOrder(order.getId())).withSelfRel()))
            .collect(Collectors.toList());

    return CollectionModel.of(orders,
            linkTo(methodOn(OrderController.class).allOrders()).withSelfRel());
}

当链接组装逻辑比较复杂、需要在多处复用时,可以借助 RepresentationModelProcessor 把链接添加逻辑抽离成独立组件。它的工作方式类似一个后处理器,Spring HATEOAS 会在资源模型返回前自动调用所有匹配类型的 Processor,把链接注入进去:

@Component
public class OrderResourceProcessor
        implements RepresentationModelProcessor<EntityModel<Order>> {

    @Override
    public EntityModel<Order> process(EntityModel<Order> model) {
        Order order = model.getContent();
        if (order != null && "待支付".equals(order.getStatus())) {
            model.add(linkTo(methodOn(OrderController.class)
                    .payOrder(order.getId())).withRel("pay"));
        }
        return model;
    }
}

注册这个 Processor 后,Controller 里就不再需要写状态判断逻辑,只需返回基础的 EntityModel,链接注入由框架统一完成。这种模式让链接生成逻辑集中在了一处,特别适合状态机复杂的业务场景。

五、整合时的注意事项与常见问题

首先是返回类型问题。如果 Controller 方法的返回类型写的是 EntityModel 的基类 RepresentationModel,Spring HATEOAS 有时无法正确推断泛型信息,导致序列化时丢失内容或链接,建议方法返回类型尽量写具体的泛型形式。其次是版本兼容,Spring Boot 3 下 linkTomethodOn 所在的包已经变化,网上大量基于 1.x 的教程代码直接照搬会编译报错,需要将导入调整为 org.springframework.hateoas.server.mvc.WebMvcLinkBuilder 下的新路径。

另外要注意媒体类型的协商。默认输出是 HAL 格式,如果客户端通过 Accept: application/prs.hal-forms+json 之类的头请求其他超媒体方言,需要额外引入对应依赖才能支持。对于前后端分离项目中前端框架的消费问题,主流的 Axios 可以直接读取 _links 字段,配合 TypeScript 定义好类型即可,并不会增加太多负担。

最后提醒一点,超媒体并不是银弹。对于纯内部服务间的调用、性能敏感的网关接口,增加链接字段会带来额外的响应体积和构建开销。HATEOAS 更适合面向外部开放、需要长期演进、消费者众多的 API,这时代价换来的是接口的稳定性和自解释能力。在动手改造之前,先评估自己的业务场景是否真的需要,再决定引入的深度。

Spring BootSpring HATEOASRESTful API修改时间:2026-09-11 21:02:45

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260911/54903.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。