Spring 调用 RESTful 服务

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 方法概述

HTTP 方法RestTemplate 方法
DELETEdelete
GETgetForObject getForEntity
HEADheadForHeaders(String url, String…​ uriVariables)
OPTIONSoptionsForAllow(String url, String…​ uriVariables)
POSTpostForLocation(String url, Object request, String…​ uriVariables)
postForObject(String url, Object request, Class responseType, String…​ uriVariables)
PUTput(String url, Object request, String…​uriVariables)
PATCH and othersexchange execute

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值