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.