Hoody's Blog
Spring RestTemplate 使用介绍

前言

RestTemplate 简化了发起 HTTP 请求以及处理响应的过程,并且支持 REST的工具

微信公众平台 微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号、获取接口权限后,可以通过本接来帮助开发。

然后真的想吐槽一下,微信的接口,是真的乱.

  • 说好的返回Json 然后返回类型是text/plain
  • 获取素材有用get请求,有的用post请求
  • 一会儿http,一会儿https 其他的坑慢慢爬吧

有兴趣可以关注一下我的微信接口项目 hoody-wechat-springboot-starter 微信公众号API

RestTemplate介绍

本篇内容基于Spring 官方文档 RestTemplate 加上个人理解/实践进行介绍
如有错误请留言,我会尽快修改的

初始化

1.常规方式

RestTemplate template = new RestTemplate();

2.如果要使用Apache HttpComponents作为请求库

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

主要方法

RestTemplate

方法 描述
getForEntity 发起GET请求,返回响应体ResponseEntity<T>
getForObject 发起GET请求,返回响应内容<T> body
postForEntity 发起POST请求,返回响应体ResponseEntity<T>
postForObject 发起POST请求,返回响应内容<T> body
exchange 上述方法的更通用(且不太固定)的版本,在需要时提供额外的灵活性。它接受一个RequestEntity(包括HTTP方法,URL,标题和正文作为输入)并返回一个ResponseEntity。
execute 执行请求的最通用方式,通过回调接口完全控制请求准备和响应提取

getForObject的使用

此方法有3个重载

1.public T getForObject(String url, Class responseType, Object... uriVariables)
可变参数接收uri参数

RestTemplate template = new RestTemplate();
String result = template.getForObject(
    "https://example.com/hotels/{hotel}/bookings/{booking}",
    String.class,
     "42", "21"); //restTemplate会对参数进行URI编码

2.public T getForObject(String url, Class responseType, Map<String, ?> uriVariables)
Map形式接收uri参数

RestTemplate template = new RestTemplate();
Map<String, String> vars = Collections.singletonMap("hotel", "42");
Hotel hotel = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/rooms/{hotel}", Hotel.class, vars);
//restTemplate会对参数进行URI编码

3.public T getForObject(URI url, Class responseType)
URI对象作为参数

RestTemplate template = new RestTemplate();
//自定义Uri实例
String result = restTemplate.getForObject(yourUri,String.class)

getForEntity 的使用

此方法有3个重载 方法参数同getForObject
唯一区别是返回值是ResponseEntity<T>类型
ResponseEntity<T> 包含了HTTP响应的头信息header

  • 通过header可以获取到响应的头信息,比如status 状态码, ContentType 响应类型等等
  • body则是响应数据通过HttpMessageConverter转换的数据.

ResponseEntity<T>进行操作

//其他重载参数同 getForObject
ResponseEntity<Hotel> entity= restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/rooms/{hotel}", Hotel.class, "super8");
//获取返回值
Hotel body = entity.getBody();
//获取响应类型
MediaType contentType = entity.getHeaders().getContentType();
//获取响应状态码
HttpStatus statusCode = entity.getStatusCode();

postForObject 的使用

此方法有3个重载
getForObject相比多了一个Object request参数
其他参数作用和getForObject相同

Object request可以使用以下值作为参数

  • org.springframework.util.MultiValueMap
  • org.springframework.http.HttpEntity

以 public T postForObject(String url, @Nullable Object request, Class responseType, Object... uriVariables) 方法举例

通过HttpEntity携带JSON参数,并明确指定请求头的Content-type

RestTemplate template = new RestTemplate();
//JSON String
String param = "{\"type\":\"student\"}";
//create request header 创建请求头
HttpHeaders headers = new HttpHeaders()
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8)
//创建请求参数
HttpEntity<String> entity = new HttpEntity<String>(param, headers)
//调用
String result = new RestTemplate().postForObject(
    "https://example.com/{class}/user", //String url
     entity, //Object request
    String.class, //Class<T> responseType
    "1")  //Object... uriVariables

通过MultiValueMap携带多个参数

在大多数情况下,您不必为每个部件指定Content-Type。内容类型是根据HttpMessageConverter所选内容类型自动确定的,不指定Content-Type以便基于文件扩展名的情况下进行自动选择。

RestTemplate template = new RestTemplate();

MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
//字符串值
parts.add("fieldPart", "fieldValue");
//图片文件
parts.add("filePart", new FileSystemResource("...logo.png"));
//json 
parts.add("jsonPart", new Person("Jason"));
//  手动指定类型,并添加xml
String xmlValue = "<user><name>hoody</name><sex>male</sex></user>";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(xmlValue, headers));

