REST 客户端

Spring 框架提供以下几种选择来调用 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

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

使用 RestClient

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

请求 URL

接下来,可以使用 uri 方法指定请求 URI。此步骤是可选的,如果 RestClient 配置了默认 URI,则可以跳过此步骤。URL 通常指定为 String,并带有可选的 URI 模板变量。以下示例配置了对 example.com/orders/42 的 GET 请求

  • 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 链接

请求头和主体

如果需要,可以通过使用 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

  • 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 请求,该请求再次使用 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 将响应转换为没有主体的响应实体。

错误处理

默认情况下,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);
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()时不会应用状态处理程序,因为交换函数已经提供了对完整响应的访问,允许您执行任何必要的错误处理。

  • 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 消息转换

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

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

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

表 1. HttpMessageConverter 实现
MessageConverter 描述

StringHttpMessageConverter

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

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 实现,可以使用 Spring 的org.springframework.oxm 包中的MarshallerUnmarshaller 抽象来读取和写入 XML。此转换器需要MarshallerUnmarshaller 才能使用。您可以通过构造函数或 bean 属性注入它们。默认情况下,此转换器支持text/xmlapplication/xml

MappingJackson2HttpMessageConverter

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

MappingJackson2XmlHttpMessageConverter

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

多部分

要发送多部分数据,您需要提供一个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

  • 反应式流背压

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

  • 利用 Java 8 lambda 的函数式、流畅的 API

  • 同步和异步交互

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

有关更多详细信息,请参阅 WebClient

RestTemplate

RestTemplate 在 HTTP 客户端库之上提供了一个高级 API,以经典的 Spring 模板类形式提供。它公开了以下组重载方法

RestClient 为同步 HTTP 访问提供了一个更现代的 API。对于异步和流式场景,请考虑反应式 WebClient
表 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 库抽象。默认情况下,它使用 SimpleClientHttpRequestFactory,但这可以通过构造函数更改。请参阅 客户端请求工厂

RestTemplate 可以被检测以实现可观察性,以便生成指标和跟踪。请参阅 RestTemplate 可观察性支持 部分。

主体

传递到 RestTemplate 方法中和从 RestTemplate 方法返回的对象在 HttpMessageConverter 的帮助下转换为 HTTP 消息,反之亦然,请参阅 HTTP 消息转换

RestTemplate 迁移到 RestClient

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

表 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 接口

Spring 框架允许您使用带 @HttpExchange 方法的 Java 接口来定义 HTTP 服务。您可以将此类接口传递给 HttpServiceProxyFactory 以创建代理,该代理通过 HTTP 客户端(如 RestClientWebClient)执行请求。您也可以从 @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

添加请求标头或多个标头。参数可以是 Map<String, ?>MultiValueMap<String, ?>,包含多个标头,也可以是 Collection<?> 的值,或单个值。支持非字符串值的类型转换。

@PathVariable

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

@RequestAttribute

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

@RequestBody

提供请求主体,可以是将要序列化的对象,也可以是 Reactive Streams Publisher,例如 MonoFlux 或任何其他通过配置的 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<?> 值,或单个值。支持非字符串值的类型转换。

返回值

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

适应 HttpExchangeAdapter 的客户端,例如 RestClientRestTemplate,支持同步返回值

方法返回值 描述

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

有关更多详细信息和选项,请参阅 RestTemplate 中的 setErrorHandler 以及 ResponseErrorHandler 层次结构的 Javadoc。


1. 必须通过 headers(Consumer<HttpHeaders>)body(Object)HttpEntity 的标头和主体提供给 RestClient
2. 必须通过 method(HttpMethod)uri(URI)headers(Consumer<HttpHeaders>)body(Object)RequestEntity 的方法、URI、标头和主体提供给 RestClient