24.10 在客户端访问RESTful服务
RestTemplate是客户端访问RESTful服务的核心类。它在概念上类似于Spring中的其他模板类,例如JdbcTemplate、 JmsTemplate和其他Spring组合项目中发现的其他模板类。
RestTemplate’s behavior is customized by providing callback methods and configuring the `HttpMessageConverter用于将对象打包到HTTP请求体中,并将任何响应解包成一个对象。通常使用XML作为消息格式,Spring提供了MarshallingHttpMessageConverter,它使用了的Object-to-XML框架,也是org.springframework.oxm包的一部分。这为你提供了各种各样的XML到对象映射技术的选择。
本节介绍如何使用RestTemplate它及其关联 的HttpMessageConverters。
24.10.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服务成为一个单行和执行REST的最佳实践。
Note: RestTemplate具有异步计数器部分:请参见第24.10.3节“异步RestTemplate”。
Table 24.1. RestTemplate方法概述
HTTP Method | RestTemplate Method |
---|---|
DELETE | delete |
GET | getForObject getForEntity |
HEAD | headForHeaders(String url, String… uriVariables) |
OPTIONS | optionsForAllow(String url, String… uriVariables) |
POST | postForLocation(String url, Object request, String… uriVariables) postForObject(String url, Object request, Class<T> responseType, String… uriVariables) |
PUT | put(String url, Object request, String…uriVariables) |
PATCH and others | exchange execute |
RestTemplate方法名称遵循命名约定,第一部分指出正在调用什么HTTP方法,第二部分指出返回的内容。例如,该方法getForObject()将执行GET,将HTTP响应转换为你选择的对象类型并返回该对象。方法postForLocation() 将执行POST,将给定对象转换为HTTP请求,并返回可以找到新创建的对象的响应HTTP Location头。在异常处理HTTP请求的情况下,RestClientException类型的异常将被抛出; 这个行为可以在RestTemplate通过插入另一个ResponseErrorHandler实现来改变。
exchange和execute方法是上面列出的更具体的方法的广义版本,并且可以支持额外的组合和方法,例如HTTP PATCH。但是,请注意,底层HTTP库还必须支持所需的组合。JDK HttpURLConnection不支持该PATCH方法,但Apache HttpComponents HttpClient4.2或更高版本支持。他们还能够通过使用一个能够捕获和传递通用类型信息的新类ParameterizedTypeReference来使得RestTemplate能够读取通用类型的HTTP响应信息(例如List)。
对象通过HttpMessageConverter实例传递给这些方法并从这些方法返回被转换为HTTP消息。主要mime类型的转换器默认注册,但你也可以编写自己的转换器并通过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>如下:
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
要创建一个实例,RestTemplate可以简单地调用默认的无参数构造函数。这将使用java.net包中的标准Java类作为底层实现来创建HTTP请求。这可以通过指定实现来覆盖ClientHttpRequestFactory。Spring提供了HttpComponentsClientHttpRequestFactory使用Apache HttpComponents HttpClient创建请求的实现。HttpComponentsClientHttpRequestFactory通过使用一个可以配置凭证信息或连接池功能的org.apache.http.client.HttpClient实例来配置。
Note: HTTP请求的java.net实现可能会在访问表示错误的响应状态(例如401)时引发异常。如果这是一个问题,请切换到HttpComponentsClientHttpRequestFactory。
前面使用Apache HttpCOmponentsHttpClientdirectly的例子用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());
Note: Apache HttpClient 支持gzip编码,要使用这个功能,构造HttpCOmponentsClientHttpRequestFactory如下:
HttpClient httpClient = HttpClientBuilder.create().build(); ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); RestTemplate restTemplate = new RestTemplate(requestFactory);
当execute方法被调用,通用的回调接口是RequestCallback并且会被调用。
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方法时,你不必担心任何资源管理,模板将始终关闭请求并处理任何错误。有关使用execute方法及它的其他方法参数的含义的更多信息,请参阅API文档。
使用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 list执行一个GET请求。这意味着如果输入的URL字符串已被编码,它将被编码两次 – 即将 http://example.com/hotel list变为http://example.com/hotel list。如果这不是预期的效果,则使用java.net.URI方法变体,假设URL已经被编码,如果要重复使用单个(完全扩展)URI多次,通常也是有用的。
UriComponentsBuilder类可用于构建和编码URI包括URI模板的支持。例如,你可以从URL字符串开始:
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();
处理请求和响应头
除了上述方法之外,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和消息体。
Jackson JSON 视图支持
可以指定一个 Jackson JSON视图来系列化对象属性的一部分,例如:
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);
24.10.2 HTTP 消息转换
通过HttpMessageConverters,对象传递到和从getForObject(),postForLocation(),和put()这些方法返回被转换成HTTP请求和HTTP相应。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在客户端和 RequestMethodHandlerAdapter在服务器端注册。
HttpMessageConverter的实现下面章节中描述。对于所有转换器,使用默认媒体类型,但可以通过设置supportedMediaTypesbean属性来覆盖。
StringHttpMessageConverter
一个HttpMessageConverter的实现,实现从HTTP请求和相应中读和写Strings。默认情况下,该转换器支持所有的文本媒体类型(text/*),并用text/plain的Content-Type来写。
FormHttpMessageConverter
一个HttpMessageConverter的实现,实现从HTTP请求和响应读写表单数据。默认情况下,该转换器读写application/x-www-form-urlencoded媒体类型。表单数据被读取并写入MultiValueMap<String, String>。
ByteArrayHttpMessageConverter
一个HttpMessageConverter的实现,实现从HTTP请求和响应中读取和写入字节数组。默认情况下,此转换器支持所有媒体类型(/),并使用其中的一种Content-Type进行写入application/octet-stream。这可以通过设置supportedMediaTypes属性和覆盖getContentType(byte[])来重写。
MarshallingHttpMessageConverter
一个HttpMessageConverter的实现,从org.springframework.oxm包中使用Spring的Marshaller和Unmarshaller抽象实现读取和写入XML。该转换器需要Marshaller和Unmarshaller才能使用它。这些可以通过构造函数或bean属性注入。默认情况下,此转换器支持( text/xml)和(application/xml)。
MappingJackson2HttpMessageConverter
一个HttpMessageConverter的实现,使用Jackson XML扩展的ObjectMapper实现读写JSON。可以根据需要通过使用JAXB或Jackson提供的注释来定制XML映射。当需要进一步控制时,XmlMapper 可以通过ObjectMapper属性注入自定义,以便需要为特定类型提供自定义XML序列化器/反序列化器。默认情况下,此转换器支持(application/xml)。
MappingJackson2XmlHttpMessageConverter
一个HttpMessageConverter的实现,可以使用Jackson XML扩展的XmlMapper读取和写入XML。可以根据需要通过使用JAXB或Jackson提供的注释来定制XML映射。当需要进一步控制时,XmlMapper 可以通过ObjectMapper属性注入自定义,以便需要为特定类型提供自定义XML序列化器/反序列化器。默认情况下,此转换器支持(application/xml)。
SourceHttpMessageConverter
一个HttpMessageConverter的实现,从HTTP请求和响应中读写 javax.xml.transform.Source。仅支持DOMSource、SAXSource和StreamSource。默认情况下,此转换器支持(text/xml)和(application/xml)。
BufferedImageHttpMessageConverter
一个HttpMessageConverter的实现,从HTTP请求和响应中读写java.awt.image.BufferedImage。此转换器读写Java I/O API支持的媒体类型。
24.10.3 异步RestTemplate
Web应用程序通常需要查询外部REST服务。当为这些需求扩张应用程序时,HTTP和同步调用的性质带来挑战:可能会阻塞多个线程,等待远程HTTP响应。
AsyncRestTemplate和第24.10.1节“RestTemplate”的API非常相似; 请 参见表24.1“RestTemplate方法概述”。这些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) {
//...
}
});
Note: 默认AsyncRestTemplate构造函数为执行HTTP请求注册一个SimpleAsyncTaskExecutor 。当处理大量短命令请求时,线程池的TaskExecutor实现ThreadPoolTaskExecutor 可能是一个不错的选择。
有关更多详细信息,参考ListenableFuture的javadoc and AsyncTestTmeplate的javadoc.