|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
REST 客户端
Spring 框架提供了以下几种方式用于调用 REST 端点:
-
WebClient- 非阻塞、响应式客户端,提供流畅的 API。 -
RestTemplate- 具有模板方法 API 的同步客户端。 -
HTTP 接口 - 带注解的接口,具有生成的动态代理实现。
WebClient
WebClient 是一个用于执行 HTTP 请求的非阻塞、响应式客户端。它在 5.0 版本中引入,作为 RestTemplate 的替代方案,支持同步、异步和流式处理场景。
WebClient 支持以下功能:
-
非阻塞 I/O。
-
响应式流背压。
-
使用更少的硬件资源实现高并发。
-
利用 Java 8 Lambda 表达式的函数式、流畅 API。
-
同步与异步交互。
-
向服务器上传流或从服务器下载流。
有关更多详情,请参见WebClient。
RestTemplate
RestTemplate 在 HTTP 客户端库之上提供了更高层次的 API。它使得只需一行代码即可轻松调用 REST 端点。它提供了以下几组重载方法:
RestTemplate 已进入维护模式,仅接受针对微小变更和 bug 的请求。请考虑改用 WebClient。 |
| 方法组 | <description> </description> |
|---|---|
|
通过 GET 请求获取一个表示。 |
|
使用 GET 方法获取一个 |
|
通过使用 HEAD 方法检索资源的所有标头。 |
|
通过使用 POST 创建一个新资源,并从响应中返回 |
|
通过使用 POST 创建一个新资源,并返回响应中的表示形式。 |
|
通过使用 POST 创建一个新资源,并返回响应中的表示形式。 |
|
通过使用 PUT 创建或更新资源。 |
|
通过使用 PATCH 方法更新资源,并返回响应中的资源表示。
请注意,JDK 的 |
|
使用 DELETE 方法删除指定 URI 处的资源。 |
|
通过使用 ALLOW 方法检索资源所允许的 HTTP 方法。 |
|
前述方法的一个更为通用(且约束更少)的版本,在需要时提供了额外的灵活性。它接受一个 这些方法允许使用 |
|
通过回调接口对请求准备和响应提取进行完全控制,从而以最通用的方式执行请求。 |
初始化
默认构造函数使用 java.net.HttpURLConnection 来执行请求。你可以通过实现 ClientHttpRequestFactory 接口来切换到其他 HTTP 库。
目前,还内置支持 Apache HttpComponents 和 OkHttp。
例如,要切换到 Apache HttpComponents,您可以使用以下方式:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
每个 ClientHttpRequestFactory 都会暴露底层 HTTP 客户端库特有的配置选项——例如,用于凭证、连接池以及其他细节。
请注意,当访问表示错误(例如 401)的响应状态时,java.net 的 HTTP 请求实现可能会抛出异常。如果这会造成问题,请切换到其他 HTTP 客户端库。 |
RestTemplate 可以进行可观测性(observability)插桩,以生成指标(metrics)和追踪(traces)。
参见RestTemplate 可观测性支持一节。 |
统一资源标识符(URIs)
许多 RestTemplate 方法接受一个 URI 模板和 URI 模板变量,这些变量可以作为 String 可变参数传入,也可以作为 Map<String,String> 传入。
以下示例使用了一个 String 可变参数:
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
以下示例使用了一个 Map<String, String>:
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
请记住,URI 模板会自动进行编码,如下例所示:
restTemplate.getForObject("https://example.com/hotel list", String.class);
// Results in request to "https://example.com/hotel%20list"
您可以使用 uriTemplateHandler 的 RestTemplate 属性来自定义 URI 的编码方式。或者,您也可以预先创建一个 java.net.URI 对象,并将其传入某个接受 RestTemplate 参数的 URI 方法中。
有关处理和编码 URI 的更多详细信息,请参阅URI 链接。
headers
您可以使用 exchange() 方法来指定请求头,如下例所示:
String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header("MyRequestHeader", "MyValue")
.build();
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
你可以通过许多返回 RestTemplate 的 ResponseEntity 方法变体来获取响应头。
身体
传入和从 RestTemplate 方法返回的对象,会借助 HttpMessageConverter 转换为原始内容或从原始内容转换而来。
在 POST 请求中,输入对象会被序列化到请求体中,如下例所示:
URI location = template.postForLocation("https://example.com/people", person);
你无需显式设置请求的 Content-Type 头。在大多数情况下,你可以根据源 Object 类型找到一个兼容的消息转换器,而所选的消息转换器会相应地设置内容类型。如有必要,你可以使用 exchange 方法显式提供 Content-Type 请求头,这反过来会影响所选择的消息转换器。
在执行 GET 请求时,响应体将被反序列化为一个输出 Object,如下例所示:
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
请求的 Accept 头部无需显式设置。在大多数情况下,可以根据预期的响应类型找到兼容的消息转换器,该转换器随后会帮助填充 Accept 头部。如有必要,您可以使用 exchange 方法显式提供 Accept 头部。
默认情况下,RestTemplate 会根据类路径检查注册所有内置的
消息转换器,这些检查有助于确定哪些可选的转换库已存在。你也可以显式地设置要使用的消息转换器。
消息转换
spring-web 模块包含了 HttpMessageConverter 接口,用于通过 InputStream 和 OutputStream 读取和写入 HTTP 请求与响应的正文。
HttpMessageConverter 实例在客户端(例如,在 RestTemplate 中)和服务器端(例如,在 Spring MVC 的 REST 控制器中)都会被使用。
框架中提供了主要媒体(MIME)类型的具體實現,默認情況下,這些實現會在客戶端註冊到 RestTemplate,並在服務器端註冊到 RequestMappingHandlerAdapter(參見
配置消息轉換器)。
HttpMessageConverter 的实现将在以下各节中进行描述。
对于所有转换器,都会使用一个默认的媒体类型,但你可以通过设置
supportedMediaTypes bean 属性来覆盖它。下表描述了每种实现:
| 消息转换器 | <description> </description> |
|---|---|
|
一种 |
|
一种 |
|
一种 |
|
一种 |
|
一种 |
|
一种 |
|
一种 |
|
一种 |
Jackson JSON 视图
您可以指定一个Jackson JSON 视图, 以仅序列化对象属性的一个子集,如下例所示:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
RequestEntity<MappingJacksonValue> requestEntity =
RequestEntity.post(new URI("https://example.com/user")).body(value);
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
多部分(Multipart)
要发送 multipart 数据,您需要提供一个 MultiValueMap<String, Object>,其值可以是用于部分(part)内容的 Object、用于文件部分的 Resource,或用于带有头部信息的部分内容的 HttpEntity。例如:
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
在大多数情况下,您无需为每个部分指定 Content-Type。内容类型会根据用于序列化该部分的 HttpMessageConverter 自动确定,或者在基于 Resource 的情况下,根据文件扩展名自动确定。如有必要,您可以使用 MediaType 包装器显式提供 HttpEntity。
一旦 MultiValueMap 准备就绪,你就可以将其传递给 RestTemplate,如下所示:
MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);
如果 MultiValueMap 包含至少一个非 String 的值,则 Content-Type 将由 FormHttpMessageConverter 设置为 multipart/form-data。如果 MultiValueMap 具有 String 个值,则 Content-Type 将默认为 application/x-www-form-urlencoded。如有必要,也可以显式设置 Content-Type。
HTTP 接口
Spring 框架允许你将 HTTP 服务定义为一个 Java 接口,其中使用注解标注的方法用于处理 HTTP 通信。然后,你可以生成一个代理来实现该接口并执行这些通信操作。这有助于简化 HTTP 远程访问,而此类访问通常涉及一个外观(facade),用于封装底层 HTTP 客户端的使用细节。
首先,声明一个包含 @HttpExchange 方法的接口:
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
其次,创建一个代理,用于执行所声明的 HTTP 交换:
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
RepositoryService service = factory.createClient(RepositoryService.class);
@HttpExchange 在类型级别上受支持,适用于所有方法:
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
interface RepositoryService {
@GetExchange
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
@PatchExchange(contentType = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void updateRepository(@PathVariable String owner, @PathVariable String repo,
@RequestParam String name, @RequestParam String description, @RequestParam String homepage);
}
方法参数
带注解的 HTTP 交换方法支持灵活的方法签名,可使用以下方法参数:
| 方法参数 | <description> </description> |
|---|---|
|
动态设置请求的 URL,覆盖注解中的 |
|
动态设置请求的 HTTP 方法,覆盖注解中的 |
|
添加一个请求头或多个请求头。参数可以是包含多个请求头的 |
|
添加一个变量,用于在请求 URL 中展开占位符。该参数可以是一个包含多个变量的 |
|
以待序列化的对象形式提供请求体,或者以响应式流(Reactive Streams) |
|
添加一个或多个请求参数。参数可以是包含多个参数的 当 |
|
添加一个请求部分,该部分可以是 String(表单字段)、 |
|
添加一个或多个 Cookie。参数可以是一个包含多个 Cookie 的 |
返回值
带注解的 HTTP 交换方法支持以下返回值:
| 方法返回值 | <description> </description> |
|---|---|
|
执行给定的请求,并释放响应内容(如果有的话)。 |
|
执行给定的请求,释放响应内容(如果有的话),并返回响应头。 |
|
执行给定的请求,并将响应内容解码为声明的返回类型。 |
|
执行给定的请求,并将响应内容解码为所声明元素类型的流。 |
|
执行给定的请求,释放响应内容(如果有的话),并返回一个包含状态和头部信息的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回一个包含状态、头部信息和解码后响应体的 |
|
执行给定的请求,将响应内容解码为声明元素类型的流,并返回一个包含状态、头部信息和已解码响应体流的 |
你也可以使用在 ReactiveAdapterRegistry 中注册的任何其他异步或响应式类型。 |
异常处理
默认情况下,WebClient 在遇到 4xx 和 5xx HTTP 状态码时会抛出 WebClientResponseException 异常。要自定义此行为,您可以注册一个响应状态处理器,该处理器将应用于通过该客户端执行的所有响应:
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(clientAdapter).build();
有关更多详细信息和选项(例如抑制错误状态码),请参见 defaultStatusHandler 中 WebClient.Builder 的 Javadoc。