Spring框架为调用REST端点提供了以下选择:
- WebClient - 非阻塞、响应式客户端和 fluent API。
- RestTemplate - 带有模板方法API的同步客户端。
- HTTP 接口 - 注解式接口,并生成动态代理实现。
一、 WebClient
WebClient
是一个非阻塞的、响应式的客户端,用于执行HTTP请求。它在5.0中引入,提供了 RestTemplate
的替代方案,支持同步、异步和流式场景。
WebClient
支持以下特性:
- 非阻塞 I/O。
- Reactive Streams 背压。
- 以较少的硬件资源实现高并发性。
- 函数式、fluent API,利用了Java 8 lambdas的优势。
- 同步和异步互动。
- 服务器的流式上传和下载。
二、RestTemplate
RestTemplate
提供了一个比HTTP客户端库更高层次的API。它使得在一行中调用REST端点变得容易。它暴露了以下几组重载方法:
Table 1. RestTemplate methods
方法组 | 说明 |
| 通过 GET 检索一个表示结果。 |
| 通过使用GET检索一个 |
| 通过使用 |
| 通过使用 POST 创建一个新的资源,并从响应中返回 |
| 通过使用POST创建一个新资源,并从响应中返回表示。 |
| 通过使用POST创建一个新资源,并从响应中返回表示。 |
| 通过使用PUT创建或更新一个资源。 |
| 通过使用 PATCH 更新一个资源,并从响应中返回表示。请注意,JDK的 |
| 通过使用 DELETE 删除指定URI上的资源。 |
| 通过使用 ALLOW 为资源检索允许的HTTP方法。 |
| 前面的方法的更通用(和更少的意见)版本,在需要时提供额外的灵活性。它接受一个 这些方法允许使用 |
| 执行请求的最通用方式,通过回调接口对请求准备和响应提取进行完全控制。 |
1、初始化
默认构造函数使用 java.net.HttpURLConnection
来执行请求。你可以通过 ClientHttpRequestFactory
的实现切换到不同的HTTP库。有内置的对以下内容的支持:
- Apache HttpComponents
- Netty
- OkHttp
例如,要切换 到Apache HttpComponents,你可以使用以下方法:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
每个 ClientHttpRequestFactory
都公开了底层HTTP客户端库的特定配置选项—例如,对于凭证、连接池和其他细节。
URI
许多 RestTemplate
方法接受URI模板和URI模板变量,或者作为一个 String
变量参数,或者作为 Map<String,String>
。
下面的例子使用了一个 String
变量的参数:
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
下面的例子使用一个 Map<String, String>
:
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
请记住URI模板是自动编码的,如下例所示:
restTemplate.getForObject("https://example.com/hotel list", String.class);
// Results in request to "https://example.com/hotel%20list"
你可以使用 RestTemplate
的 uriTemplateHandler
属性来定制URI的编码方式。或者,你可以准备一个 java.net.URI
,并把它传入接受 URI
的 RestTemplate
方法之一。
Header
你可以使用 exchange()
方法来指定 header,如下例所示:
String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header("MyRequestHeader", "MyValue")
.build();
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
你可以通过许多返回 ResponseEntity
的 RestTemplate
方法变体获得响应头信息。
2、Body
在 HttpMessageConverter
的帮助下,传入 RestTemplate
方法和从 RestTemplate
方法返回的对象被转换为原始内容。
在POST中,一个输入对象被序列化到请求体中,如下面的例子所示:
URI location = template.postForLocation("https://example.com/people", person);
你不需要明确地设置请求的 Content-Type
头。在大多数情况下,你可以根据源对象类型找到一个兼容的消息转换器(message converter),所选择的消息转换器会相应地设置 content type。如果有必要,你可以使用 exchange
方法来明确地提供 Content-Type
的请求头,而这又会影响到选择何种消息转换器。
在一个GET中,响应的 body 被反序列化为一个输出 Object
,如下例所示:
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
请求的 Accept 标头不需要明确设置。在大多数情况下,可以根据预期的响应类型找到一个兼容的消息转换器,然后帮助填充 Accept 头。如果有必要,你可以使用 exchange 方法来明确提供 Accept 头。 默认情况下,RestTemplate 注册了所有内置的 消息转换器(message converter),这取决于 classpath 检查,有助于确定有哪些可选的 converter 库存在。你也可以明确地设置要使用的消息转换器。
消息转换
spring-web
模块包含 HttpMessageConverter
contract,用于通过 InputStream
和 OutputStream
读写 HTTP 请求和响应的 body。HttpMessageConverter
实例在客户端(例如,在 RestTemplate
中)和服务器端(例如,在Spring MVC REST controller 中)使用。
框架中提供了主要 type(MIME)type 的具体实现,默认情况下,在客户端与 RestTemplate
注册,在服务器端与 RequestMappingHandlerAdapter
注册
HttpMessageConverter
的实现将在下面几节中描述。对于所有的转换器,都使用默认的 media type,但是你可以通过设置 supportedMediaTypes
bean 属性来覆盖它。下表描述了每个实现:
Table 2. HttpMessageConverter 实现
MessageConverter | 说明 |
| 一个 |
| 一个 |
| 一个 |
| 一个 |
| 一个 |
| 一个 |
| 一个 |
| 一个 |
3、Jackson JSON 视图
你可以指定一个 Jackson JSON 视图 来只序列化对象属性的一个子集,如下面的例子所示:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
RequestEntity<MappingJacksonValue> requestEntity =
RequestEntity.post(new URI("https://example.com/user")).body(value);
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
4、Multipart
为了发送 multipart 数据,你需要提供一个 MultiValueMap<String, Object>
,其值可以是一个用于 part 内容的 Object
,一个用于文件 part 的 Resource
,或者一个用于带有 header 的 part 内容的 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));
在大多数情况下,你不需要为每个 part 指定 Content-Type
。content type 是根据为序列化它而选择的 HttpMessageConverter
自动确定的,或者在 Resource
的情况下,根据文件扩展名确定。如果有必要,你可以用一个 HttpEntity
wrapper 明确地提供 MediaType
。
一旦 MultiValueMap
准备好了,你就可以把它传递给 RestTemplate
,如下所示:
MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);
如果 MultiValueMap
至少包含一个非 String
值,Content-Type
就被 FormHttpMessageConverter
设置为 multipart/form-data
。如果 MultiValueMap
有 String
值,Content-Type
被默认为 application/x-www-form-urlencoded
。如果有必要,也可以明确设置 Content-Type
。
三、HTTP 接口
Spring框架允许你将HTTP服务定义为一个Java接口,并为 HTTP exchange 提供注解方法。然后你可以生成一个实现该接口并执行 exchange 的代理。这有助于简化HTTP远程访问,因为远程访问通常涉及到一个 facade,该 facade 包装了使用底层HTTP客户端的细节。
声明一个带有 @HttpExchange
方法的接口:
interface RepositoryService {
@GetExchange("/repos/{owner}/{repo}")
Repository getRepository(@PathVariable String owner, @PathVariable String repo);
// more HTTP exchange methods...
}
创建一个代理,执行所声明的 HTTP exchange:
WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).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);
}
1、方法参数
注解式 HTTP exchange 方法支持灵活的方法签名,有以下方法参数:
方法参数 | 说明 |
| 动态设置请求的URL,覆盖注解的 |
| 动态地设置请求的HTTP方法,覆盖注解的 |
| 添加一个请求头或多个头。参数可以是一个 |
| 添加一个变量,用于在请求URL中扩展一个占位符。参数可以是一个带有多个变量的 |
| 提供请求的 body,可以是要被序列化的对象,也可以是Reactive Streams |
| 添加一个请求参数或多个参数。参数可以是一个 当 |
| 添加一个 request part,它可以是一个 |
| 添加一个或多个cookie。参数可以是一个 |
2、返回值
注解式 HTTP exchange 方法支持以下返回值:
方法返回值 | 说明 |
| 执行给定的请求,并释放响应内容,如果有的话。 |
| 执行给定的请求,释放响应内容(如果有),并返回响应头信息。 |
| 执行给定的请求并将响应内容解码为声明的返回类型。 |
| 执行给定的请求,并将响应内容解码为声明的元素类型的流。 |
| 执行给定的请求,并释放响应内容(如果有的话),并返回一个带有状态和头信息的 |
| 执行给定的请求,将响应内容解码为声明的返回类型,并返回一个带有状态、头信息和解码后的 body 的 |
| 执行给定的请求,将响应内容解码为声明的元素类型的流,并返回一个带有状态、头信息和解码后的响应体流的 |
3、异常处理
默认情况下,WebClient
会对 4xx 和 5xx HTTP状态代码引发 WebClientResponseException
。要定制这一点,你可以注册一个响应状态 handler,适用于通过客户端执行的所有响应:
WebClient webClient = WebClient.builder()
.defaultStatusHandler(HttpStatusCode::isError, resp -> ...)
.build();
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builder(clientAdapter).build();
关于更多的细节和选项,例如抑制错误状态代码,请参见 WebClient.Builder
中 defaultStatusHandler
的 Javadoc。
大家好,我是Doker品牌的Sinbad,欢迎点赞和评论,您的鼓励是我们持续更新的动力!欢迎加微信进入技术群聊!