20220507 1. Integration - REST Endpoints
前言
Spring 框架为调用 REST 端点提供了两种选择:
RestTemplate
: 带有同步模板方法 API 的原始 Spring REST 客户端- WebClient : 一种非阻塞、反应式的替代方案,既支持同步方案,也支持异步方案以及流方案
在 5.0 版本中,
RestTemplate
处于维护模式,只有少量的更改请求和错误修复被接受。请考虑使用WebClient
,它提供了更现代的 API ,并支持同步、异步和流场景
RestTemplate
RestTemplate
通过 HTTP 客户端库提供了更高级别的 API 。它使得在单行中调用 REST 端点变得容易。它公开下列重载方法组:
方法组 | 描述 |
---|---|
getForObject |
通过 GET 检索一个表示 |
getForEntity |
使用 GET 检索 ResponseEntity (即 status、 headers 和 body) |
headForHeaders |
使用 HEAD 检索资源的所有标头 |
postForLocation |
使用 POST 创建新资源,并从响应返回 Location 头 |
postForObject |
使用 POST 创建新资源,并从响应返回表示 |
postForEntity |
使用 POST 创建新资源,并从响应返回表示 |
put |
使用 PUT 创建或更新资源 |
patchForObject |
使用 PATCH 更新资源并返回响应的表示形式。注意,JDK HttpURLConnection 不支持 PATCH ,但是 Apache HttpComponents 和其他组件支持 PATCH |
delete |
使用 DELETE 删除指定 URI 处的资源 |
optionsForAllow |
使用 ALLOW 为资源检索允许的 HTTP 方法 |
exchange |
更加通用,提供额外的灵活性。它接受一个 RequestEntity (包括 HTTP 方法、 URL、 header 和 body 作为输入) ,并返回一个 ResponseEntity 这些方法允许使用 ParameterizedTypeReference 而不是 Class 来指定具有泛型的响应类型 |
execute |
执行请求的最通用方式,通过回调接口完全控制请求准备和响应提取 |
初始化(Initialization)
缺省构造函数使用 java.net.HttpURLConnection
来执行请求。您可以使用 ClientHttpRequestFactory
实现切换到不同的 HTTP 库。它内置了以下功能:
- Apache HttpComponents
- Netty
- OkHttp
例如,要切换到 Apache HttpComponents ,可以使用以下命令:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
每个 ClientHttpRequestFactory
都公开特定于底层 HTTP 客户端库的配置选项ーー例如,凭证、连接池和其他细节。
注意,HTTP 请求的
java.net
实现在访问表示错误的响应 (比如 401) 的状态时可能会引发异常。如果这是一个问题,切换到另一个 HTTP 客户端库。
URIs
许多 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
方法之一。
有关使用和编码 URI 的详细信息,请参阅 URI Links
Headers
您可以使用 exchange()
方法来指定请求头,如下面的示例所示:
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
方法变体获得响应标头。
Body
在 HttpMessageConverter
的帮助下,从 RestTemplate
方法传入和返回的对象将转换为原始内容。
在 POST 上,输入对象被序列化到请求主体,如下面的示例所示:
URI location = template.postForLocation("https://example.com/people", person);
您不需要显式地设置请求的 Content-Type
标头。在大多数情况下,您可以找到基于源 Object
类型的兼容消息转换器,并且所选择的消息转换器相应地设置内容类型。如果有必要,可以使用 exchange
方法显式地提供 Content-Type
请求标头,而这又会影响选择哪个消息转换器。
在 GET 上,响应的主体被反序列化为输出对象,如下面的示例所示:
Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);
请求的 Accept
标头不需要显式设置。在大多数情况下,可以根据预期的响应类型找到兼容的消息转换器,这有助于填充 Accept
头。如果需要,可以使用 exchange
方法显式地提供 Accept
标头。
默认情况下,RestTemplate
注册所有内置的 消息转换器 ,这取决于类路径检查,该检查有助于确定存在哪些可选转换库。您还可以将消息转换器设置为显式使用。
消息转换
spring-web
模块包含 HttpMessageConverter
契约,用于通过 InputStream
和 OutputStream
读写 HTTP 请求和响应的主体。HttpMessageConverter
实例用于客户端 (例如,在 RestTemplate
中) 和服务器端 (例如,在 Spring MVC REST 控制器中) 。
框架中提供了主要媒体 (MIME) 类型的具体实现,并且默认情况下,在客户端使用 RestTemplate
注册,在服务器端使用 RequestMethodHandlerAdapter
注册 (参见 配置消息转换器 )
以下几节将描述 HttpMessageConverter
的实现。对于所有转换器,都使用默认的媒体类型,但是可以通过设置 supportedMediaTypes
bean 属性来覆盖它。下表介绍了 HttpMessageConverter
实现:
MessageConverter | 描述 |
---|---|
StringHttpMessageConverter |
一个 HttpMessageConverter 实现,可以从 HTTP 请求和响应中读写 String 实例。默认情况下,此转换器支持所有文本媒体类型 ( text/* ) ,并使用 text/plain (Content-Type )进行写入 |
FormHttpMessageConverter |
一个 HttpMessageConverter 实现,可以从 HTTP 请求和响应中读取和写入表单数据。默认情况下,此转换器读取和写入 application/x-www-form-urlencoded 媒体类型。表单数据被读取并写入 MultiValueMap<String, String> 。该转换器还可以写入(但不读取) 从 MultiValueMap<String, Object> 读取的多部分数据。默认情况下,支持 multipart/form-data 。从 Spring Framework 5.2 开始,可以支持其他多部分子类型来写入表单数据。有关详细信息,请参阅 FormHttpMessageConverter |
ByteArrayHttpMessageConverter |
一个 HttpMessageConverter 实现,可以从 HTTP 请求和响应中读取和写入字节数组。默认情况下,此转换器支持所有媒体类型 ( */* ) ,并使用 Content-Type application/octet-stream 进行写操作。可以通过设置 supportedMediaTypes 属性和重写 getContentType(byte[]) 来重写此属性 |
MarshallingHttpMessageConverter |
一个 HttpMessageConverter 实现,可以使用 Spring 的 Marshaller 和 Unmarshaller 从 org.springframework.oxm 包中抽象出来读写 XML 。这个转换器在使用之前需要一个 Marshaller 和 Unmarshaller 。可以通过构造函数或 bean 属性注入这些属性。默认情况下,此转换器支持 text/xml 和 application/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 |
可以读写 javax.xml.transform.Source 的 HttpMessageConverter 实现。源自 HTTP 请求和响应。只支持 DOMSource 、 SAXSource 和 StreamSource 。默认情况下,此转换器支持 text/xml 和 application/xml |
BufferedImageHttpMessageConverter |
可读写来自 HTTP 请求和响应里的 java.awt.image.BufferedImage 的 HttpMessageConverter 实现。这个转换器读写 Java I/O API 支持的媒体类型 |
Jackson JSON Views
可以指定一个 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);
Multipart
要发送多部分数据,您需要提供 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));
在大多数情况下,您不必为每个部件指定 Content-Type
。内容类型是基于序列化它所选择的 HttpMessageConverter
自动确定的,或者在基于文件扩展名的资源的情况下。如果有必要,可以显式地为 MediaType
提供一个 HttpEntity
包装器。
一旦 MultiValueMap
准备好了,你可以将它传递给 RestTemplate
,如下所示:
MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);
如果 MultiValueMap
包含至少一个非 String
值,则 FormHttpMessageConverter
将 Content-Type
设置为 multipart/form-data
。如果 MultiValueMap
具有 String
值,则 Content-Type
默认为 application/x-www-form-urlencoded
。如果需要,也可以显式设置 Content-Type
。
使用 AsyncRestTemplate
(已弃用)
不推荐使用 AsyncRestTemplate
。对于所有可能考虑使用 AsyncRestTemplate
的用例,请使用 WebClient