构建超媒体驱动的 RESTful Web 服务

本指南将引导您完成使用 Spring 创建“Hello, World”超媒体驱动的 REST Web 服务的过程。

超媒体 是 REST 的一个重要方面。它允许您构建在很大程度上解耦客户端和服务器的服务,并使它们能够独立发展。REST 资源返回的表示形式不仅包含数据,还包含指向相关资源的链接。因此,表示形式的设计对于整个服务的设计至关重要。

您将构建什么

您将使用 Spring HATEOAS 构建一个超媒体驱动的 REST 服务:一个 API 库,您可以使用它来创建指向 Spring MVC 控制器、构建资源表示和控制它们如何呈现为受支持的超媒体格式(如 HAL)的链接。

该服务将在 https://127.0.0.1:8080/greeting 处接受 HTTP GET 请求。

它将以 JSON 格式响应问候语,并使用最简单的超媒体元素(指向资源本身的链接)进行增强。以下清单显示了输出

{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"https://127.0.0.1:8080/greeting?name=World"
    }
  }
}

响应已指示您可以使用查询字符串中的可选 name 参数自定义问候语,如下所示

https://127.0.0.1:8080/greeting?name=User

name 参数值覆盖了 World 的默认值,并在响应中反映出来,如下所示

{
  "content":"Hello, User!",
  "_links":{
    "self":{
      "href":"https://127.0.0.1:8080/greeting?name=User"
    }
  }
}

您需要什么

如何完成本指南

与大多数 Spring 入门指南 一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到可工作的代码。

从头开始,请继续执行 从 Spring Initializr 开始

跳过基础知识,请执行以下操作

完成后,您可以将您的结果与 gs-rest-hateoas/complete 中的代码进行比较。

从 Spring Initializr 开始

您可以使用此 预初始化项目 并单击“生成”以下载 ZIP 文件。此项目配置为适合本教程中的示例。

手动初始化项目

  1. 导航到 https://start.spring.io。此服务会提取应用程序所需的所有依赖项,并为您完成大部分设置。

  2. 选择 Gradle 或 Maven 以及您要使用的语言。本指南假设您选择了 Java。

  3. 单击依赖项并选择Spring HATEOAS

  4. 单击生成

  5. 下载生成的 ZIP 文件,它是使用您的选择配置的 Web 应用程序的存档。

如果您的 IDE 集成了 Spring Initializr,则可以从您的 IDE 中完成此过程。
您也可以从 Github 分叉项目并在您的 IDE 或其他编辑器中打开它。

创建资源表示类

现在您已经设置了项目和构建系统,您可以创建 Web 服务了。

首先考虑服务交互。

该服务将在 /greeting 处公开一个资源以处理 GET 请求,可选地在查询字符串中使用 name 参数。GET 请求应返回一个 200 OK 响应,并在正文中包含 JSON 以表示问候语。

除此之外,资源的 JSON 表示形式将在 _links 属性中使用超媒体元素列表进行增强。最基本的表单是指向资源本身的链接。表示形式应类似于以下清单

{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"https://127.0.0.1:8080/greeting?name=World"
    }
  }
}

content 是问候语的文本表示形式。_links 元素包含一个链接列表(在本例中,恰好有一个,关系类型为 relhref 属性指向访问的资源)。

要对问候语表示形式进行建模,请创建一个资源表示类。由于 _links 属性是表示模型的基本属性,因此 Spring HATEOAS 提供了一个基类(称为 RepresentationModel),它允许您添加 Link 的实例并确保它们按前面显示的方式呈现。

创建一个扩展 RepresentationModel 并添加内容的字段和访问器以及构造函数的普通 Java 对象,如下所示(来自 src/main/java/com/example/resthateoas/Greeting.java

package com.example.resthateoas;

import org.springframework.hateoas.RepresentationModel;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Greeting extends RepresentationModel<Greeting> {

	private final String content;

	@JsonCreator
	public Greeting(@JsonProperty("content") String content) {
		this.content = content;
	}

	public String getContent() {
		return content;
	}
}
  • @JsonCreator:指示 Jackson 如何创建此 POJO 的实例。

  • @JsonProperty:将字段标记为 Jackson 应将此构造函数参数放入其中。

正如您将在本指南的后面看到的那样,Spring 将使用 Jackson JSON 库自动将类型为 Greeting 的实例编组为 JSON。

接下来,创建将提供这些问候语的资源控制器。

创建 REST 控制器

在 Spring 构建 RESTful Web 服务的方法中,HTTP 请求由控制器处理。组件由 @RestController 注解标识,该注解组合了 @Controller@ResponseBody 注解。以下 GreetingController(来自 src/main/java/com/example/resthateoas/GreetingController.java)通过返回 Greeting 类的新的实例来处理 /greetingGET 请求

package com.example.resthateoas;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@RestController
public class GreetingController {

	private static final String TEMPLATE = "Hello, %s!";

	@RequestMapping("/greeting")
	public HttpEntity<Greeting> greeting(
		@RequestParam(value = "name", defaultValue = "World") String name) {

		Greeting greeting = new Greeting(String.format(TEMPLATE, name));
		greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());

		return new ResponseEntity<>(greeting, HttpStatus.OK);
	}
}