String result = new RestTemplate().postForObject(
    "https://example.com/{class}/user", //String url
     parts, //Object request
    String.class, //Class<T> responseType
    "1")  //Object... uriVariables

postForEntity的使用

此方法有3个重载 方法参数同postForObject 唯一区别是返回值是ResponseEntity类型 ResponseEntity 包含了HTTP响应的头信息header

通过HttpEntity携带JSON参数,并明确指定请求头的Content-type 举例

RestTemplate template = new RestTemplate();
//JSON String
String param = "{\"type\":\"student\"}";
//create request header 创建请求头
HttpHeaders headers = new HttpHeaders()
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8)
//创建请求参数
HttpEntity<String> entity = new HttpEntity<String>(param, headers)
//调用
ResponseEntity<String> result = new RestTemplate().postForObject(
    "https://example.com/{class}/user", //String url
     entity, //Object request
    String.class, //Class<T> responseType
    "1")  //Object... uriVariables

//获取返回值
String body = entity.getBody();
//获取响应类型
MediaType contentType = entity.getHeaders().getContentType();
//获取响应状态码
HttpStatus statusCode = entity.getStatusCode();

exchange 的使用

返回值是ResponseEntity<T>类型
postForEntitygetForEntity的参数基本相同
主要的的区别:
1.可以以org.springframework.http.HttpMethod枚举作为参数,指定请求方法(GET,POST,DELETE等等)
2.可以给GET请求添加请求头信息

其他还有一些官方文档也没多写,
主要作用是 使用RestTemplate作为工具,写自定义REST组件时,可以自由控制请求方式

例子:
使用exchange 发起GET请求
设置请求头header:

  • 设置user-agent
  • 设置接收数据类型为JSON
RestTemplate template = new RestTemplate();
//创建header
HttpHeaders headers = new HttpHeaders();
//设置user-agent
headers.add(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
        "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36");
//添加接收数据媒体类型,JSON
List<MediaType> acceptableMediaTypes = new ArrayList<>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON_UTF8)
headers.setAccept(acceptableMediaTypes)
//创建请求体
HttpEntity<HttpHeaders> entity = new HttpEntity<>(headers)
//发出请求
ResponseEntity<String> responseEntity = restTemplate.exchange(
        "https://example.com/hotels/{hotel}/bookings/{booking}",
        HttpMethod.GET,
        entity,
        String.class,
        "42","21"
)
//获取返回值
String body = entity.getBody();
//获取响应类型
MediaType contentType = entity.getHeaders().getContentType();
//获取响应状态码
HttpStatus statusCode = entity.getStatusCode();

execute 介绍

一般情况下,使用上述方法就够了
execute方法是所有上述请求方法最后都会调用execute方法

查看源码可以发现 getForXXX,postForXXX的最后都是execute

@Override
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request,
        Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
//最后都会调用`execute`方法
    return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables));
}

getForXXX,postForXXX方法内部主要做了这几件事:

  • 使用request参数和responseType创建了一个RequestCallback,它会根据responseType设置header的acceptType,并且将request参数组织requestBody
  • 使用responseType参数创建了一个ResponseExtractor,它会根据responseType自动采用合适的HttpMessageConverter对返回值进行转型

当然我们也可以直接调用execute,使用自己的RequestCallback, ResponseExtractor实现

比如这个例子,这个是我做微信接口时遇到的 下载永久素材时:
如果下载的是图片,音频等微信会直接返回文件流
如果下载的是视频或者素材id错误时,会返回文本信息download_urlerror_code

