RestTemplate 是客户端访问 RESTful 服务的核心类。它在概念上类似于 Spring 中的其他模板类,如 JdbcTemplate 和 JmsTemplate 及 其他 Spring 组合项目中的模板类。RestTemplate 的行为是通过提供回调方法及配置 HttpMessageConverter 进行自定义的,HttpMessageConverter 将对象封送到 Http 请求体中,并将任何响应解组成为一个对象。
1. RestTemplate
在 Java 中调用 RESTful 服务通常使用工具类来完成,例如 Apache HttpComponents 中的 HttpClient。对于常见的 REST 操作来说 ,这种方法太低级,如下所示:
String uri = "http://example.com/hotels/1/bookings";
PostMethod post = new PostMethod(uri);
String request = // create booking request content
post.setRequestEntity(new StringRequestEntity(request));
httpClient.executeMethod(post);
if (HttpStatus.SC_CREATED == post.getStatusCode()) {
Header location = post.getRequestHeader("Location");
if (location != null) {
System.out.println("Created new booking at :" + location.getValue());
}
}
RestTemplate 提供了更高级别的方法,这些方法与六种主要的 Http 方法一一对应,它使得大部分 RESTful 服务的调用在一行内完成,以获得最佳实践。
表格 1.1. RestTemplate 方法概述
RestTemplate 方法的名称遵循命名约定,第一部分指出正在调用什么HTTP方法,第二部分指出返回的内容。例如,getForObject() 方法将执行GET,将HTTP响应转换为指定的对象类型,并返回该对象。如果在处理 Http 请求的过程中出现问题,将抛出一个类型为 RestClientException 的异常;可以通过将一个 ResponseErrorHandler 实现插入到 RestTemplate 来更改此行为。
exchange 和 execute 方法是上面列出的方法的通用版本,可以支持其他组合和方法,如 HTTP PATCH。但是,请注意,底层HTTP库也必须支持所需的组合。JDK HttpURLConnection 不支持 PATCH 方法,但 Apache HttpComponents HttpClient 4.2以上版本支持。还可以使用 ParameterizedTypeReference(一个能够捕获和传递泛型信息的类)来让 RestTemplate 将响应读取为一个泛型类型对象。
这些方法的输入及输出对象与 HTTP 消息之间的相互转换由 HttpMessageConverter 实例来完成。主要的 mime 类型会注册默认的 Converters,你也可以编写自己的 Converters,并通过设置 messageConverters 属性进行注册。注册到模板的默认转换器实例有 ByteArrayHttpMessageConverter,StringHttpMessageConverter,FormHttpMessageConverter 和SourceHttpMessageConverter。如果使用 MarshallingHttpMessageConverter 或 MappingJackson2HttpMessageConverter,则需要设置 messageConverters 属性覆盖这些默认值。
每个方法都有两种形式的 URI 模板参数:与变量个数相同长度的 String 参数或 Map<String, String>。例如:
String result = restTemplate.getForObject(
"http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42", "21");
使用变量长度的参数及
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
使用一个 Map<String, String>。
你可以调用无参的构造方法来创建一个 RestTemplate 实例。这将使用 java.net 包中的标准Java类作为底层实现来创建HTTP请求。可以通过指定一个 ClientHttpRequestFactory 实现来进行覆盖。Spring 提供了 HttpComponentsClientHttpRequestFactory 实现来使用 Apache HttpComponents HttpClient 创建请求。
前面使用 Apache HttpComponents HttpClient 实现的例子可以直接使用 RestTemplate 重写为:
uri = "http://example.com/hotels/{id}/bookings";
RestTemplate template = new RestTemplate();
Booking booking = // create booking object
URI location = template.postForLocation(uri, booking, "1");
如果要使用 Apache HttpComponents 代替原生的 java.net 的话,请按如下所示构建 RestTemplate:
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
Apache HttpClient 支持 gzip 编码。如果要使用它,按如下所示构建 HttpComponentsClientHttpRequestFactory:
HttpClient httpClient = HttpClientBuilder.create().build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
常见的回调接口为 RequestCallback ,它在 execute 方法执行时被调用。
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor, String... uriVariables)
// also has an overload with uriVariables as a Map<String, String>.
RequestCallback 接口定义如下:
public interface RequestCallback {
void doWithRequest(ClientHttpRequest request) throws IOException;
}
它可以操作请求头,并写入请求体。当使用 execute 方法时,你不需要再担心资源的管理,模板将始终关闭请求并处理所有错误。
1.1. URI 的使用
对每一个主要的 Http 方法,RestTemplate 提供了 String 类型的 URI 或 java.net.URI 两种类型作为方法的第一个参数。
String 类型的 URI 可以接受的模板参数分为两种,一种是与变量个数相同的 String 参数,另一种是 Map<String, String>。同时,它假定 URL 字符串没有被编码过且需要进行编码。示例如下:
restTemplate.getForObject("http://example.com/hotel list", String.class);
将在 http://example.com/hotel%20list 上执行 GET 请求。这意味着,如果输入的 URL 已经被编码过,那它可能会被再次编码,如:http://example.com/hotel%20list 将会成为 http://example.com/hotel%2520list。如果这个结果不符合预期,请使用 java.net.URI 替换字符串类型的 URI。
UriComponentsBuilder 类可以用来构建和编码URI,包括对 URI 模板的支持。例如:
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://example.com/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
URI uri = uriComponents.toUri();
或者分别指定每个 URI 组件:
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
URI uri = uriComponents.toUri();
1.2. 处理请求及响应头
除了上述方法之外,RestTemplate 还具有 exchange() 方法,可以用于基于 HttpEntity 类的任意 HTTP 方法执行。
最重要的是,exchange() 可以用户添加请求头并读取响应头。例如:
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
HttpEntity<String> response = template.exchange(
"http://example.com/hotels/{hotel}",
HttpMethod.GET, requestEntity, String.class, "42");
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
在上面的例子中,我们首先准备了一个包含 MyRequestHeader 头的请求实体。接着我们接收到响应,并读取 MyResponseHeader 及响应体。
1.3. Jackson JSON 视图支持
可以指定一个 Jackson JSON View 来序列化对象的部分属性。例如:
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(value);
String s = template.postForObject("http://example.com/user", entity, String.class);
2. HTTP 消息转换
方法 getForObject(),postForLocation() 及 put() 中的输入输出对象与 HTTP 消息之间的相互转换都需要使用到 HttpMessageConverters。HttpMessageConverter 接口的定义如下,你可以更好地理解其功能:
public interface HttpMessageConverter<T> {
// Indicate whether the given class and media type can be read by this converter.
boolean canRead(Class<?> clazz, MediaType mediaType);
// Indicate whether the given class and media type can be written by this converter.
boolean canWrite(Class<?> clazz, MediaType mediaType);
// Return the list of MediaType objects supported by this converter.
List<MediaType> getSupportedMediaTypes();
// Read an object of the given type from the given input message, and returns it.
T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
// Write an given object to the given output message.
void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
针对主要媒体(mime)类型的实现在框架中已经提供,默认情况下会注册到客户端的 RestTemplate 及服务器端的 AnnotationMethodHandlerAdapter 中。以下部分将介绍 HttpMessageConverters 的实现。对于所有转换器,默认的媒体类型都可以通过设置 supportedMediaTypes 属性进行覆盖。
2.1. StringHttpMessageConverter
一个可以从 HTTP 请求和响应中读取和写入 Strings 的 HttpMessageConverter 实现。默认情况下,此转换器支持所有文本媒体类型(text/ *),并使用 text/plain 的 Content-Type 进行写入。
2.2. FormHttpMessageConverter
一个可以从 HTTP 请求和响应中读取和写入表单数据的 HttpMessageConverter 实现。默认情况下,此转换器读取和写入媒体类型 application/x-www-form-urlencoded。表单数据会读入及写出到一个 MultiValueMap<String, String>中。
2.3. ByteArrayHttpMessageConverter
一个可以从 HTTP 请求和响应中读取和写入字节数组的 HttpMessageConverter 实现。默认情况下,此转换器支持所有媒体类型(*/*),并以Content-Type 为 application/octet-stream 的形式进行写入。可以通过设置 supportedMediaTypes 属性进行覆盖,同时重写 getContentType(byte[]) 方法。
2.4. MarshallingHttpMessageConverter
一个可以读写 XML 的 HttpMessageConverter 实现。该转换器需要使用 org.springframework.oxm 中的 Marshaller 及 Unmarshaller 接口,这些可以通过构造函数及 bean 属性进行注入。默认情况下,此转换器支持(text/xml)和(application/xml)。
2.5. MappingJackson2HttpMessageConverter
一个可以使用 Jackson 的 ObjectMapper 读取和写入 JSON 的 HttpMessageConverter 实现。可以根据需要使用 Jackson 提供的注释来定制 JSON 映射。需要进一步控制时,可以通过 ObjectMapper 属性注入自定义 ObjectMapper,以便为特定类型提供自定义的 JSON 序列化器/反序列化器。默认情况下,此转换器支持(application/json)。
2.6. MappingJackson2XmlHttpMessageConverter
一个可以使用 Jackson XML 扩展的 XmlMapper 来读写 XML 的 HttpMessageConverter 实现。可以根据需要通过使用 JAXB 或 Jackson 提供的注释来定制 XML 映射。需要进一步控制时,可以通过 ObjectMapper 属性注入自定义的 XmlMapper,以便为特定类型提供自定义的 XML 序列化器/反序列化器。默认情况下,此转换器支持(application/xml)。
2.7. SourceHttpMessageConverter
一个可以从 HTTP 请求和响应读取和写入 javax.xml.transform.Source 的 HttpMessageConverter 实现。只支持DOMSource,SAXSource 和 StreamSource。 默认情况下,此转换器支持(text/xml)和(application/xml)。
2.8. BufferedImageHttpMessageConverter
一个可以从 HTTP 请求和响应中读取和写入 java.awt.image.BufferedImage 的 HttpMessageConverter 实现。该转换器使用 Java/IO API 实现媒体类型的读取和写入。
3. 异步 RestTemplate
Web 应用程序通常需要调用外部的 REST 服务。HTTP 特性和同步调用可能会在为这些需求扩展应用程序时带来挑战:可能会阻塞多个线程,等待远程 HTTP 响应。
AsyncRestTemplate 与 RestTemplate 的 API 非常相似,这些 API 之间的主要区别是 AsyncRestTemplate 返回的是 ListenableFuture 包装器,而不是具体的结果 。
之前的 RestTemplate 示例可以转换为:
// async call
Future<ResponseEntity<String>> futureEntity = template.getForEntity(
"http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
// get the concrete result - synchronous call
ResponseEntity<String> entity = futureEntity.get();
ListenableFuture 接收完成时的回调:
ListenableFuture<ResponseEntity<String>> futureEntity = template.getForEntity(
"http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
// register a callback
futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
@Override
public void onSuccess(ResponseEntity<String> entity) {
//...
}
@Override
public void onFailure(Throwable t) {
//...
}
});
默认情况下,AsyncRestTemplate 构造函数会注册一个 SimpleAsyncTaskExecutor 来执行 HTTP 请求。 当处理大量 short-lived 请求时,线程池的 TaskExecutor 实现(如ThreadPoolTaskExecutor)可能是一个不错的选择。
参考链接: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#rest-client-access