此控制器简洁明了,但有很多内容。我们将逐步分解它。

@RequestMapping 注解确保对 /greeting 的 HTTP 请求映射到 greeting() 方法。

上面的示例没有指定 GETPUTPOST 等,因为 @RequestMapping 默认映射所有 HTTP 操作。使用 @GetMapping("/greeting") 来缩小此映射范围。在这种情况下,您还需要 import org.springframework.web.bind.annotation.GetMapping;

@RequestParam 将查询字符串参数 name 的值绑定到 greeting() 方法的 name 参数。由于使用了 defaultValue 属性,因此此查询字符串参数隐式地不是 required。如果请求中不存在,则使用 defaultValueWorld

由于类上存在 @RestController 注解,因此会向 greeting 方法添加一个隐式的 @ResponseBody 注解。这会导致 Spring MVC 将返回的 HttpEntity 及其有效负载(Greeting)直接呈现到响应中。

方法实现中最有趣的部分是如何创建指向控制器方法的链接以及如何将其添加到表示模型中。linkTo(…)methodOn(…) 都是 ControllerLinkBuilder 上的静态方法,允许您在控制器上模拟方法调用。返回的 LinkBuilder 将检查控制器方法的映射注解以构建方法映射到的确切 URI。

Spring HATEOAS 尊重各种 X-FORWARDED- 标头。如果您将 Spring HATEOAS 服务置于代理之后并使用 X-FORWARDED-HOST 标头正确配置它,则生成的链接将被正确格式化。

withSelfRel() 的调用创建一个 Link 实例,您将其添加到 Greeting 表示模型中。

@SpringBootApplication 是一个便利注解,它添加了以下所有内容

  • @Configuration:将类标记为应用程序上下文的 bean 定义的来源。

  • @EnableAutoConfiguration:告诉 Spring Boot 根据类路径设置、其他 bean 和各种属性设置开始添加 bean。例如,如果 spring-webmvc 位于类路径上,则此注解将应用程序标记为 Web 应用程序并激活关键行为,例如设置 DispatcherServlet

  • @ComponentScan:告诉 Spring 在 com/example 包中查找其他组件、配置和服务,使其能够找到控制器。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法启动应用程序。您是否注意到没有一行 XML?也没有 web.xml 文件。此 Web 应用程序是 100% 纯 Java,您无需处理配置任何管道或基础设施。

构建可执行 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源的单个可执行 JAR 文件,并运行该文件。构建可执行 jar 使得在整个开发生命周期中、跨不同环境等方面轻松交付、版本化和部署服务作为应用程序。

如果您使用 Gradle,则可以使用 ./gradlew bootRun 运行应用程序。或者,您可以使用 ./gradlew build 构建 JAR 文件,然后运行 JAR 文件,如下所示

java -jar build/libs/gs-rest-hateoas-0.1.0.jar

如果您使用 Maven,则可以使用 ./mvnw spring-boot:run 运行应用程序。或者,您可以使用 ./mvnw clean package 构建 JAR 文件,然后运行 JAR 文件,如下所示

java -jar target/gs-rest-hateoas-0.1.0.jar
此处描述的步骤创建了一个可运行的 JAR。您还可以 构建一个经典的 WAR 文件

显示日志输出。服务应在几秒钟内启动并运行。

测试服务

现在服务已启动,请访问 https://127.0.0.1:8080/greeting,您应该会看到以下内容

{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"https://127.0.0.1:8080/greeting?name=World"
    }
  }
}

通过访问以下 URL 提供一个 name 查询字符串参数:https://127.0.0.1:8080/greeting?name=User。请注意 content 属性的值如何从 Hello, World! 更改为 Hello, User!,以及 self 链接的 href 属性也反映了此更改,如下所示

{
  "content":"Hello, User!",
  "_links":{
    "self":{
      "href":"https://127.0.0.1:8080/greeting?name=User"
    }
  }
}

此更改表明 GreetingController 中的 @RequestParam 设置按预期工作。name 参数已赋予默认值 World,但始终可以通过查询字符串显式覆盖。

总结

恭喜!您刚刚使用 Spring HATEOAS 开发了一个超媒体驱动的 RESTful Web 服务。

获取代码