REST 客户端

TODAY Framework 提供了以下选项来调用 REST 端点:

  • RestClient - 具有流畅 API 的同步客户端。

  • WebClient - 具有流畅 API 的非阻塞、响应式客户端。

  • RestTemplate - 具有模板方法 API 的同步客户端。

  • HTTP 接口 - 具有生成的动态代理实现的注解接口。

RestClient

RestClient 是一个同步 HTTP 客户端,提供现代、流畅的 API。 它提供了对 HTTP 库的抽象,允许方便地将 Java 对象转换为 HTTP 请求,并从 HTTP 响应创建对象。

创建 RestClient

RestClient 是使用静态 create 方法之一创建的。 您还可以使用 builder() 获取具有更多选项的构建器,例如指定要使用的 HTTP 库(请参阅 客户端请求工厂)和要使用的消息转换器(请参阅 HTTP 消息转换),设置默认 URI、默认路径变量、默认请求头或 uriBuilderFactory,或注册拦截器和初始化器。

一旦创建(或构建),RestClient 就可以安全地供多个线程使用。

以下示例显示了如何创建默认的 RestClient,以及如何构建自定义的 RestClient

  • Java

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")
  .requestInterceptor(myCustomInterceptor)
  .requestInitializer(myCustomInitializer)
  .build();

使用 RestClient

使用 RestClient 发出 HTTP 请求时,首先要指定要使用的 HTTP 方法。 这可以通过 method(HttpMethod) 或使用便捷方法 get()head()post() 等来完成。

请求 URL

接下来,可以使用 uri 方法指定请求 URI。 此步骤是可选的,如果 RestClient 配置了默认 URI,则可以跳过此步骤。 URL 通常指定为 String,带有可选的 URI 模板变量。 字符串 URL 默认编码,但这可以通过使用自定义 uriBuilderFactory 构建客户端来更改。

URL 也可以通过函数或作为 java.net.URI 提供,这两者都不编码。 有关处理和编码 URI 的更多详细信息,请参阅 URI 链接

请求头和正文

如有必要,可以通过使用 header(String, String)headers(Consumer<HttpHeaders>) 添加请求头,或使用便捷方法 accept(MediaType…​)acceptCharset(Charset…​) 等来操作 HTTP 请求。 对于可以包含正文的 HTTP 请求(POSTPUTPATCH),还有其他方法可用:contentType(MediaType)contentLength(long)

请求正文本身可以通过 body(Object) 设置,其内部使用 HTTP 消息转换。 或者,可以使用 ParameterizedTypeReference 设置请求正文,允许您使用泛型。 最后,可以将正文设置为写入 OutputStream 的回调函数。

检索响应

设置请求后,通过调用 retrieve() 访问 HTTP 响应。 可以使用 body(Class)body(ParameterizedTypeReference)(对于列表等参数化类型)访问响应正文。 body 方法将响应内容转换为各种类型——例如,字节可以转换为 String,JSON 可以使用 Jackson 转换为对象,依此类推(请参阅 HTTP 消息转换)。

响应也可以转换为 ResponseEntity,从而可以访问响应头和正文。

此示例显示了如何使用 RestClient 执行简单的 GET 请求。

Java
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 打印结果

通过 ResponseEntity 提供对响应状态代码和头的访问:

Java
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 打印结果

RestClient 可以使用 Jackson 库将 JSON 转换为对象。 请注意此示例中 URI 变量的使用,并且 Accept 头设置为 JSON。

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 域对象

在下一个示例中,RestClient 用于执行包含 JSON 的 POST 请求,该请求再次使用 Jackson 进行转换。

Java
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 将响应转换为没有正文的响应实体。

错误处理

默认情况下,RestClient 在检索具有 4xx 或 5xx 状态代码的响应时会抛出 RestClientException 的子类。 可以使用 onStatus 覆盖此行为。

  • Java

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 抛出自定义异常

Exchange

对于更高级的场景,RestClient 通过 exchange() 方法提供对底层 HTTP 请求和响应的访问,该方法可用于代替 retrieve()。 使用 exchange() 时不应用状态处理程序,因为 exchange 函数已经提供了对完整响应的访问,允许您执行任何必要的错误处理。

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 域对象

HTTP 消息转换

infra-web 模块包含 HttpMessageConverter 接口,用于通过 InputStreamOutputStream 读取和写入 HTTP 请求和响应的正文。 HttpMessageConverter 实例在客户端(例如,在 RestClient 中)和服务器端(例如,在 Web MVC REST 控制器中)使用。