//素材ID
String mediaId = "xxxxxxxxxxx"
//微信 api 地址
String url = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=${getAccessToken()}"
// 请求参数,要求以JSON格式发送
String json = "{\"media_id\":\"${mediaId}\"}"
//创建请求头,加入json
HttpHeaders headers = new HttpHeaders()
headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8)
HttpEntity<String> httpEntity = new HttpEntity<String>(json, headers)
//使用RestTemplate提供的方法创建RequestCallback
RequestCallback requestCallback = restTemplate.httpEntityCallback(httpEntity)
// 自定义返回值处理器
ResponseExtractor<File> responseExtractor =
        new ResponseExtractor<File>() {
            @Override
            File extractData(ClientHttpResponse response) throws IOException {
                //判断响应媒体类型是否是文本
                if (response.getHeaders().getContentType() == org.springframework.http.MediaType.TEXT_PLAIN) {
                    //获取输入流,并转为String
                    InputStream inputStream = response.getBody()
                    byte[] bytes = new byte[inputStream.available()]
                    inputStream.read(bytes)
                    String str = new String(bytes)
                    //转为JSON对象
                    JSONObject result = new JSONObject(str)
                    //判断微信接口是否返回错误
                    if (!result.isNull("errcode")) {
                        println(result.toString())
                        throw new WechatMediaException("get Media fail :${result.toString()}")
                    }
                    //获取视频下载地址
                    String videoUrl = new JSONObject(str).getString("down_url")
                    //下载视频方法
                    return downloadVideo(videoUrl)
                }
                //如果是其他类型
                else {
                    InputStream inputStream = response.getBody()
                    //获取响应文件名
                    String filename = response.getHeaders().getContentDisposition().getFilename()
                    //使用响应文件名创建临时文件
                    File file = File.createTempFile(
                            filename.substring(0, filename.lastIndexOf(".")),
                            filename.substring(filename.lastIndexOf("."), filename.length())
                    )
                    file.deleteOnExit()
                    //输入流写入临时文件
                    FileOutputStream fileOutputStream = new FileOutputStream(file)
                    byte[] bytes = new byte[1024]
                    int ch
                    while ((ch = inputStream.read(bytes)) > -1) {
                        fileOutputStream.write(bytes, 0, ch);
                    }
                    fileOutputStream.close();
                    //返回文件给调用对象
                    return file
                }
            }
        }
//调用execute
File mediaFile = restTemplate.execute(url, HttpMethod.GET,
        requestCallback, responseExtractor)

HttpMessageConverter

通过上表方法进行HTTP请求时,会传入一个参数Class<T> responseType,指定返回值的类型,此类型作为<T> body返回 一般情况下,直接指定响应类型Class即可
RestTemplate会根据响应类型ContentTypeClass选择合适的转化器

流程图: HttpMessageConverter 处理流程.PNG

类型转换默认包含:

转化器 描述
StringHttpMessageConverter String.class类型从HTTP请求和响应中读取和写入实例的实现。默认情况下,此转换器支持所有文字媒体类型(text/*),并用写Content-Type的text/plain。
FormHttpMessageConverter MultiValueMap<String, String>类型从HTTP请求和响应中读取和写入表单数据的实现。默认情况下,此转换器读取和写入 application/x-www-form-urlencoded媒体类型。
ByteArrayHttpMessageConverter byte[].class从HTTP请求和响应中读取和写入字节数组的实现。默认情况下,该转换器支持所有媒体类型(/),并用写Content-Type的application/octet-stream。您可以通过设置supportedMediaTypes属性和覆盖来覆盖它getContentType(byte[])。
MarshallingHttpMessageConverter 通过使用Spring Marshaller和包中的Unmarshaller抽象来读写XML 的org.springframework.oxm实现。该转换器需要先加入Marshaller和Unmarshaller才可以使用。您可以通过构造函数或bean属性注入这些。默认情况下,此转换器支持 text/xml和application/xml。
MappingJackson2HttpMessageConverter 使用Jackson的读写JSON的实现 ObjectMapper。您可以根据需要通过使用Jackson提供的注释来自定义JSON映射。当您需要进一步控制时(对于需要为特定类型提供自定义JSON序列化器/反序列化器的情况),您可以注入一个自定义ObjectMapper,默认情况下,此转换器支持application/json
MappingJackson2XmlHttpMessageConverter 使用Jackson XML扩展来读写XML 的实现 XmlMapper。您可以根据需要通过使用JAXB或Jackson提供的注释来自定义XML映射。当您需要进一步控制时(对于需要为特定类型提供自定义XML序列化器/反序列化器的情况),您可以注入一个自定义XmlMapper。默认情况下,此转换器支持application/xml
SourceHttpMessageConverter 以javax.xml.transform.Source从HTTP请求和响应中读取和写入 。只支持DOMSourceSAXSource,StreamSource。默认情况下,此转换器支持 text/xml和application/xml
BufferedImageHttpMessageConverter 可以从HTTP请求和响应中读取和写入java.awt.image.BufferedImage。支持使用Java I/O API进行读写操作。

实现自定义HttpMessageConverter

1.继承org.springframework.http.converter.AbstractHttpMessageConverter 注意重写以下几个方法

  • getSupportedMediaTypes: 返回自定义转化器支持的媒体类型 ContentType
  • read: 读取http 响应并组织需要返回的对象。
  • write: 解析支持的对象类型数据,并将数据写入到response中。

其他方法酌情重写

2.将自定义转化器 添加进默认 HttpMessageConverter 列表

@Configuration
public class MVCConfig implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new TestMessageConverter());
    }
}

转载请保留我的名字和原文地址:http://www.hoody.tech/blog/detail/32

添加新评论,支持Markdown格式