|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
REST 客户端
Spring 框架提供了以下几种调用 REST 端点的方式:
-
WebClient- 非阻塞、响应式客户端,具有流畅的API。 -
RestTemplate- 具有模板方法API的同步客户端。 -
HTTP 接口 - 带有生成的动态代理实现的注解接口。
WebClient
WebClient 是一个用于执行 HTTP 请求的非阻塞、响应式客户端。它在 5.0 版本中引入,为 RestTemplate 提供了替代方案,支持同步、异步和流式场景。
WebClient 支持以下内容:
-
非阻塞 I/O。
-
响应式流的背压。
-
高并发且硬件资源更少。
-
功能式的、流畅的API,利用了Java 8的lambda表达式。
-
同步和异步交互。
-
从服务器上流式上传或流式下载。
请参阅 WebClient 了解更多信息。
RestTemplate
RestTemplate 在HTTP客户端库之上提供了更高级的API。它使得在一行代码中调用REST端点变得非常简单。它公开了以下几组重载方法:
RestTemplate 处于维护模式,仅接受针对微小更改和错误的请求。请考虑改用
WebClient。 |
| 方法组 | 描述 |
|---|---|
|
通过 GET 方法获取表示。 |
|
通过使用GET方法获取一个<code>0</code>(即状态、标题和正文)。 |
|
通过 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 客户端库的配置选项 — 例如,用于凭据、连接池和其他细节。
注意,HTTP 请求的 java.net 实现在访问表示错误(如 401)的响应状态时可能会抛出异常。如果这是个问题,请改用其他 HTTP 客户端库。 |
RestTemplate 可以被用于可观测性,以生成指标和跟踪信息。
请参阅 RestTemplate 可观测性支持 部分。 |
统一资源标识符
许多 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 属性来自定义 URIs 的编码方式。或者,您可以准备一个 java.net.URI 并将其传递到接受 URI 的其中一个 RestTemplate 方法中。
有关使用和编码URI的更多详细信息,请参阅 URI链接。
标题
您可以使用 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 模块包含通过 InputStream 和 OutputStream 读取和写入 HTTP 请求和响应正文的 HttpMessageConverter 接口。
HttpMessageConverter 实例在客户端(例如,在 RestTemplate 中)和服务器端(例如,在 Spring MVC REST 控制器中)使用。
框架中提供了主要媒体(MIME)类型的具体实现,并且默认情况下在客户端注册为RestTemplate,在服务器端注册为RequestMappingHandlerAdapter(参见
配置消息转换器)。
HttpMessageConverter 的实现方法在以下章节中进行了描述。
对于所有转换器,都会使用默认的媒体类型,但您可以通过设置
supportedMediaTypes bean 属性来覆盖它。下表描述了每种实现:
| MessageConverter | 描述 |
|---|---|
|
一个可以从HTTP请求和响应中读取和写入 |
|
一个可以从HTTP请求和响应中读取和写入表单数据的 |
|
一个可以从HTTP请求和响应中读取和写入字节数组的 |
|
一个 |
|
一个可以使用 Jackson 的 |
|
一个可以使用Jackson XML扩展的 |
|
一个可以读取和写入HTTP请求和响应中 |
|
一个可以读取和写入HTTP请求和响应中的 |
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);
多部分
要发送多部分数据,您需要提供一个 MultiValueMap<String, Object>,其值可以是 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 的值,则由 FormHttpMessageConverter 将 Content-Type 设置为 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 交换方法支持灵活的方法签名,可包含以下方法参数:
| 方法参数 | 描述 |
|---|---|
|
动态设置请求的URL,覆盖注解的 |
|
动态设置请求的HTTP方法,覆盖注解的 |
|
添加一个请求头或多个请求头。参数可以是带有多个头的 |
|
添加一个变量以在请求URL中展开占位符。该参数可以是多个变量时的 |
|
将请求的正文提供为要序列化的对象,或作为响应式流 |
|
添加一个请求参数或多个参数。该参数可以是多个参数的 当将 |
|
添加一个请求部分,该部分可以是字符串(表单字段)、 |
|
添加一个或多个cookie。该参数可以是多个cookie的 |
返回值
使用注解的 HTTP 交换方法支持以下返回值:
| 方法返回值 | 描述 |
|---|---|
|
执行给定的请求,并释放响应内容(如果有)。 |
|
执行给定的请求,释放响应内容(如果有),并返回响应头。 |
|
执行给定的请求,并将响应内容解码为声明的返回类型。 |
|
执行给定的请求,并将响应内容解码为声明的元素类型的流。 |
|
执行给定的请求,并释放响应内容(如果有),然后返回一个包含状态和头部信息的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回一个包含状态、头部和解码后主体的 |
|
执行给定的请求,将响应内容解码为声明的元素类型的流,并返回一个包含状态、头部和解码后的响应体流的 |
您也可以使用在
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();
有关更多详细信息和选项(例如抑制错误状态码),请参阅 WebClient.Builder 中 defaultStatusHandler 的 Javadoc。