此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
REST 客户端
Spring Framework 提供了以下用于调用 REST 端点的选项:
-
RestClient
— 具有流畅 API 的同步客户端 -
WebClient
— 具有流畅 API 的非阻塞、响应式客户端 -
RestTemplate
— 带有模板方法 API 的同步客户端 -
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()
,head()
,post()
,等,或method(HttpMethod)
.
请求 URL
接下来,使用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 的更多详细信息,请参阅 URI 链接。
请求标头和正文
如有必要,可以通过添加请求标头来作 HTTP 请求header(String, String)
,headers(Consumer<HttpHeaders>
,或使用方便的方法accept(MediaType…)
,acceptCharset(Charset…)
等等。
对于可以包含正文 (POST
,PUT
和PATCH
),可以使用其他方法:contentType(MediaType)
和contentLength(long)
.
如果客户端配置了ApiVersionInserter
.
请求正文本身可以通过body(Object)
,它在内部使用 HTTP 消息转换。
或者,可以使用ParameterizedTypeReference
,允许您使用泛型。
最后,可以将正文设置为一个回调函数,该函数写入OutputStream
.
检索响应
设置请求后,可以通过链接方法调用来发送请求。retrieve()
.
例如,可以使用retrieve().body(Class)
或retrieve().body(ParameterizedTypeReference)
对于列表等参数化类型。
这body
方法将响应内容转换为各种类型——例如,字节可以转换为String
,可以使用 Jackson 将 JSON 转换为对象,依此类推(参见 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
header 设置为 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 header 到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 header 到application/json |
3 | 将 JSON 响应转换为Pet 域对象 |
在下一个示例中,RestClient
用于执行包含 JSON 的 POST 请求,该请求再次使用 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 header 到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 header 到application/json |
4 | 用pet 作为请求正文 |
5 | 将响应转换为没有正文的响应实体。 |
错误处理
默认情况下,RestClient
抛出一个RestClientException
检索带有 4xx 或 5xx 状态代码的响应时。
可以使用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()
方法,可以使用它来代替retrieve()
.
使用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
,基于文件扩展名。
如有必要,可以显式提供MediaType
使用HttpEntity
包装纸。
一旦MultiValueMap
准备就绪,可以将其用作POST
请求, 使用RestClient.post().body(parts)
(或RestTemplate.postForObject
).
如果MultiValueMap
包含至少一种非String
value,则Content-Type
设置为multipart/form-data
通过FormHttpMessageConverter
.
如果MultiValueMap
有String
值,则Content-Type
默认为application/x-www-form-urlencoded
.
如有必要,Content-Type
也可以显式设置。
客户端请求工厂
要执行 HTTP 请求,RestClient
使用客户端 HTTP 库。这些库通过ClientRequestFactory
接口。
有多种实现方式可供选择:
-
JdkClientHttpRequestFactory
对于 Java 的HttpClient
-
HttpComponentsClientHttpRequestFactory
用于 Apache HTTP 组件HttpClient
-
JettyClientHttpRequestFactory
对于 Jetty'sHttpClient
-
ReactorNettyClientRequestFactory
用于 Reactor Netty'sHttpClient
-
SimpleClientHttpRequestFactory
作为简单的默认值
如果未指定请求工厂,则在RestClient
已构建,它将使用 Apache 或 JettyHttpClient
如果它们在类路径上可用。否则,如果java.net.http
模块,它将使用 Java 的HttpClient
. 最后,它将诉诸简单的默认值。
请注意,SimpleClientHttpRequestFactory 在访问表示错误的响应状态时可能会引发异常(例如,401)。如果这是一个问题,请使用任何备用请求工厂。 |
WebClient
WebClient
是一个非阻塞、响应式客户端,用于执行 HTTP 请求。它是在 5.0 中引入,并提供了RestTemplate
,支持同步、异步和流式处理方案。
WebClient
支持以下内容:
-
无阻塞 I/O
-
反应流背压
-
高并发,硬件资源少
-
利用 Java 8 lambda 的函数式流畅 API
-
同步和异步交互
-
流式传输到服务器或从服务器流式传输
有关更多详细信息,请参阅 WebClient。
RestTemplate
这RestTemplate
以经典 Spring Template 类的形式提供基于 HTTP 客户端库的高级 API。
它公开了以下重载方法组:
这RestClient 为同步 HTTP 访问提供了更现代的 API。
对于异步和流式处理方案,请考虑响应式 WebClient。 |
方法组 | 描述 |
---|---|
|
通过 GET 检索表示。 |
|
检索一个 |
|
使用 HEAD 检索资源的所有标头。 |
|
使用 POST 创建新资源并返回 |
|
使用 POST 创建新资源,并从响应中返回表示形式。 |
|
使用 POST 创建新资源,并从响应中返回表示形式。 |
|
使用 PUT 创建或更新资源。 |
|
使用 PATCH 更新资源,并从响应中返回表示形式。
请注意,JDK |
|
使用 DELETE 删除指定 URI 处的资源。 |
|
使用 ALLOW 检索资源允许的 HTTP 方法。 |
|
上述方法的更通用(且不那么固执己见)版本,可在需要时提供额外的灵活性。
它接受一个 这些方法允许使用 |
|
执行请求的最通用方式,完全控制请求 通过回调接口进行准备和响应提取。 |
初始化
RestTemplate
使用与RestClient
.
默认情况下,它使用SimpleClientHttpRequestFactory
,但这可以通过构造函数进行更改。
请参阅客户端请求工厂。
RestTemplate 可以检测以实现可观测性,以生成指标和跟踪。
请参阅 RestTemplate 可观测性支持部分。 |
身体
传入和返回的对象RestTemplate
方法与 HTTP 消息进行转换和从 HTTP 消息转换
在HttpMessageConverter
,请参阅 HTTP 消息转换。
从RestTemplate
自RestClient
下表显示RestClient
等效项RestTemplate
方法。
它可用于从后者迁移到前者。
RestTemplate 方法 |
RestClient 等效 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 接口客户端
您可以将 HTTP 服务定义为 Java 接口,并使用@HttpExchange
方法,并使用HttpServiceProxyFactory
从中创建一个客户端代理,以便通过 HTTP 进行远程访问RestClient
,WebClient
或RestTemplate
.在服务器端,一个@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...
方法参数
@HttpExchange
方法支持具有以下输入的灵活方法签名:
方法参数 | 描述 |
---|---|
|
动态设置请求的 URL,覆盖注释的 |
|
提供一个 |
|
动态设置请求的 HTTP 方法,覆盖注释的 |
|
添加一个或多个请求标头。参数可以是单个值,
一个 |
|
添加一个变量,用于在请求 URL 中展开占位符。参数可以是 |
|
提供 |
|
将请求的正文作为要序列化的对象提供,或者将请求的正文作为
反应流 |
|
添加一个或多个请求参数。参数可以是 什么时候 |
|
添加一个请求部分,可以是 String(表单字段), |
|
从 |
|
添加一个或多个 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
和setErrorHandler
之RestTemplate
.
装饰适配器
HttpExchangeAdapter
和ReactorHttpExchangeAdapter
是解耦 HTTP 的合约
从调用底层的详细信息接口客户端基础结构
客户。有适配器实现RestClient
,WebClient
和RestTemplate
.
有时,通过装饰器拦截客户端调用可能很有用
可在HttpServiceProxyFactory.Builder
.例如,您可以申请
内置装饰器来抑制 404 异常并返回ResponseEntity
跟NOT_FOUND
和null
身体:
// 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 Framework 提供了 专用配置支持。它使应用程序能够专注于识别 HTTP 服务 按组,并为每个组自定义客户端,而框架透明 创建客户端代理的注册表,并将每个代理声明为 Bean。
HTTP 服务组只是一组接口,它们共享相同的客户端设置和HttpServiceProxyFactory
实例创建代理。通常,这意味着每个组
主机,但您可以为同一目标主机设置多个组,以防
底层客户端需要以不同的方式配置。
声明 HTTP 服务组的一种方法是通过@ImportHttpServices
注释@Configuration
类,如下所示:
@Configuration
@ImportHttpServices(group = "echo", types = {EchoServiceA.class, EchoServiceB.class}) (1)
@ImportHttpServices(group = "greeting", basePackageClasses = GreetServiceA.class) (2)
public class ClientConfig {
}
1 | 手动列出组“echo”的接口 |
2 | 检测基本包下组“问候语”的接口 |
上面允许您声明 HTTP 服务和组。作为替代方案,您还可以注释 HTTP 接口,如下所示:
@HttpServiceClient("echo")
public interface EchoServiceA {
// ...
}
@HttpServiceClient("echo")
public interface EchoServiceB {
// ...
}
上述需要专门的进口注册商,如下所示:
public class MyClientHttpServiceRegistrar implements AbstractClientHttpServiceRegistrar { (1)
@Override
protected void registerHttpServices(GroupRegistry registry, AnnotationMetadata metadata) {
findAndRegisterHttpServiceClients(groupRegistry, List.of("org.example.echo")); (2)
}
}
@Configuration
@Import(MyClientHttpServiceRegistrar.class) (3)
public class ClientConfig {
}
1 | 扩展专用AbstractClientHttpServiceRegistrar |
2 | 指定要查找客户端接口的基本包 |
3 | 导入注册商 |
@HttpServiceClient 接口被排除在@ImportHttpServices scans,因此当指向同一包时,与客户端接口的扫描没有重叠。 |
也可以通过创建 HTTP 服务registrar 然后导入它来以编程方式声明组:
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 | 检测基本包下组“问候语”的接口 |
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 Service 组、Spring Security 添加 OAuth 支持和 Spring Cloud 添加负载
平衡。 |
由于上述原因,每个客户端代理都可以作为一个 Bean 使用,您可以 方便地按类型自动接线:
@RestController
public class EchoController {
private final EchoService echoService;
public EchoController(EchoService echoService) {
this.echoService = echoService;
}
// ...
}
但是,如果有多个相同类型的客户端代理,例如同一接口
在多个组中,则没有该类型的唯一 bean,并且您无法通过
仅键入。对于这种情况,您可以直接使用HttpServiceProxyRegistry
那
保存所有代理,并按组获取您需要的代理:
@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 | 访问EchoService 组“echo1”的客户端代理 |
2 | 访问EchoService 组“echo2”的客户端代理 |
RequestEntity
方法、URI、标头和正文必须提供给RestClient
通过method(HttpMethod)
,uri(URI)
,headers(Consumer<HttpHeaders>)
和body(Object)
.