Spring 6是一个非常强大的框架,它提供了许多工具和接口来简化远程接口调用。其中,WebClient、RestTemplate、HTTP Interface和RestClient是四种方式。
WebClient是Spring 5中新引入的一个接口基于响应式,它提供了一种更简单、更灵活的方式来调用远程接口。与RestTemplate相比,WebClient更加现代化,具有更好的性能和更低的内存占用。
RestTemplate是Spring 3中引入的一个接口,它提供了一种更加简单、更加直观的方式来调用远程接口。虽然WebClient是更现代化的选择,但RestTemplate仍然是一种常用的远程接口调用方式。
HTTP Interface将 HTTP 服务定义为一个 JAVA 接口,其中包含用于 HTTP 交换的注解方法。然后,你可以生成一个实现该接口并执行交换的代理。这有助于简化 HTTP 远程访问,因为远程访问通常需要使用一个门面来封装使用底层 HTTP 客户端的细节。
RestClient是一个同步 HTTP 客户端,提供现代、流畅的 API。它为 HTTP 库提供了一个抽象,可以方便地从 Java 对象转换为 HTTP 请求,并从 HTTP 响应创建对象。
下面分别介绍4个REST接口调用的详细使用。
RestTemplate 提供了比 HTTP 客户端库更高级别的 API。它使调用 REST 端点变得简单易行。它公开了以下几组重载方法:
方法 |
描述 |
getForObject |
通过GET检索数据。 |
getForEntity |
使用 GET 获取响应实体(即状态、标头和正文)。 |
headForHeaders |
使用 HEAD 读取资源的所有标头。 |
postForLocation |
使用 POST 创建新资源,并从响应中返回位置标头。 |
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 |
执行请求的最通用方式,可通过回调接口完全控制请求准备和响应提取。 |
默认构造函数使用 java.NET.HttpURLConnection 来执行请求。你可以通过 ClientHttpRequestFactory 的实现切换到不同的 HTTP 库。目前,该程序还内置了对 Apache HttpComponents 和 OkHttp 的支持。示例:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
每个 ClientHttpRequestFactory 都会公开底层 HTTP 客户端库的特定配置选项,例如,凭证、连接池和其他细节。
许多 RestTemplate 方法都接受 URI 模板和 URI 模板变量,可以是字符串变量参数,也可以是 Map<String,String>。
String result = restTemplate.getForObject(
"http://pack.com/users/{userId}", String.class, 666) ;
Map<String, ?>方式:
Map<String, Object> params = Collections.singletonMap("userId", 666);
String result = restTemplate.getForObject(
"http://pack.com/users/{userId}", String.class, params) ;
可以使用 exchange() 方法指定请求头,如下例所示:
String uriTemplate = "http://pack.com/users/{userId}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header("x-api-token", "aabbcc")
.build() ;
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("x-version");
String body = response.getBody() ;
如果你当前CLASSPATH存在MAppingJackson2HttpMessageConverter,那么你可以直接将请求结果映射为你所需要的结果对象,如下示例所示,将目标接口返回值直接转换为User对象。
User user = restTemplate.getForObject("http://pack.com/users/{userId}", User.class, 666);
默认情况下,RestTemplate 会注册所有内置的消息转换器,这决定于你当前类路径是否有相应的转换库。你也可以显式设置要使用的消息转换器。默认构造函数如下:
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
// ...其它转换器
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}
// ...其它转换器
this.uriTemplateHandler = initUriTemplateHandler();
}
注意:RestTemplate 目前处于维护模式,只接受小改动和错误请求。请考虑改用 WebClient。
WebClient 是执行 HTTP 请求的非阻塞、反应式客户端。它在 5.0 中引入,提供了 RestTemplate 的替代方案,支持同步、异步和流场景。
WebClient 支持以下功能:
示例:
Mono<Person> result = client.get()
.uri("/users/{userId}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(User.class);
Spring Framework 可让你将 HTTP 服务定义为一个 Java 接口,其中包含用于 HTTP 交换的注解方法。然后,你可以生成一个实现该接口并执行交换的代理。这有助于简化 HTTP 远程访问,因为远程访问通常需要使用一个门面来封装使用底层 HTTP 客户端的细节。
首先,声明一个带有 @HttpExchange 方法的接口:
@HttpExchange(url = "/demos")
public interface DemoInterface {
@PostExchange("/format3/{id}")
Users queryUser(@PathVariable Long id);
}
创建一个代理,执行所声明的 HTTP exchanges:
@Service
public class DemoService {
private final DemoInterface demoInterface ;
public DemoService() {
// 基于响应式调用;你当前的环境需要引入webflux
WebClient client = WebClient.builder().baseUrl("http://localhost:8088/").build() ;
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build() ;
this.demoInterface = factory.createClient(DemoInterface.class) ;
}
public Users queryUser(Long id) {
return this.demoInterface.queryUser(id) ;
}
}
测试接口
@Resource
private DemoService demoService ;
@GetMapping("/{id}")
public Users getUser(@PathVariable("id") Long id) {
return this.demoService.queryUser(id) ;
}
执行结果
图片
支持的方法参数
方法参数 |
说明 |
URI |
动态设置请求的 URL,覆盖注解的 url 属性。 |
HttpMethod |
动态设置请求的 HTTP 方法,覆盖注解的方法属性 |
@RequestHeader |
添加一个或多个请求标头。参数可以是包含多个标头的 Map<String, ?> 或 MultiValueMap<String, ?>、值集合<?> 或单个值。 |
@PathVariable |
添加一个变量,用于扩展请求 URL 中的占位符。参数可以是包含多个变量的 Map<String, ?> 或单个值。 |
@RequestBody |
提供请求的正文,既可以是要序列化的对象,也可以是 Reactive Streams Publisher(如 Mono、Flux 或通过配置的 ReactiveAdapterRegistry 支持的任何其他异步类型)。 |
@RequestParam |
添加一个或多个请求参数。参数可以是包含多个参数的 Map<String, ?> 或 MultiValueMap<String, ?>、数值集合<?> 或单个数值。 当 "content-type"设置为 "application/x-www-form-urlencoded "时,请求参数将在请求正文中编码。否则,它们将作为 URL 查询参数添加。 |
@RequestPart |
添加一个请求部分,它可以是字符串(表单字段)、资源(文件部分)、对象(要编码的实体,如 JSON)、HttpEntity(部分内容和标头)、Spring 部分或上述任何部分的 Reactive Streams 发布器。 |
@CookieValue |
添加一个或多个 cookie。参数可以是包含多个 cookie 的 Map<String, ?> 或 MultiValueMap<String, ?>、值集合<?> 或单个值。 |
支持的返回值
返回值 |
说明 |
|
执行给定的请求,并发布响应内容(如果有)。 |
|
执行给定的请求,释放响应内容(如果有),并返回响应标头。 |
|
执行给定的请求,并根据声明的返回类型对响应内容进行解码。 |
|
执行给定的请求,并将响应内容解码为已声明元素类型的数据流。 |
|
执行给定的请求,释放响应内容(如果有),并返回一个包含状态和标头的 ResponseEntity。 |
|
执行给定的请求,按照声明的返回类型解码响应内容,并返回一个包含状态、标头和解码后正文的 ResponseEntity。 |
Mono<ResponseEntity<Flux<T>> |
执行给定的请求,将响应内容解码为已声明元素类型的数据流,并返回一个包含状态、标头和解码后的响应正文数据流的 ResponseEntity。 |
默认情况下,WebClient为4xx和5xx HTTP状态代码引发WebClientResponseException。要自定义此项,可以注册响应状态处理程序,该处理程序应用于通过客户端执行的所有响应:
WebClient client = WebClient.builder()
// 状态码为4xx或5xx
.defaultStatusHandler(HttpStatusCode::isError, resp -> Mono.just(new RuntimeException(resp.statusCode().toString() + "请求错误")))
.baseUrl("http://localhost:8088/").build() ;
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(client)).build() ;
该接口是在Spring6.1.1版本中才有的,是一个同步 HTTP 客户端,提供现代、流畅的 API。
创建RestClientRestClient 是通过静态创建方法之一创建的。你还可以使用 builder 获取带有更多选项的生成器,例如指定要使用的 HTTP 库和要使用的消息转换器,设置默认 URI、默认路径变量、默认请求头,或注册拦截器和初始化器。
创建(或构建)后,RestClient 可由多个线程安全使用。
// 默认通过静态方法创建
RestClient restClient = RestClient.create();
// 自定义方式
RestClient restClient = RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory())
// 自定义消息转换器
// .messageConverters(converters -> converters.add(new PackCustomMessageConverter()))
.baseUrl("http://localhost:8088")
.defaultUriVariables(Map.of("id", "888"))
.defaultHeader("x-api-token", "aabbcc")
.requestInterceptor(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
System.out.println("我是拦截器") ;
return execution.execute(request, body) ;
}
})
.requestInitializer(new ClientHttpRequestInitializer() {
@Override
public void initialize(ClientHttpRequest request) {
System.out.println("我是初始化器") ;
request.getHeaders().add("x-version", "1.0.0") ;
}
})
.build() ;
调用及处理返回值
Users users = customClient.get()
.uri("/demos/users/{id}")
.retrieve()
.body(Users.class) ;
post+body请求方式
Users user = new Users();
ResponseEntity<Void> response = restClient.post()
.uri("/demos/users")
.contentType(APPLICATION_JSON)
.body(user)
.retrieve()
.toBodilessEntity() ;
错误处理默认情况下,当返回状态代码为 4xx 或 5xx 的响应时,RestClient 会抛出 RestClientException 的子类。可以使用 onStatus.RestClientException 命令重写该行为。
Users users = customClient.get()
.uri("/demos/users/{id}")
.retrieve()
// 处理返回状态码为:4xx和5xx
.onStatus(HttpStatusCode::isError, (request, response) -> {
throw new RuntimeException(response.getStatusCode().toString() + "请求错误") ;
})
.body(Users.class) ;
总结:实现远程接口调用方面的强大功能。无论是使用WebClient、RestTemplate、HTTP Interface还是直接使用RestClient,Spring都提供了丰富的工具和接口来简化开发者的操作。这些工具和接口不仅具有高性能、低内存占用的优点,而且提供了良好的可扩展性和灵活性,使得开发者可以根据实际需求进行定制化开发。