REST 客户端

Spring框架提供以下选项用于调用REST端点:

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

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

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

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

RestClient

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 模板变量。String 类型的 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("响应状态: " + result.getStatusCode()); (3)
System.out.println("响应头: " + result.getHeaders()); (3)
System.out.println("内容: " + result.getBody()); (3)
1 为指定的URL设置一个GET请求
2 将响应转换为ResponseEntity
3 打印结果
val result = restClient.get() (1)
  .uri("https://example.com") (1)
  .retrieve()
  .toEntity<String>() (2)

println("响应状态: " + result.statusCode) (3)
println("响应头: " + result.headers) (3)
println("内容: " + 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模块包含用于通过InputStreamOutputStream读取和写入HTTP请求和响应主体的HttpMessageConverter接口。 HttpMessageConverter实例在客户端(例如,在RestClient中)和服务器端(例如,在Spring MVC REST控制器中)使用。

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

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

表1. HttpMessageConverter实现
消息转换器 描述

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实现,可以通过使用Spring的MarshallerUnmarshaller抽象来读取和写入XML,这些抽象来自org.springframework.oxm包。在使用之前,此转换器需要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));

// 使用RestClient.post或RestTemplate.postForEntity发送

在大多数情况下,您不必为每个部分指定Content-Type。内容类型是根据选择用于序列化的HttpMessageConverter自动确定的,或者在Resource的情况下,根据文件扩展名确定。如果需要,您可以使用HttpEntity包装器显式提供MediaType

一旦MultiValueMap准备就绪,您可以将其用作POST请求的主体,使用RestClient.post().body(parts)(或RestTemplate.postForObject)。

如果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一起使用

  • JettyClientHttpRequestFactory用于Jetty的HttpClient

  • ReactorNettyClientRequestFactory用于Reactor Netty的HttpClient

  • SimpleClientHttpRequestFactory作为简单的默认值

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

WebClient

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

WebClient支持以下功能:

  • 非阻塞I/O

  • 响应式流背压

  • 高并发性,使用更少的硬件资源

  • 函数式风格,流畅的API,充分利用Java 8的lambda表达式

  • 同步和异步交互

  • 从服务器流式上或流式下

更多详情请参见WebClient

RestTemplate

RestTemplate

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

更现代的API是通过RestClient实现的,用于同步HTTP访问。对于异步和流式处理场景,请考虑使用响应式的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方法的对象通过HttpMessageConverter转换为HTTP消息,详见HTTP消息转换

RestTemplate迁移到RestClient

以下表格显示了RestClient方法对应于RestTemplate方法。可以用于从后者迁移到前者。

表3. 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 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);

对于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<?>,或一个单独的值。对于非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(部分内容和头部),Spring的Part,或上述任何内容的Reactive Streams Publisher

MultipartFile

MultipartFile添加一个请求部分,通常在Spring MVC控制器中使用,表示上传的文件。

@CookieValue

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

返回值

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

适配到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();

有关更多详细信息和选项,请参阅RestTemplatesetErrorHandler的Javadoc和ResponseErrorHandler层次结构。


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