REST 客户端
Spring 框架提供了以下选项来调用 REST 端点:
-
RestClient - 具有流畅 API 的同步客户端。
-
WebClient - 具有流畅 API 的非阻塞反应式客户端。
-
RestTemplate - 具有模板方法 API 的同步客户端。
-
HTTP Interface - 带有生成的动态代理实现的注解接口。
RestClient
RestClient
是一个同步的 HTTP 客户端,提供现代化、流畅的 API。它对 HTTP 库进行了抽象,方便将 Java 对象转换为 HTTP 请求,并从 HTTP 响应中创建对象。
创建一个 RestClient
RestClient
是通过其中一个静态 create
方法创建的。您还可以使用 builder()
来获取一个具有更多选项的构建器,例如指定要使用的 HTTP 库(请参见 Client Request Factories)和要使用的消息转换器(请参见 HTTP Message Conversion),设置默认 URI、默认路径变量、默认请求头,或 uriBuilderFactory
,或注册拦截器和初始化器。
一旦创建(或构建),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")
.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")
.requestInterceptor(myCustomInterceptor)
.requestInitializer(myCustomInitializer)
.build()
使用 RestClient
在使用 RestClient
发起 HTTP 请求时,首先需要指定使用哪种 HTTP 方法。这可以通过 method(HttpMethod)
或者便利方法 get()
、head()
、post()
等来完成。
请求 URL
接下来,可以使用 uri
方法指定请求的 URI。此步骤是可选的,如果 RestClient
配置了默认 URI,则可以跳过。URL 通常指定为一个 String
,并可以包含可选的 URI 模板变量。以下示例配置了一个 GET 请求到 [example.com/orders/42](https://example.com/orders/42)
:
- 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 Links。
请求头和主体
如果需要,可以通过添加请求头来操控 HTTP 请求,使用 header(String, String)
、headers(Consumer<HttpHeaders>
,或者使用便利方法 accept(MediaType…)
、acceptCharset(Charset…)
等等。对于可以包含主体的 HTTP 请求(POST
、PUT
和 PATCH
),还有其他可用的方法:contentType(MediaType)
和 contentLength(long)
。
请求体本身可以通过 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
设置一个 GET 请求
指定要连接的 URL
获取响应
将响应转换为字符串
打印结果
val result= restClient.get() 1
.uri("https://example.com") 2
.retrieve() 3
.body<String>() 4
println(result) 5
设置一个 GET 请求
指定要连接的 URL
获取响应
将响应转换为字符串
打印结果
通过 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
为指定的 URL 设置一个 GET 请求
将响应转换为
ResponseEntity
打印结果
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
为指定的 URL 设置一个 GET 请求
将响应转换为
ResponseEntity
打印结果
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
使用 URI 变量
将
Accept
头设置为application/json
将 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
使用 URI 变量
将
Accept
头设置为application/json
将 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
创建一个
Pet
领域对象设置一个 POST 请求,以及要连接的 URL
将
Content-Type
头设置为application/json
使用
pet
作为请求体将响应转换为一个没有主体的响应实体。
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
创建一个
Pet
领域对象设置一个 POST 请求,以及要连接的 URL
将
Content-Type
头设置为application/json
使用
pet
作为请求体将响应转换为一个没有主体的响应实体。
错误处理
默认情况下,RestClient
在获取状态码为 4xx 或 5xx 的响应时,会抛出 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);
创建一个 GET 请求,针对返回 404 状态码的 URL
为所有 4xx 状态码设置状态处理程序
抛出自定义异常
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>()
创建一个 GET 请求,针对返回 404 状态码的 URL
为所有 4xx 状态码设置状态处理程序
抛出自定义异常
交易所
对于更高级的场景,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;
}
});
exchange
提供请求和响应当响应具有 4xx 状态码时抛出异常
将响应转换为 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
}
}
exchange
提供请求和响应当响应具有 4xx 状态码时抛出异常
将响应转换为 Pet 域对象
HTTP 消息转换
Jackson JSON 视图
要仅序列化对象属性的一个子集,可以指定一个 Jackson JSON View,如下例所示:
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();
Multipart
要发送多部分数据,您需要提供一个 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 Components 的HttpClient
-
JettyClientHttpRequestFactory
用于 Jetty 的HttpClient
-
ReactorNettyClientRequestFactory
用于 Reactor Netty 的HttpClient
-
SimpleClientHttpRequestFactory
作为一个简单的默认选项
如果在构建 RestClient
时未指定请求工厂,它将使用 Apache 或 Jetty 的 HttpClient
,前提是它们在类路径上可用。否则,如果加载了 java.net.http
模块,它将使用 Java 的 HttpClient
。最后,它将退回到简单的默认值。
请注意,SimpleClientHttpRequestFactory
在访问表示错误的响应状态时可能会引发异常(例如,401)。如果这是一个问题,请使用任何其他请求工厂。
WebClient
WebClient
是一个非阻塞的、反应式的客户端,用于执行 HTTP 请求。它在 5.0 中引入,提供了 RestTemplate
的替代方案,支持同步、异步和流式场景。
WebClient
支持以下内容:
-
非阻塞 I/O
-
响应式流背压
-
高并发,使用更少的硬件资源
-
函数式风格,流畅的 API,利用 Java 8 的 lambda 表达式
-
同步和异步交互
-
从服务器流式传输或向服务器流式传输
有关更多详细信息,请参见 WebClient。
RestTemplate
RestTemplate
提供了一个高层次的 API,封装了 HTTP 客户端库,形式为经典的 Spring 模板类。它暴露了以下几组重载方法:
RestClient 提供了一个更现代的 API 用于同步 HTTP 访问。对于异步和流式场景,请考虑反应式的 WebClient。
表 1. RestTemplate 方法
方法组 | 描述 |
---|---|
getForObject | 通过 GET 检索表示。 |
getForEntity | 通过 GET 检索 ResponseEntity (即状态、头部和主体)。 |
headForHeaders | 通过 HEAD 检索资源的所有头部。 |
postForLocation | 通过 POST 创建新资源,并返回响应中的 Location 头部。 |
postForObject | 通过 POST 创建新资源,并返回响应中的表示。 |
postForEntity | 通过 POST 创建新资源,并返回响应中的表示。 |
put | 通过 PUT 创建或更新资源。 |
patchForObject | 通过 PATCH 更新资源,并返回响应中的表示。请注意,JDK 的 HttpURLConnection 不支持 PATCH ,但 Apache HttpComponents 等支持。 |
delete | 通过 DELETE 删除指定 URI 的资源。 |
optionsForAllow | 通过 ALLOW 检索资源的允许 HTTP 方法。 |
exchange | 前面方法的更通用(且更少偏见)版本,在需要时提供额外的灵活性。它接受一个 RequestEntity (包括 HTTP 方法、URL、头部和主体作为输入)并返回一个 ResponseEntity 。这些方法允许使用 ParameterizedTypeReference 代替 Class 来指定带有泛型的响应类型。 |
execute | 执行请求的最通用方式,通过回调接口对请求准备和响应提取进行完全控制。 |
初始化
RestTemplate
使用与 RestClient
相同的 HTTP 库抽象。默认情况下,它使用 SimpleClientHttpRequestFactory
,但可以通过构造函数进行更改。请参见 Client Request Factories。
RestTemplate
可以进行观察性仪表化,以生成指标和追踪。请参见 RestTemplate 观察性支持 部分。
Body
传递给 RestTemplate
方法的对象和从中返回的对象通过 HttpMessageConverter
转换为 HTTP 消息,详见 HTTP 消息转换。
从 RestTemplate
迁移到 RestClient
下表显示了 RestTemplate
方法的 RestClient
等效项。它可以用于从后者迁移到前者。
表 2. RestClient 等价于 RestTemplate 方法
RestTemplate 方法 | RestClient 等价物 |
---|---|
getForObject(String, Class, Object…) | get() .uri(String, Object…) .retrieve() .body(Class) |
getForObject(String, Class, Map) | get() .uri(String, Map) .retrieve() .body(Class) |
getForObject(URI, Class) | get() .uri(URI) .retrieve() .body(Class) |
getForEntity(String, Class, Object…) | get() .uri(String, Object…) .retrieve() .toEntity(Class) |
getForEntity(String, Class, Map) | get() .uri(String, Map) .retrieve() .toEntity(Class) |
getForEntity(URI, Class) | get() .uri(URI) .retrieve() .toEntity(Class) |
headForHeaders(String, Object…) | head() .uri(String, Object…) .retrieve() .toBodilessEntity() .getHeaders() |
headForHeaders(String, Map) | head() .uri(String, Map) .retrieve() .toBodilessEntity() .getHeaders() |
headForHeaders(URI) | head() .uri(URI) .retrieve() .toBodilessEntity() .getHeaders() |
postForLocation(String, Object, Object…) | post() .uri(String, Object…) .body(Object).retrieve() .toBodilessEntity() .getLocation() |
postForLocation(String, Object, Map) | post() .uri(String, Map) .body(Object) .retrieve() .toBodilessEntity() .getLocation() |
postForLocation(URI, Object) | post() .uri(URI) .body(Object) .retrieve() .toBodilessEntity() .getLocation() |
postForObject(String, Object, Class, Object…) | post() .uri(String, Object…) .body(Object) .retrieve() .body(Class) |
postForObject(String, Object, Class, Map) | post() .uri(String, Map) .body(Object) .retrieve() .body(Class) |
postForObject(URI, Object, Class) | post() .uri(URI) .body(Object) .retrieve() .body(Class) |
postForEntity(String, Object, Class, Object…) | post() .uri(String, Object…) .body(Object) .retrieve() .toEntity(Class) |
postForEntity(String, Object, Class, Map) | post() .uri(String, Map) .body(Object) .retrieve() .toEntity(Class) |
postForEntity(URI, Object, Class) | post() .uri(URI) .body(Object) .retrieve() .toEntity(Class) |
put(String, Object, Object…) | put() .uri(String, Object…) .body(Object) .retrieve() .toBodilessEntity() |
put(String, Object, Map) | put() .uri(String, Map) .body(Object) .retrieve() .toBodilessEntity() |
put(URI, Object) | put() .uri(URI) .body(Object) .retrieve() .toBodilessEntity() |
patchForObject(String, Object, Class, Object…) | patch() .uri(String, Object…) .body(Object) .retrieve() .body(Class) |
patchForObject(String, Object, Class, Map) | patch() .uri(String, Map) .body(Object) .retrieve() .body(Class) |
patchForObject(URI, Object, Class) | patch() .uri(URI) .body(Object) .retrieve() .body(Class) |
delete(String, Object…) | delete() .uri(String, Object…) .retrieve() .toBodilessEntity() |
delete(String, Map) | delete() .uri(String, Map) .retrieve() .toBodilessEntity() |
delete(URI) | delete() .uri(URI) .retrieve() .toBodilessEntity() |
optionsForAllow(String, Object…) | options() .uri(String, Object…) .retrieve() .toBodilessEntity() .getAllow() |
optionsForAllow(String, Map) | options() .uri(String, Map) .retrieve() .toBodilessEntity() .getAllow() |
optionsForAllow(URI) | options() .uri(URI) .retrieve() .toBodilessEntity() .getAllow() |
exchange(String, HttpMethod, HttpEntity, Class, Object…) | method(HttpMethod) .uri(String, Object…) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1] |
exchange(String, HttpMethod, HttpEntity, Class, Map) | method(HttpMethod) .uri(String, Map) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1] |
exchange(URI, HttpMethod, HttpEntity, Class) | method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [1] |
exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Object…) | method(HttpMethod) .uri(String, Object…) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1] |
exchange(String, HttpMethod, HttpEntity, ParameterizedTypeReference, Map) | method(HttpMethod) .uri(String, Map) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1] |
exchange(URI, HttpMethod, HttpEntity, ParameterizedTypeReference) | method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [1] |
exchange(RequestEntity, Class) | method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(Class) [2] |
exchange(RequestEntity, ParameterizedTypeReference) | method(HttpMethod) .uri(URI) .headers(Consumer<HttpHeaders>) .body(Object) .retrieve() .toEntity(ParameterizedTypeReference) [2] |
execute(String, HttpMethod, RequestCallback, ResponseExtractor, Object…) | method(HttpMethod) .uri(String, Object…) .exchange(ExchangeFunction) |
execute(String, HttpMethod, RequestCallback, ResponseExtractor, Map) | method(HttpMethod) .uri(String, Map) .exchange(ExchangeFunction) |
execute(URI, HttpMethod, RequestCallback, ResponseExtractor) | method(HttpMethod) .uri(URI) .exchange(ExchangeFunction) |
HTTP 接口
Spring 框架允许您将 HTTP 服务定义为具有 @HttpExchange
方法的 Java 接口。您可以将这样的接口传递给 HttpServiceProxyFactory
来创建一个代理,该代理通过 HTTP 客户端(例如 RestClient
或 WebClient
)执行请求。您还可以从 @Controller
实现该接口以处理服务器请求。
首先创建带有 @HttpExchange
方法的接口:
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
现在您可以创建一个代理,当调用方法时执行请求。
对于 RestClient
:
RestClient restClient = RestClient.builder().baseUrl("https://api.github.com/").build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
对于 WebClient
:
WebClient webClient = WebClient.builder().baseUrl("https://api.github.com/").build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
RepositoryService service = factory.createClient(RepositoryService.class);
对于 RestTemplate
:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/"));
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).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 交换方法支持灵活的方法签名,具有以下方法参数:
方法参数 | 描述 |
---|---|
URI | 动态设置请求的 URL,覆盖注解的 url 属性。 |
UriBuilderFactory | 提供一个 UriBuilderFactory 来扩展 URI 模板和 URI 变量。实际上,替换底层客户端的 UriBuilderFactory (及其基础 URL)。 |
HttpMethod | 动态设置请求的 HTTP 方法,覆盖注解的 method 属性。 |
@RequestHeader | 添加请求头或多个头。参数可以是单个值、一个值的 Collection<?> 、Map<String, ?> 、MultiValueMap<String, ?> 。支持非字符串值的类型转换。头值被添加,不会覆盖已经添加的头值。 |
@PathVariable | 添加一个变量以扩展请求 URL 中的占位符。参数可以是一个包含多个变量的 Map<String, ?> ,或一个单独的值。支持非字符串值的类型转换。 |
@RequestAttribute | 提供一个 Object 作为请求属性。仅由 RestClient 和 WebClient 支持。 |
@RequestBody | 提供请求的主体,可以是要序列化的对象,或一个 Reactive Streams Publisher ,如 Mono 、Flux ,或通过配置的 ReactiveAdapterRegistry 支持的任何其他异步类型。 |
@RequestParam | 添加请求参数或多个参数。参数可以是一个包含多个参数的 Map<String, ?> 或 MultiValueMap<String, ?> ,一个值的 Collection<?> ,或一个单独的值。支持非字符串值的类型转换。当 "content-type" 设置为 "application/x-www-form-urlencoded" 时,请求参数会被编码在请求主体中。否则,它们会作为 URL 查询参数添加。 |
@RequestPart | 添加一个请求部分,可以是字符串(表单字段)、Resource (文件部分)、对象(要编码的实体,例如 JSON)、HttpEntity (部分内容和头)、Spring Part ,或上述任何内容的 Reactive Streams Publisher 。 |
MultipartFile | 从 MultipartFile 添加请求部分,通常在 Spring MVC 控制器中使用,表示上传的文件。 |
@CookieValue | 添加一个或多个 cookie。参数可以是一个包含多个 cookie 的 Map<String, ?> 或 MultiValueMap<String, ?> ,一个值的 Collection<?> ,或一个单独的值。支持非字符串值的类型转换。 |
方法参数不能为 null
,除非 required
属性(在参数注释中可用)被设置为 false
,或者参数被标记为可选,这由 MethodParameter#isOptional 决定。
自定义参数解析器
对于更复杂的情况,HTTP 接口不支持将 RequestEntity
类型作为方法参数。这将占用整个 HTTP 请求,并不会改善接口的语义。开发者可以将多个方法参数组合成一个自定义类型,而不是添加许多方法参数,并配置一个专门的 HttpServiceArgumentResolver
实现。
在以下 HTTP 接口中,我们使用自定义的 Search
类型作为参数:
- Java
- Kotlin
interface RepositoryService {
@GetExchange("/repos/search")
List<Repository> searchRepository(Search search);
}
interface RepositoryService {
@GetExchange("/repos/search")
fun searchRepository(search: Search): List<Repository>
}
我们可以实现我们自己的 HttpServiceArgumentResolver
,以支持我们自定义的 Search
类型,并将其数据写入即将发送的 HTTP 请求中。
- 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
}
}
最后,我们可以在设置过程中使用这个参数解析器,并使用我们的 HTTP 接口。
- 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)
返回值
支持的返回值取决于底层客户端。
客户端适配了 HttpExchangeAdapter
的如 RestClient
和 RestTemplate
支持同步返回值:
方法返回值 | 描述 |
---|---|
void | 执行给定的请求。 |
HttpHeaders | 执行给定的请求并返回响应头。 |
<T> | 执行给定的请求并将响应内容解码为声明的返回类型。 |
ResponseEntity<Void> | 执行给定的请求并返回一个带有状态和头部的 ResponseEntity 。 |
ResponseEntity<T> | 执行给定的请求,将响应内容解码为声明的返回类型,并返回一个带有状态、头部和解码后的主体的 ResponseEntity 。 |
客户端适配了 ReactorHttpExchangeAdapter
,例如 WebClient
,支持上述所有内容以及反应式变体。下表显示了 Reactor 类型,但您也可以使用通过 ReactiveAdapterRegistry
支持的其他反应式类型:
方法返回值 | 描述 |
---|---|
Mono<Void> | 执行给定的请求,并释放响应内容(如果有的话)。 |
Mono<HttpHeaders> | 执行给定的请求,释放响应内容(如果有的话),并返回响应头。 |
Mono<T> | 执行给定的请求,并将响应内容解码为声明的返回类型。 |
Flux<T> | 执行给定的请求,并将响应内容解码为声明的元素类型的流。 |
Mono<ResponseEntity<Void>> | 执行给定的请求,释放响应内容(如果有的话),并返回一个带有状态和头的 ResponseEntity 。 |
Mono<ResponseEntity<T>> | 执行给定的请求,将响应内容解码为声明的返回类型,并返回一个带有状态、头和解码主体的 ResponseEntity 。 |
Mono<ResponseEntity<Flux<T>> | 执行给定的请求,将响应内容解码为声明的元素类型的流,并返回一个带有状态、头和解码响应主体流的 ResponseEntity 。 |
默认情况下,ReactorHttpExchangeAdapter
的同步返回值超时时间取决于底层 HTTP 客户端的配置。您也可以在适配器级别设置 blockTimeout
值,但我们建议依赖底层 HTTP 客户端的超时设置,因为它在更低的层次上操作并提供更多控制。
错误处理
要自定义错误响应处理,您需要配置底层的 HTTP 客户端。
对于 RestClient
:
默认情况下,RestClient
对 4xx 和 5xx HTTP 状态码会引发 RestClientException
。要自定义此行为,可以注册一个响应状态处理程序,该处理程序适用于通过客户端执行的所有响应:
RestClient restClient = RestClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, (request, response) -> ...)
.build();
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
有关更多详细信息和选项,例如抑制错误状态代码,请参阅 RestClient.Builder
中 defaultStatusHandler
的 Javadoc。
对于 WebClient
:
默认情况下,WebClient
对于 4xx 和 5xx HTTP 状态码会引发 WebClientResponseException
。要自定义此行为,可以注册一个响应状态处理程序,该处理程序适用于通过客户端执行的所有响应:
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter adapter = WebClientAdapter.create(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(adapter).build();
有关更多详细信息和选项,例如抑制错误状态代码,请参阅 WebClient.Builder
中 defaultStatusHandler
的 Javadoc。
对于 RestTemplate
:
默认情况下,RestTemplate
对 4xx 和 5xx HTTP 状态码会抛出 RestClientException
。要自定义此行为,可以注册一个错误处理器,该处理器适用于通过客户端执行的所有响应:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(myErrorHandler);
RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
有关更多详细信息和选项,请参阅 RestTemplate
中 setErrorHandler
的 Javadoc 以及 ResponseErrorHandler
层次结构。
1. HttpEntity
的头部和主体必须通过 headers(Consumer<HttpHeaders>)
和 body(Object)
提供给 RestClient
。
2。RequestEntity
方法、URI、头部和主体必须通过 method(HttpMethod)
、uri(URI)
、headers(Consumer<HttpHeaders>)
和 body(Object)
提供给 RestClient
。