框架中提供了主要媒体 (MIME) 类型的具体实现,默认情况下,这些实现已在客户端向 RestClientRestTemplate 注册,在服务器端向 RequestMappingHandlerAdapter 注册(请参阅 配置消息转换器)。

下面介绍了几种 HttpMessageConverter 的实现。 有关完整列表,请参阅 HttpMessageConverter Javadoc。 对于所有转换器,都使用默认媒体类型,但您可以通过设置 supportedMediaTypes 属性来覆盖它。

Table 1. HttpMessageConverter 实现
MessageConverter 描述

StringHttpMessageConverter

一个 HttpMessageConverter 实现,可以从 HTTP 请求和响应中读取和写入 String 实例。 默认情况下,此转换器支持所有文本媒体类型 (text/*) 并以 text/plainContent-Type 写入。

FormHttpMessageConverter

一个 HttpMessageConverter 实现,可以从 HTTP 请求和响应中读取和写入表单数据。 默认情况下,此转换器读取和写入 application/x-www-form-urlencoded 媒体类型。 表单数据从 MultiValueMap<String, String> 中读取并写入其中。 转换器还可以写入(但不能读取)从 MultiValueMap<String, Object> 读取的多部分数据。 默认情况下,支持 multipart/form-data。 支持其他多部分子类型用于写入表单数据。 有关更多详细信息,请参阅 FormHttpMessageConverter 的 javadoc。

ByteArrayHttpMessageConverter

一个 HttpMessageConverter 实现,可以从 HTTP 请求和响应中读取和写入字节数组。 默认情况下,此转换器支持所有媒体类型 (*/*) 并以 application/octet-streamContent-Type 写入。 您可以通过设置 supportedMediaTypes 属性并覆盖 getContentType(byte[]) 来覆盖此设置。

MarshallingHttpMessageConverter

一个 HttpMessageConverter 实现,可以使用 infra.oxm 包中的 Infra MarshallerUnmarshaller 抽象读取和写入 XML。 此转换器在使用前需要 MarshallerUnmarshaller。 您可以通过构造函数或 Bean 属性注入这些。 默认情况下,此转换器支持 text/xmlapplication/xml

JacksonJsonHttpMessageConverter

一个 HttpMessageConverter 实现,可以使用 Jackson 的 ObjectMapper 读取和写入 JSON。 您可以根据需要通过使用 Jackson 提供的注解来自定义 JSON 映射。 当您需要进一步控制时(对于需要为特定类型提供自定义 JSON 序列化器/反序列化器的情况),可以通过 ObjectMapper 属性注入自定义 ObjectMapper。 默认情况下,此转换器支持 application/json

JacksonXmlHttpMessageConverter

一个 HttpMessageConverter 实现,可以使用 Jackson XML 扩展的 XmlMapper 读取和写入 XML。 您可以根据需要通过使用 JAXB 或 Jackson 提供的注解来自定义 XML 映射。 当您需要进一步控制时(对于需要为特定类型提供自定义 XML 序列化器/反序列化器的情况),可以通过 ObjectMapper 属性注入自定义 XmlMapper。 默认情况下,此转换器支持 application/xml

SourceHttpMessageConverter

一个 HttpMessageConverter 实现,可以从 HTTP 请求和响应中读取和写入 javax.xml.transform.Source。 仅支持 DOMSourceSAXSourceStreamSource。 默认情况下,此转换器支持 text/xmlapplication/xml

默认情况下,RestClientRestTemplate 会注册所有内置消息转换器,具体取决于类路径上底层库的可用性。 您还可以通过使用 RestClient 构建器上的 messageConverters() 方法或通过 RestTemplatemessageConverters 属性显式设置要使用的消息转换器。

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();

Multipart

要发送多部分数据,您需要提供一个 MultiValueMap<String, 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 = HttpHeaders.forWritable();
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 准备就绪,您可以使用 RestClient.post().body(parts)(或 RestTemplate.postForObject)将其用作 POST 请求的正文。

如果 MultiValueMap 包含至少一个非 String 值,则 FormHttpMessageConverterContent-Type 设置为 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

  • ReactorClientHttpRequestFactory 用于 Reactor Netty 的 HttpClient

如果在构建 RestClient 时未指定请求工厂,并且它们在类路径上可用,则它将使用 Apache。 否则,如果加载了 java.net.http 模块,它将使用 Java 的 HttpClient。 最后,它将求助于简单的默认值。

WebClient

WebClient 是一个非阻塞的响应式客户端,用于执行 HTTP 请求。 它是在 5.0 中引入的,提供了 RestTemplate 的替代方案,支持同步、异步和流式场景。

