REST 客户端
Spring 框架提供了以下几种调用 REST 端点的方式:
-
RestClient— 同步客户端,具有流畅的API -
WebClient— 非阻塞、响应式客户端,具有流畅的API -
RestTemplate— 同步客户端,使用模板方法API,现已不推荐使用,建议改用RestClient -
HTTP 服务客户端 — 由生成的代理支持的注解接口
RestClient
RestClient 是一个同步HTTP客户端,提供了执行请求的流畅API。
它作为HTTP库的抽象层,处理HTTP请求和响应内容与更高层次Java对象之间的转换。
创建一个RestClient
RestClient 具有静态的 create 快捷方法。
它还提供了一个 builder() 以提供更多选项:
一旦创建,RestClient就可以在多个线程中安全使用。
以下展示了如何创建或构建一个RestClient:
-
Java
-
Kotlin
RestClient defaultClient = RestClient.create();
RestClient customClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.messageConverters(converters -> converters.add(new MyCustomMessageConverter()))
.baseUrl("https://example.com")
.defaultUriVariables(Map.of("variable", "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.defaultVersion("1.2")
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build();
val defaultClient = RestClient.create()
val customClient = RestClient.builder()
.requestFactory(HttpComponentsClientHttpRequestFactory())
.messageConverters { converters -> converters.add(MyCustomMessageConverter()) }
.baseUrl("https://example.com")
.defaultUriVariables(mapOf("variable" to "foo"))
.defaultHeader("My-Header", "Foo")
.defaultCookie("My-Cookie", "Bar")
.defaultVersion("1.2")
.apiVersionInserter(ApiVersionInserter.fromHeader("API-Version").build())
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
使用 RestClient
执行HTTP请求前,首先需要指定要使用的HTTP方法。可以使用便捷方法如GET、POST、PUT等,或是使用request()方法。
请求URL
接下来,使用 uri 方法指定请求URI。
这是可选的,如果您已经通过构建器配置了baseUrl,可以跳过这一步。
URL通常以 String 的形式指定,可包含可选的URI模板变量。
以下是如何执行请求的示例:
-
Java
-
Kotlin
int id = 42;
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
val id = 42
restClient.get()
.uri("https://example.com/orders/{id}", id)
// ...
还可以使用函数进行更多控制,例如指定请求参数。
字符串URL默认会被编码,但可以通过使用自定义的 uriBuilderFactory 构建客户端来更改此行为。
URL也可以通过函数或作为 java.net.URI 提供,这两种方式均不会被编码。
有关处理和编码URI的更多详细信息,请参见 URI链接。
请求头和请求体
如有需要,可以通过添加请求头的方式操纵HTTP请求,使用header(String, String)、headers(Consumer<HttpHeaders>或便捷方法accept(MediaType…)、acceptCharset(Charset…)等进行操作。
对于可以包含正文的HTTP请求(POST、PUT、PATCH),还有额外的方法可用:contentType(MediaType)和contentLength(long)。
如果客户端配置了ApiVersionInserter,则可以为请求设置API版本。
请求体本身可以通过 body(Object) 来设置,其内部使用了 HTTP 消息转换。
或者,可以使用 ParameterizedTypeReference 来设置请求体,从而允许你使用泛型。
最后,可以将请求体设置为一个回调函数,该函数向 OutputStream 写入数据。
获取响应
请求设置完成后,可以通过在retrieve()之后链接方法调用来发送请求。
例如,可以使用retrieve().body(Class)或retrieve().body(ParameterizedTypeReference)来访问响应体,适用于列表等参数化类型。
body方法将响应内容转换为多种类型——比如,字节可以转换为String,JSON可以使用Jackson转换为对象等(参见HTTP消息转换)。
响应也可以转换为一个ResponseEntity,从而可以访问响应头以及正文,使用retrieve().toEntity(Class)
调用 retrieve() 本身是一个无操作,返回 ResponseSpec。
应用程序必须在 ResponseSpec 上调用一个终端操作以产生任何副作用。
如果消费响应对您的用例没有兴趣,您可以使用 retrieve().toBodilessEntity()。 |
此示例演示了如何使用 RestClient 执行简单的 GET 请求。
-
Java
-
Kotlin
String result = restClient.get() (1)
.uri("https://example.com") (2)
.retrieve() (3)
.body(String.class); (4)
System.out.println(result); (5)
| 1 | 设置一个 GET 请求 |
| 2 | 指定要连接的 URL |
| 3 | 获取响应 |
| 4 | 将响应转换为字符串 |
| 5 | 打印结果 |
val result= restClient.get() (1)
.uri("https://example.com") (2)
.retrieve() (3)
.body<String>() (4)
println(result) (5)
| 1 | 设置一个 GET 请求 |
| 2 | 指定要连接的 URL |
| 3 | 获取响应 |
| 4 | 将响应转换为字符串 |
| 5 | 打印结果 |
通过 ResponseEntity 可以访问响应状态码和响应头:
-
Java
-
Kotlin
ResponseEntity<String> result = restClient.get() (1)
.uri("https://example.com") (1)
.retrieve()
.toEntity(String.class); (2)
System.out.println("Response status: " + result.getStatusCode()); (3)
System.out.println("Response headers: " + result.getHeaders()); (3)
System.out.println("Contents: " + result.getBody()); (3)
| 1 | 为指定的 URL 设置一个 GET 请求 |
| 2 | 将响应转换为 ResponseEntity |
| 3 | 打印结果 |
val result = restClient.get() (1)
.uri("https://example.com") (1)
.retrieve()
.toEntity<String>() (2)
println("Response status: " + result.statusCode) (3)
println("Response headers: " + result.headers) (3)
println("Contents: " + result.body) (3)
| 1 | 为指定的 URL 设置一个 GET 请求 |
| 2 | 将响应转换为 ResponseEntity |
| 3 | 打印结果 |
RestClient 可以使用 Jackson 库将 JSON 转换为对象。
请注意本示例中 URI 变量的用法,以及 Accept 头部被设置为 JSON。
-
Java
-
Kotlin
int id = ...;
Pet pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) (1)
.accept(APPLICATION_JSON) (2)
.retrieve()
.body(Pet.class); (3)
| 1 | 使用 URI 变量 |
| 2 | 将 Accept 头设置为 application/json |
| 3 | 将 JSON 响应转换为一个 Pet 领域对象 |
val id = ...
val pet = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id) (1)
.accept(APPLICATION_JSON) (2)
.retrieve()
.body<Pet>() (3)
| 1 | 使用 URI 变量 |
| 2 | 将 Accept 头设置为 application/json |
| 3 | 将 JSON 响应转换为一个 Pet 领域对象 |
在下一个示例中,RestClient用于执行一个包含JSON的POST请求,该JSON再次使用Jackson进行转换。
-
Java
-
Kotlin
Pet pet = ... (1)
ResponseEntity<Void> response = restClient.post() (2)
.uri("https://petclinic.example.com/pets/new") (2)
.contentType(APPLICATION_JSON) (3)
.body(pet) (4)
.retrieve()
.toBodilessEntity(); (5)
| 1 | 创建一个 Pet 域对象 |
| 2 | 设置一个 POST 请求,以及要连接的 URL |
| 3 | 将 Content-Type 头设置为 application/json |
| 4 | 使用 pet 作为请求体 |
| 5 | 将响应转换为没有主体的响应实体。 |
val pet: Pet = ... (1)
val response = restClient.post() (2)
.uri("https://petclinic.example.com/pets/new") (2)
.contentType(APPLICATION_JSON) (3)
.body(pet) (4)
.retrieve()
.toBodilessEntity() (5)
| 1 | 创建一个 Pet 域对象 |
| 2 | 设置一个 POST 请求,以及要连接的 URL |
| 3 | 将 Content-Type 头设置为 application/json |
| 4 | 使用 pet 作为请求体 |
| 5 | 将响应转换为没有主体的响应实体。 |
错误处理
默认情况下,当获取到状态码为 4xx 或 5xx 的响应时,RestClient 会抛出一个 RestClientException 的子类异常。
此行为可以通过使用 onStatus 来重写。
-
Java
-
Kotlin
String result = restClient.get() (1)
.uri("https://example.com/this-url-does-not-exist") (1)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { (2)
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); (3)
})
.body(String.class);
| 1 | 创建一个针对返回 404 状态码的 URL 的 GET 请求 |
| 2 | 为所有 4xx 状态码设置状态处理器 |
| 3 | 抛出自定义异常 |
val result = restClient.get() (1)
.uri("https://example.com/this-url-does-not-exist") (1)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError) { _, response -> (2)
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) } (3)
.body<String>()
| 1 | 创建一个针对返回 404 状态码的 URL 的 GET 请求 |
| 2 | 为所有 4xx 状态码设置状态处理器 |
| 3 | 抛出自定义异常 |
交换
对于更高级的场景,RestClient 通过 exchange() 方法提供了对底层 HTTP 请求和响应的访问,可用来替代 retrieve()。
当使用 exchange() 时,状态处理器不会被应用,因为 exchange 函数已经提供了对完整响应的访问,允许你执行所需的任何错误处理。
-
Java
-
Kotlin
Pet result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(APPLICATION_JSON)
.exchange((request, response) -> { (1)
if (response.getStatusCode().is4xxClientError()) { (2)
throw new MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()); (2)
}
else {
Pet pet = convertResponse(response); (3)
return pet;
}
});
| 1 | exchange 提供请求和响应 |
| 2 | 当响应的状态码为 4xx 时抛出异常 |
| 3 | 将响应转换为 Pet 领域对象 |
val result = restClient.get()
.uri("https://petclinic.example.com/pets/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.exchange { request, response -> (1)
if (response.getStatusCode().is4xxClientError()) { (2)
throw MyCustomRuntimeException(response.getStatusCode(), response.getHeaders()) (2)
} else {
val pet: Pet = convertResponse(response) (3)
pet
}
}
| 1 | exchange 提供请求和响应 |
| 2 | 当响应的状态码为 4xx 时抛出异常 |
| 3 | 将响应转换为 Pet 领域对象 |
HTTP消息转换
Jackson JSON 视图
要仅序列化对象属性的子集,可以指定一个 Jackson JSON 视图,如下例所示:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
ResponseEntity<Void> response = restClient.post() // or RestTemplate.postForEntity
.contentType(APPLICATION_JSON)
.body(value)
.retrieve()
.toBodilessEntity();
多部分
要发送多部分数据,您需要提供一个 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));
// send using RestClient.post or RestTemplate.postForEntity
在大多数情况下,您不必为每个部分指定 Content-Type。
内容类型会根据用于序列化的 HttpMessageConverter 自动确定,或者在 Resource 的情况下,根据文件扩展名确定。
如有必要,您可以使用 HttpEntity 包装器显式提供 MediaType。
一旦 MultiValueMap 准备就绪,你就可以将其用作 POST 请求的主体,使用 RestClient.post().body(parts)(或 RestTemplate.postForObject)。
如果 MultiValueMap 至少包含一个非 String 值,则 Content-Type 将由 FormHttpMessageConverter 设置为 multipart/form-data。
如果 MultiValueMap 具有 String 值,则 Content-Type 默认为 application/x-www-form-urlencoded。
如有必要,也可以显式设置 Content-Type。
客户端请求工厂
为了执行HTTP请求,RestClient 使用了一个客户端HTTP库。
这些库通过 ClientRequestFactory 接口进行适配。
有多种实现可用:
-
JdkClientHttpRequestFactory代表 Java 的HttpClient -
HttpComponentsClientHttpRequestFactory与 Apache HTTP 组件一起使用HttpClient -
JettyClientHttpRequestFactory用于 Jetty 的HttpClient -
ReactorNettyClientRequestFactory对应 Reactor Netty 的HttpClient -
SimpleClientHttpRequestFactory作为简单的默认值
如果在构建 RestClient 时未指定请求工厂,它将使用 Apache 或 Jetty 的 HttpClient(如果它们在类路径中可用)。
否则,如果已加载 java.net.http 模块,它将使用 Java 的 HttpClient。
最后,它将回退到简单的默认实现。
请注意,当访问表示错误的响应(例如,401)的状态时,SimpleClientHttpRequestFactory可能会引发异常。
如果这是个问题,请使用任何替代的请求工厂。 |
WebClient
WebClient 是一个用于执行 HTTP 请求的非阻塞、响应式客户端。它在 5.0 版本中引入,为 RestTemplate 提供了替代方案,支持同步、异步和流式场景。
WebClient 支持以下内容:
-
非阻塞 I/O
-
响应式流背压
-
使用较少的硬件资源实现高并发
-
利用lambda表达式的函数式、流畅API
-
同步和异步交互
-
从服务器流式上传或流式下载
请参阅 WebClient 了解更多信息。
RestTemplate
RestTemplate 以经典的 Spring 模板类形式,在 HTTP 客户端库之上提供了高级 API。
它公开了以下几组重载方法:
自Spring Framework 7.0起,RestTemplate已被弃用,建议使用RestClient,并将在未来版本中移除,请参考“迁移到RestClient”的指南。
对于异步和流式处理场景,请考虑使用响应式的WebClient。 |
| 方法组 | 描述 |
|---|---|
|
通过 GET 方法获取表示。 |
|
通过使用GET方法获取一个<code>0</code>(即状态、标题和正文)。 |
|
通过 HEAD 方法检索资源的所有标头。 |
|
通过使用 POST 创建新资源,并从响应中返回 |
|
通过使用 POST 创建一个新资源,并从响应中返回表示。 |
|
通过使用 POST 创建一个新资源,并从响应中返回表示。 |
|
通过 PUT 方法创建或更新资源。 |
|
通过使用 PATCH 更新资源并从响应中返回表示。
请注意,JDK |
|
通过使用DELETE方法删除指定URI处的资源。 |
|
通过使用 ALLOW 来获取资源允许的 HTTP 方法。 |
|
前面方法的更通用(且更不具倾向性)版本,在需要时提供额外的灵活性。它接受一个 这些方法允许使用 |
|
执行请求的最通用方式,通过回调接口对请求准备和响应提取进行完全控制。 |
初始化
RestTemplate 使用与 RestClient 相同的HTTP库抽象。
默认情况下,它使用 SimpleClientHttpRequestFactory,但可以通过构造函数更改此设置。
请参阅 客户端请求工厂。
RestTemplate 可以被用于可观测性,以生成指标和跟踪信息。
请参阅 RestTemplate 可观测性支持 部分。 |
正文
传递给 RestTemplate 方法以及从其返回的对象,会在 HttpMessageConverter 的帮助下转换为HTTP消息或从HTTP消息转换而来,参见 HTTP消息转换。
迁移到 RestClient 版本
应用程序可以逐步采用 RestClient,首先集中于API的使用,然后是基础设施的设置。
您可以考虑以下步骤:
-
从现有
RestTemplate实例创建一个或多个RestClient,例如:RestClient restClient = RestClient.create(restTemplate)。 逐步在您的应用程序中替换RestTemplate的使用,逐个组件地进行,首先专注于发出请求。 请参见下表以获取API对应项。 -
当所有客户端请求都通过
RestClient实例传递后,您可以开始通过使用RestClient.Builder来复制现有RestTemplate实例的创建。因为RestTemplate和RestClient共享相同的基础设施,您可以在设置中重用自定义的ClientHttpRequestFactory或ClientHttpRequestInterceptor。参见<code>7</code>构造器API。
如果类路径上没有其他库可用,RestClient 将选择由现代JDK HttpClient 支持的 JdkClientHttpRequestFactory,而 RestTemplate 会选用依赖 HttpURLConnection 的 SimpleClientHttpRequestFactory。这可以解释在HTTP层面运行时的细微行为差异。
下表展示了 RestClient 对应于 RestTemplate 方法的等价物。
RestTemplate 方法 |
RestClient 等效 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RestClient 和 RestTemplate 实例在抛出异常时表现出相同的行为(其中 RestClientException 类型位于层次结构的顶端)。
当 RestTemplate 一致地为 "4xx" 响应状态抛出 HttpClientErrorException 时,
RestClient 则通过自定义的 "状态处理器" 提供了更大的灵活性。
HTTP 服务客户端
您可以将HTTP服务定义为一个具有@HttpExchange个方法的Java接口,并使用
HttpServiceProxyFactory从该接口创建客户端代理,以通过
RestClient、WebClient或RestTemplate进行HTTP远程访问。在服务器端,一个@Controller类
可以实现相同的接口来处理带有
@HttpExchange
控制器方法的请求。
首先,创建Java接口:
public interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
可选地,在类型级别使用 @HttpExchange 声明所有方法的公共属性:
@HttpExchange(url = "/repos/{owner}/{repo}", accept = "application/vnd.github.v3+json")
public 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);
}
接下来,配置客户端并创建HttpServiceProxyFactory:
// Using RestClient...
RestClient restClient = RestClient.create("...");
RestClientAdapter adapter = RestClientAdapter.create(restClient);
// or WebClient...
WebClient webClient = WebClient.create("...");
WebClientAdapter adapter = WebClientAdapter.create(webClient);
// or RestTemplate...
RestTemplate restTemplate = new RestTemplate();
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
现在,您可以创建客户端代理了:
RepositoryService service = factory.createClient(RepositoryService.class);
// Use service methods for remote calls...
HTTP服务客户端是进行HTTP远程访问的强大而富有表现力的选择。 它允许一个团队掌握REST API如何工作、哪些部分对客户端应用程序相关、需要创建什么输入和输出类型、需要什么端点方法签名、应具备哪些Javadoc等知识。最终形成的Java API既提供了指导也随时可以投入使用。
方法参数
@HttpExchange 个方法支持以下输入的灵活方法签名:
| 方法参数 | 描述 |
|---|---|
|
动态设置请求的URL,覆盖注解的 |
|
提供一个 |
|
动态设置请求的HTTP方法,覆盖注解的 |
|
向请求中添加一个或多个头部信息。参数可以是单个值,也可以是 |
|
添加一个变量以在请求URL中展开占位符。该参数可以是多个变量时的 |
|
向请求属性中添加时提供一个 |
|
将请求的正文提供为要序列化的对象,或作为响应式流 |
|
添加一个请求参数或多个参数。该参数可以是多个参数的 当将 |
|
添加一个请求部分,可以是字符串(表单字段)、 |
|
从 |
|
添加一个或多个cookie。该参数可以是多个cookie的 |
方法参数不能为null,除非该参数注解上的required属性(在可用时)设置为false,或者根据MethodParameter#isOptional确定该参数为可选。
RestClientAdapter 为类型为
StreamingHttpOutputMessage.Body 的方法参数提供了额外支持,该参数允许通过写入
OutputStream 来发送请求体。
自定义参数
您可以配置一个自定义的HttpServiceArgumentResolver。下面的示例接口使用了自定义的Search方法参数类型:
-
Java
-
Kotlin
public interface RepositoryService {
@GetExchange("/repos/search")
List<Repository> searchRepository(Search search);
}
interface RepositoryService {
@GetExchange("/repos/search")
fun searchRepository(search: Search): List<Repository>
}
一个自定义的参数解析器可以这样实现:
-
Java
-
Kotlin
static class SearchQueryArgumentResolver implements HttpServiceArgumentResolver {
@Override
public boolean resolve(Object argument, MethodParameter parameter, HttpRequestValues.Builder requestValues) {
if (parameter.getParameterType().equals(Search.class)) {
Search search = (Search) argument;
requestValues.addRequestParameter("owner", search.owner());
requestValues.addRequestParameter("language", search.language());
requestValues.addRequestParameter("query", search.query());
return true;
}
return false;
}
}
class SearchQueryArgumentResolver : HttpServiceArgumentResolver {
override fun resolve(
argument: Any?,
parameter: MethodParameter,
requestValues: HttpRequestValues.Builder
): Boolean {
if (parameter.getParameterType() == Search::class.java) {
val search = argument as Search
requestValues.addRequestParameter("owner", search.owner)
.addRequestParameter("language", search.language)
.addRequestParameter("query", search.query)
return true
}
return false
}
}
要配置自定义参数解析器:
-
Java
-
Kotlin
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(new SearchQueryArgumentResolver())
.build();
RepositoryService repositoryService = factory.createClient(RepositoryService.class);
Search search = Search.create()
.owner("spring-projects")
.language("java")
.query("rest")
.build();
List<Repository> repositories = repositoryService.searchRepository(search);
val restClient = RestClient.builder().baseUrl("https://api.github.com/").build()
val adapter = RestClientAdapter.create(restClient)
val factory = HttpServiceProxyFactory
.builderFor(adapter)
.customArgumentResolver(SearchQueryArgumentResolver())
.build()
val repositoryService = factory.createClient<RepositoryService>(RepositoryService::class.java)
val search = Search(owner = "spring-projects", language = "java", query = "rest")
val repositories = repositoryService.searchRepository(search)
默认情况下,RequestEntity不支持作为方法参数,而是鼓励使用更细粒度的方法参数来处理请求的各个部分。 |
返回值
支持的返回值取决于底层客户端。
适配 HttpExchangeAdapter 的客户端(如 RestClient 和 RestTemplate)
支持同步返回值:
| 方法返回值 | 描述 |
|---|---|
|
执行给定的请求。 |
|
执行给定的请求并返回响应头。 |
|
执行给定的请求,并将响应内容解码为声明的返回类型。 |
|
执行给定的请求,并返回一个包含状态和头部信息的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回一个包含状态、头部和解码后主体的 |
适应了ReactorHttpExchangeAdapter的客户端,如WebClient,支持上述所有功能以及响应式变体。下表展示了Reactor类型,但您也可以使用通过ReactiveAdapterRegistry支持的其他响应式类型:
| 方法返回值 | 描述 |
|---|---|
|
执行给定的请求,并释放响应内容(如果有)。 |
|
执行给定的请求,释放响应内容(如果有),并返回响应头。 |
|
执行给定的请求,并将响应内容解码为声明的返回类型。 |
|
执行给定的请求,并将响应内容解码为声明的元素类型的流。 |
|
执行给定的请求,并释放响应内容(如果有),然后返回一个包含状态和头部信息的 |
|
执行给定的请求,将响应内容解码为声明的返回类型,并返回一个包含状态、头部和解码后主体的 |
|
执行给定的请求,将响应内容解码为声明的元素类型的流,并返回一个包含状态、头部和解码后的响应体流的 |
默认情况下,同步返回值的超时时间设置为 ReactorHttpExchangeAdapter 时,
取决于底层HTTP客户端的配置方式。您也可以在适配器级别设置一个 blockTimeout
值,但我们建议依赖底层HTTP客户端的超时设置,因为它在更低的层级上运行,并提供更多的控制能力。
RestClientAdapter 提供了对返回类型为
InputStream 或 ResponseEntity<InputStream> 的额外支持,这些类型可访问原始响应
体内容。
错误处理
为了自定义HTTP服务客户端代理的错误处理,您可以根据需要配置底层客户端。默认情况下,客户端会针对4xx和5xx的HTTP状态码抛出异常。要自定义此行为,可按照以下方式注册一个响应状态处理器,该处理器将应用于客户端执行的所有响应:
// For RestClient
RestClient restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
// or for WebClient...
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
// or for RestTemplate...
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
更多详细信息及选项(如抑制错误状态码),请参阅每个客户端的参考文档,以及defaultStatusHandler在RestClient.Builder或WebClient.Builder中的Javadoc,和RestTemplate的setErrorHandler。
装饰适配器
HttpExchangeAdapter 和 ReactorHttpExchangeAdapter 是契约,用于解耦HTTP接口客户端基础设施与调用底层客户端的具体细节。存在针对 RestClient、WebClient 和 RestTemplate 的适配器实现。
有时,通过在HttpServiceProxyFactory.Builder中配置的装饰器来拦截客户端调用可能会很有用。例如,您可以应用内置装饰器来抑制404异常,并返回一个带有NOT_FOUND和null内容体的ResponseEntity:
// For RestClient
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(restCqlientAdapter)
.exchangeAdapterDecorator(NotFoundRestClientAdapterDecorator::new)
.build();
// or for WebClient...
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builderFor(webClientAdapter)
.exchangeAdapterDecorator(NotFoundWebClientAdapterDecorator::new)
.build();
HTTP 服务组
使用HttpServiceProxyFactory创建客户端代理非常简单,但将它们声明为bean会导致配置重复。您可能还拥有多个目标主机,因此需要配置多个客户端,甚至需要创建更多的客户端代理bean。
为了便于在大规模场景下与接口客户端协作,Spring框架提供了专门的配置支持。这使得应用程序能够专注于按组识别HTTP服务,并为每个组定制客户端,同时框架透明地创建客户端代理的注册表,并将每个代理声明为一个bean。
一个HTTP服务组简单来说就是一组接口,它们共享相同的客户端设置和
HttpServiceProxyFactory 实例来创建代理。通常,这意味着每个主机有一个组,但如果你需要为同一目标主机配置不同底层客户端的情况,也可以拥有多个组。
声明HTTP服务组的一种方式是通过在配置类中使用@RestController注解,如下所示:
@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class}) (1)
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) (2)
public class ClientConfig {
}
| 1 | 手动为“echo”组列出接口 |
| 2 | 检测基础包下"group greeting"的接口 |
也可以通过创建一个 HTTP 服务注册器并导入它来以编程方式声明组:
public class MyHttpServiceRegistrar extends AbstractHttpServiceRegistrar { (1)
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
registry.forGroup("echo").register(EchoServiceA.class, EchoServiceB.class); (2)
registry.forGroup("greeting").detectInBasePackages(GreetServiceA.class); (3)
}
}
@Configuration
@Import(MyHttpServiceRegistrar.class) (4)
public class ClientConfig {
}
| 1 | 创建AbstractHttpServiceRegistrar的扩展类 |
| 2 | 手动为“echo”组列出接口 |
| 3 | 检测基础包下"group greeting"的接口 |
| 4 | 导入注册器 |
您可以将@ImportHttpService注解与程序注册器混合使用,
并且可以将导入分布在多个配置类中。所有导入
共同协作贡献相同的、共享的HttpServiceProxyRegistry实例。 |
一旦HTTP服务组被声明,添加一个HttpServiceGroupConfigurer bean来为每个组定制客户端。例如:
@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class})
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class)
public class ClientConfig {
@Bean
public RestClientHttpServiceGroupConfigurer groupConfigurer() {
return groups -> {
// configure client for group "echo"
groups.filterByName("echo").forEachClient((group, clientBuilder) -> ...);
// configure the clients for all groups
groups.forEachClient((group, clientBuilder) -> ...);
// configure client and proxy factory for each group
groups.forEachGroup((group, clientBuilder, factoryBuilder) -> ...);
};
}
}
Spring Boot 通过使用 HttpServiceGroupConfigurer 添加了对 HTTP 服务组客户端属性的支持,通过 Spring Security 添加了 OAuth 支持,并且通过 Spring Cloud 添加了负载均衡功能。 |
因此,每个客户端代理都作为一个 bean 可用,您可以方便地通过类型进行自动装配:
@RestController
public class EchoController {
private final EchoService echoService;
public EchoController(EchoService echoService) {
this.echoService = echoService;
}
// ...
}
然而,如果存在同一类型的多个客户端代理,例如多个组中的相同接口,那么该类型就没有唯一的bean,您不能仅通过类型进行自动装配。对于这种情况,您可以直接使用持有所有代理的ApplicationContext,并通过组来获取所需的代理:
@RestController
public class EchoController {
private final EchoService echoService1;
private final EchoService echoService2;
public EchoController(HttpServiceProxyRegistry registry) {
this.echoService1 = registry.getClient("echo1", EchoService.class); (1)
this.echoService2 = registry.getClient("echo2", EchoService.class); (2)
}
// ...
}
| 1 | 访问组 "echo1" 的 EchoService 号客户端代理 |
| 2 | 访问组 "echo2" 的 EchoService 号客户端代理 |
RequestEntity 方法、URI、请求头和请求体必须通过 RestClient、method(HttpMethod)、uri(URI) 和 headers(Consumer<HttpHeaders>) 提供给 body(Object)。