WebClient 支持以下内容:

  • 非阻塞 I/O

  • Reactive Streams 背压

  • 使用更少的硬件资源实现高并发

  • 利用 Java 8 lambdas 的函数式风格、流畅的 API

  • 同步和异步交互

  • 向上流式传输或从服务器向下流式传输

RestTemplate

RestTemplate 以经典的 Infra 模板类的形式提供了 HTTP 客户端库的高级 API。 它公开了以下重载方法组:

RestClient 为同步 HTTP 访问提供了更现代的 API。
Table 2. 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 库抽象。 默认情况下,它使用 JdkClientHttpRequestFactory,但这可以通过构造函数更改。 请参阅 客户端请求工厂

正文

传入和从 RestTemplate 方法返回的对象在 HttpMessageConverter 的帮助下转换为 HTTP 消息,请参阅 HTTP 消息转换

RestTemplate 迁移到 RestClient

下表显示了 RestTemplate 方法的 RestClient 等效项。 它可用于从后者迁移到前者。

Table 3. RestTemplate 方法的 RestClient 等效项
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 接口

TODAY Framework 允许您将 HTTP 服务定义为具有 @HttpExchange 方法的 Java 接口。 您可以将此类接口传递给 HttpServiceProxyFactory 以创建代理,该代理通过 HTTP 客户端(如 RestClientWebClient)执行请求。 您还可以从 @Controller 实现该接口以进行服务器请求处理。

首先创建带有 @HttpExchange 方法的接口:

interface RepositoryService {

  @GetExchange("/repos/{owner}/{repo}")
  Repository getRepository(@PathVariable String owner, @PathVariable String repo);

  // 更多 HTTP 交换方法...

}

现在您可以创建一个代理,该代理在调用方法时执行请求。

对于 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);

@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

添加一个或多个请求头。参数可以是具有多个头的 Map<String, ?>MultiValueMap<String, ?>,值的 Collection<?> 或单个值。支持非 String 值的类型转换。

@PathVariable

添加一个变量以扩展请求 URL 中的占位符。参数可以是具有多个变量的 Map<String, ?> 或单个值。支持非 String 值的类型转换。

@RequestAttribute

提供一个 Object 作为请求属性添加。仅 WebClient 支持。

@RequestBody

提供请求的正文,作为要序列化的对象,或 Reactive Streams Publisher,如 MonoFlux 或通过配置的 ReactiveAdapterRegistry 支持的任何其他异步类型。

@RequestParam

添加一个或多个请求参数。参数可以是具有多个参数的 Map<String, ?>MultiValueMap<String, ?>,值的 Collection<?> 或单个值。支持非 String 值的类型转换。

"content-type" 设置为 "application/x-www-form-urlencoded" 时,请求参数在请求正文中编码。否则,它们作为 URL 查询参数添加。

@RequestPart

添加请求部分,可以是 String(表单字段)、Resource(文件部分)、Object(要编码的实体,例如作为 JSON)、HttpEntity(部分内容和头)、Infra Part 或上述任何项的 Reactive Streams Publisher

Part

Part 添加请求部分,通常在 Web MVC 控制器中使用,其中它表示上传的文件。

@CookieValue

添加一个或多个 cookie。参数可以是具有多个 cookie 的 Map<String, ?>MultiValueMap<String, ?>,值的 Collection<?> 或单个值。支持非 String 值的类型转换。

返回值

支持的返回值取决于底层客户端。

适配到 RequestExecutionFactory 的客户端(如 RestClient)支持同步和异步返回值:

方法返回值 描述

void

执行给定的请求。

HttpHeaders

执行给定的请求并返回响应头。

<T>

执行给定的请求并将响应内容解码为声明的返回类型。

ResponseEntity<Void>

执行给定的请求并返回带有状态和头的 ResponseEntity

ResponseEntity<T>

执行给定的请求,将响应内容解码为声明的返回类型,并返回带有状态、头和解码正文的 ResponseEntity

ClientResponse, ClientHttpResponse

直接返回原始响应

Future<T>, CompletionStage<T>

返回异步结果, T 可以是是上面的任何一个

适配到 RequestExecutionFactory 的客户端(如 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.BuilderdefaultStatusHandler 的 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.BuilderdefaultStatusHandler 的 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();

1. HttpEntity headers and body have to be supplied to the RestClient via headers(Consumer<HttpHeaders>) and body(Object).
2. RequestEntity method, URI, headers and body have to be supplied to the RestClient via method(HttpMethod), uri(URI), headers(Consumer<HttpHeaders>) and body(Object).