RestTemlate源码分析及工具类设计

先说个大概的结构:

  • RestTemlate的所有方法最终调用的是doExecute(),层层向外封装
  • 其中exchange()适合用来封装成工具类,他的返回值是ResponseEntity
  • postForObject() postForEntity本质上一样,只是后者对运行结果进行了封装,返回的也是ResponseEntity,类似于lambda表达式中的Option类

1.本文的源码分析部分采用“从外到内”的顺序进行分析
2.类似postForObject和getForObject这种的区别仅仅是GET请求和POST请求的区别,本文仅分析POST请求的
3.postFroLocation()方法是返回一个URI,不是本文的重点

1.返回值ResponseEntity有什么用?

1.1 postForObject()和postForEntity()

看源码的注释部分,几乎一致,也就是两个方法基本没有什么区别

其中一个返回值是封装类ResponseEntity


重要!!:关于提取响应的方式

postForObject:HttpMessageConvertExtractor
postforEntity:ResponseExtractor
这两个Extractor类都有一个公共接口extractData,他在execute方法执行的时候被调用,用于获取HTTP响应的数据,只是在实现上,一个只获取响应体,另一个还要额外获取headers和status
公共接口

1.2ResponseEntity的重要参数

既然这个封装类有状态码status,响应头headers,那么一定在某个地方,getForObject没有去指定而getForEntity指定了(这时候应该猜到是extractData()方法,将在后面讨论)

形参

父类

1.2.1状态码

1.2.2 getBody() getHeaders()

直接获取上面两个参数,不再赘述

1.3ResponseEntity是如何封装返回值的?

1.3.1 responseExtrator.extractData(response)提取数据

  • 已知,最终都是调用doExecute()
  • 两个方法到这里的Extractor都是非空的,都是一定要执行,两个执行逻辑不同

1.3.2extractDatad抽象方法

这里直接看getForEntity对应的ResponseEntityResponseExtractor即封装了status和headers的执行过程


因此,只有返回值是Entity的才有headers和status,否则像getForObject这种方法返回的只是一个响应体

1.3.3公共的extractData()逻辑

即两个都会调用的,上面那么打红框的,通篇都是只针对了响应体进行操作,没有涉及任何的headers和status

1.3.4nonNull检验是否为空

如果是封装为Entity,那么还要再加一层判断方法

1.4小结

  • 到此为止我们分析了为什么getForObject()返回的只有响应体,而getForEntity()返回的包括了响应体 响应头 响应状态

  • 而getForEntity()和exchange()返回的都是ResponseEntity对象,因此第二点引出二者的区别

  • get和post请求只有一个区别,后续会演示如何使用API

2.exchange()和xxxtForEntity()

  • 可以看出区别就是requestEntity请求参数
  • 至于请求方法method不是主要矛盾
  • 这三个方法,参数所对应的含义如下:
    第一个:url
    对于exchange,第二个参数是请求方式自定义
    第三个:请求体参数,exchange和post有,get没有,用map或dto或requestEntity封装
    第四个:返回值类型
    第五个:路径参数,对应的是@RequestParam和@PathVariable,使用方式是在url中用{}占位,类似logger的{},也类似String.format()中的%s占位,因此Object…uriVariables是一个可变数组

这里找了一个写的比较全的API演示:参考链接

2.1get占位符和可变数组

2.1.1@RequestParam案例

请求的接口有两个@RequestParam参数,通过姓名和性别获取图片的一个例子

    ResponseEntity<byte[]> responseEntity =
            restTemplate.getForEntity("http://localhost:8080/pic/pic1?name={name}&sex={sex}", byte[].class,
                    "名字", "性别");
    ServletOutputStream os = response.getOutputStream();
    os.write(responseEntity.getBody());

当然对于@RequestParam最好的做法是放在最后一个形参中,传一个map,在后面会提到

2.1.2@PathVariable案例

大同小异

 restTemplate.getForEntity("http://localhost:8080/pic/pic1/{name}/{sex}", byte[].class,
                    "名字", "性别");

2.2post携带请求体

2.2.1当本地有dto代码时-直接使用

当本地有dto代码的时候,第二个形参直接用dto就好

    //当本地有dto的代码时,可以直接用dto来作为第二个请求体形参
    People people = new People();
    people.setAge(22);
    people.setName("张三");
    people.setTall(181);
    //最后一个可变数组的形参不用指定
    ResponseEntity<byte[]> res =
            restTemplate.postForEntity("http://localhost:8080/pic/people/pic1", people, byte[].class);
    ServletOutputStream os = response.getOutputStream();
    os.write(res.getBody());

但不一定是任何时候都有这个dto代码,如果没有这个dto代码,去调用其他的服务器,还需要写一个dto吗?不需要

2.2.2 不能使用HashMap

使用HashMap虽然在语法上没有问题**,但是会导致请求体对应不上去**,被请求的接口收不到请求体参数

例如:

     HashMap<String, String> hashMap = new HashMap<>();
    hashMap.put("name","张三");
    hashMap.put("age","1");
    hashMap.put("tall","181");

    //最后一个可变数组的形参不用指定
    ResponseEntity<byte[]> res =
            restTemplate.postForEntity("http://localhost:8080/pic/people/pic1", hashMap, byte[].class);
    ServletOutputStream os = response.getOutputStream();
    os.write(res.getBody());

2.2.3 使用SpringMVC下的map

MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();

他是org.springframework.util包下的一个Map
他兼容了类型匹配,与SpringMVC的适配性很好

    MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
    map.add("name","张三");
    map.add("age","11");
    map.add("tall","181");

    //最后一个可变数组的形参不用指定
    ResponseEntity<byte[]> res =
            restTemplate.postForEntity("http://localhost:8080/pic/people/pic1", map, byte[].class);
    ServletOutputStream os = response.getOutputStream();
    os.write(res.getBody());

2.2.4 同时设置请求体and请求头

  • 请求体写在MultiValueMap

  • 请求头写在HttpHeaders

  • 最终用HttpEntity封装,需要注意泛型的指定就是MultiValueMap

          MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
          map.add("name","张三");
          map.add("age","11");
          map.add("tall","181");
    
          HttpHeaders httpHeaders = new HttpHeaders();
          //按需求自行添加
      //        httpHeaders.setContentType();
      //        httpHeaders.setExpires();
    
          //这个Entity包含了请求体 和 请求头
          //泛型的指定即:map的类型
          HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map,httpHeaders);
    
          //最后一个可变数组的形参不用指定
          ResponseEntity<byte[]> res =
                  restTemplate.postForEntity("http://localhost:8080/pic/people/pic1", entity, byte[].class);
          ServletOutputStream os = response.getOutputStream();
          os.write(res.getBody());
    

2.2.5同时请求体 请求参数

有两种写法,一种是url拼接,利用最后一个参数是可变数组

ResponseEntity<byte[]> res = restTemplate.exchange("http://localhost:8080/pic/withbody?param1={param1}", HttpMethod.POST,
    httpEntity, byte[].class, "请求参数1"
);

第二种是最后一个参数用一个map

HashMap<String, String> urls = new HashMap<>();
urls.put("param1","测试1");

ResponseEntity<byte[]> res = restTemplate.exchange("http://localhost:8080/pic/withbody", HttpMethod.POST,
    httpEntity, byte[].class, urls
);

3.工具类

  • 根据公司业务写了一个工具类,可以根据需要,对baseByteTractorbaseJsonTractor进行封装
  • 可根据业务,携带param、body、header请求接口,并可选择是否将其响应数据的header和statusCode写入响应体
  • 工具类没有选择使用Spring Bean,而是使用安全的单例模式,移植性更强

3.1完整代码:

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

/**
 * @Description RestTemplate工具类
 * 1.getByte() postByte()是用来下载流的,因此形参必须有HttpResponseBody
 * 2.getJson() postJson()是用来获取json的,形参可以不带response
 * 3.每个方法最后一个形参requireBodyOnly默认true,即只需要响应体,一般建议设为true,
 *     否则可能造成因header自动解析产生的问题
 * 4.post请求可以额外携带请求体
 * 5.可能产生EOFException异常,正常,可以全局捕获日志记录
 * 
 * @Author zjh
 * @Date 17:04 2022/7/28
 **/
public class RestTemplateUtils {

  private static          Logger       logger = LoggerFactory.getLogger("RestLogger");
  private volatile static RestTemplate restTemplate;

  /**
   * RestTemplate单例 懒汉 双检锁
   **/
  public static RestTemplate getSingleRestTemplate() {
    if (restTemplate == null) {
      synchronized (RestTemplateUtils.class) {
        if (restTemplate == null) {
          restTemplate = new RestTemplate();
        }
      }
    }
    return restTemplate;
  }


  /**
   * 基础方法,下载文件二进制流到response输出流
   *
   * @param url 不包含请求参数 即 ?param={}
   * @param method POST或GET
   * @param requestHeaders 请求头,可为空
   * @param requestParams 请求参数,可为空
   * @param requestBody 请求体,可为空
   * @param requireBodyOnly 是否是只需要响应体,默认true,true:只需要响应体  false:同时返回响应状态 响应头  响应体
   * @return void
   **/
  public static void baseByteTractor(String url, HttpMethod method,
      @Nullable Map<String, String> requestHeaders,
      @Nullable Map<String, Object> requestParams,
      @Nullable Map<String, Object> requestBody,
      HttpServletResponse response,
      @Nullable Boolean requireBodyOnly) {
    if (requireBodyOnly == null) {
      requireBodyOnly = true;
    }
    MultiValueMap<String, Object> params  = new LinkedMultiValueMap<String, Object>();
    MultiValueMap<String, Object> body    = new LinkedMultiValueMap<String, Object>();
    HttpHeaders                   headers = new HttpHeaders();

    //1.设置请求参数
    if (requestParams != null && !requestParams.isEmpty()) {
      Iterator<Entry<String, Object>> paramsIterator = requestParams.entrySet().iterator();
      Entry<String, Object>           nextParam      = null;
      while (paramsIterator.hasNext()) {
        nextParam = paramsIterator.next();
        params.add(nextParam.getKey(), nextParam.getValue());
      }
    }
    //2.设置请求头
    if (requestHeaders != null && !requestHeaders.isEmpty()) {
      Iterator<Entry<String, String>> headersIterator = requestHeaders.entrySet().iterator();
      Entry<String, String>           nextHeader      = null;
      while (headersIterator.hasNext()) {
        nextHeader = headersIterator.next();
        headers.add(nextHeader.getKey(), nextHeader.getValue());
      }
    }
    //3.设置请求体
    if (requestBody != null && !requestBody.isEmpty()) {
      Iterator<Entry<String, Object>> bodyIterator = requestBody.entrySet().iterator();
      Entry<String, Object>           bodyNext     = null;
      while (bodyIterator.hasNext()) {
        bodyNext = bodyIterator.next();
        body.add(bodyNext.getKey(), bodyNext.getValue());
      }
    }
    //4.请求体 请求头封装到HttpEntity
    HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
    //5.执行
    RestTemplate         restTemplate = getSingleRestTemplate();
    BufferedOutputStream bos          = null;
    try {
      ResponseEntity<byte[]> exchange = restTemplate.exchange(url, method, entity, byte[].class, params);
      if (!requireBodyOnly && response != null) {
        //响应状态码
        response.setStatus(exchange.getStatusCodeValue());
        //响应头,可能存在一个key对应多个value,本方法中会将同名header合并
        HttpHeaders                           resHeaders         = exchange.getHeaders();
        Iterator<Entry<String, List<String>>> resHeadersIterator = resHeaders.entrySet().iterator();
        while (resHeadersIterator.hasNext()) {
          Entry<String, List<String>> headersNext = resHeadersIterator.next();
          response.setHeader(headersNext.getKey(), headersNext.getValue().toString());
        }
      }
      //响应体
      ServletOutputStream os = response.getOutputStream();
      bos = new BufferedOutputStream(os);
      byte[] buf = exchange.getBody();
      bos.write(buf);
      bos.flush();
    } catch (IOException e) {
      logger.error("RestTemplateUtils获取接口二进制流异常");
    } catch (RestClientException e) {
      logger.error("远程调用接口异常{}", e);
    } finally {
      try {
        bos.close();
      } catch (IOException e) {
        logger.error("RestTemplateUtils流关闭异常");
      }
    }
  }

  /**
   * 基础方法,请求接口,返回JSON
   *
   * @param url 不包含请求参数 即 ?param={}
   * @param method POST或GET
   * @param requestHeaders 请求头,可为空
   * @param requestParams 请求参数,可为空
   * @param requestBody 请求体,可为空
   * @param requireBodyOnly 是否是只需要响应体,默认true,true:只需要响应体  false:同时返回响应状态 响应头  响应体
   * @return String Json
   **/
  public static String baseJsonTracktor(String url, HttpMethod method,
      @Nullable Map<String, String> requestHeaders,
      @Nullable Map<String, Object> requestParams,
      @Nullable Map<String, Object> requestBody,
      @Nullable HttpServletResponse response,
      @Nullable Boolean requireBodyOnly) {
    if (requireBodyOnly == null) {
      requireBodyOnly = true;
    }
    MultiValueMap<String, Object> params  = new LinkedMultiValueMap<String, Object>();
    MultiValueMap<String, Object> body    = new LinkedMultiValueMap<String, Object>();
    HttpHeaders                   headers = new HttpHeaders();

    //1.设置请求参数
    if (requestParams != null && !requestParams.isEmpty()) {
      Iterator<Entry<String, Object>> paramsIterator = requestParams.entrySet().iterator();
      Entry<String, Object>           nextParam      = null;
      while (paramsIterator.hasNext()) {
        nextParam = paramsIterator.next();
        params.add(nextParam.getKey(), nextParam.getValue());
      }
    }
    //2.设置请求头
    if (requestHeaders != null && !requestHeaders.isEmpty()) {
      Iterator<Entry<String, String>> headersIterator = requestHeaders.entrySet().iterator();
      Entry<String, String>           nextHeader      = null;
      while (headersIterator.hasNext()) {
        nextHeader = headersIterator.next();
        headers.add(nextHeader.getKey(), nextHeader.getValue());
      }
    }
    //3.设置请求体
    if (requestBody != null && !requestBody.isEmpty()) {
      Iterator<Entry<String, Object>> bodyIterator = requestBody.entrySet().iterator();
      Entry<String, Object>           bodyNext     = null;
      while (bodyIterator.hasNext()) {
        bodyNext = bodyIterator.next();
        body.add(bodyNext.getKey(), bodyNext.getValue());
      }
    }
    //4.请求体 请求头封装到HttpEntity
    HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
    //5.执行
    RestTemplate restTemplate = getSingleRestTemplate();
    String       bodyJson     = "";
    try {
      ResponseEntity<String> exchange = restTemplate.exchange(url, method, entity, String.class, params);
      if (!requireBodyOnly && response != null) {
        //响应状态码
        response.setStatus(exchange.getStatusCodeValue());
        //响应头,可能存在一个key对应多个value,本方法中会将同名header合并
        HttpHeaders                           resHeaders         = exchange.getHeaders();
        Iterator<Entry<String, List<String>>> resHeadersIterator = resHeaders.entrySet().iterator();
        while (resHeadersIterator.hasNext()) {
          Entry<String, List<String>> headersNext = resHeadersIterator.next();
          response.setHeader(headersNext.getKey(), headersNext.getValue().toString());
        }
      }
      bodyJson = exchange.getBody();
    } catch (RestClientException e) {
      logger.error("远程调用接口异常{}", e);
    }
    return bodyJson;
  }

  /**
   * GET请求 下载流
   *
   * @param url 不包含请求参数 即 ?param={}
   * @param requestParams 请求参数,可为空
   * @param requireBodyOnly 是否是只需要响应体,默认true,true:只需要响应体  false:同时返回响应状态 响应头  响应体
   * @return void
   **/
  public static void getByte(String url, HttpServletResponse response,
      @Nullable Map<String, Object> requestParams,
      @Nullable Boolean requireBodyOnly) {
    if (requireBodyOnly == null) {
      requireBodyOnly = true;
    }
    baseByteTractor(url, HttpMethod.GET, null, requestParams, null, response, requireBodyOnly);
  }


  /**
   * POST请求 下载流
   *
   * @param url 不包含请求参数 即 ?param={}
   * @param requestParams 请求参数,可为空
   * @param requestBody 请求体,可为空
   * @param requireBodyOnly 是否是只需要响应体,默认true,true:只需要响应体  false:同时返回响应状态 响应头  响应体
   * @return void
   **/
  public static void postByte(String url, HttpServletResponse response,
      @Nullable Map<String, Object> requestParams,
      @Nullable Map<String, Object> requestBody,
      @Nullable Boolean requireBodyOnly) {
    if (requireBodyOnly == null) {
      requireBodyOnly = true;
    }
    baseByteTractor(url, HttpMethod.POST, null, requestParams, requestBody, response, requireBodyOnly);
  }


  /**
   * GET请求 获取JSON
   *
   * @param url 不包含请求参数 即 ?param={}
   * @param requestParams 请求参数,可为空
   * @param requireBodyOnly 是否是只需要响应体,默认true,true:只需要响应体  false:同时返回响应状态 响应头  响应体
   * @return void
   **/
  public static String getJson(String url,
      @Nullable HttpServletResponse response,
      @Nullable Map<String, Object> requestParams,
      @Nullable Boolean requireBodyOnly) {
    if (requireBodyOnly == null) {
      requireBodyOnly = true;
    }
    String resJson = baseJsonTracktor(url, HttpMethod.GET, null, requestParams, null, response, requireBodyOnly);
    return resJson;
  }


  /**
   * POST请求 获取JSON
   *
   * @param url 不包含请求参数 即 ?param={}
   * @param requestParams 请求参数,可为空
   * @param requestBody 请求体,可为空
   * @param requireBodyOnly 是否是只需要响应体,默认true,true:只需要响应体  false:同时返回响应状态 响应头  响应体
   * @return void
   **/
  public static String postJson(String url,
      @Nullable HttpServletResponse response,
      @Nullable Map<String, Object> requestParams,
      @Nullable Map<String, Object> requestBody,
      @Nullable Boolean requireBodyOnly) {
    if (requireBodyOnly == null) {
      requireBodyOnly = true;
    }
    String resJson = baseJsonTracktor(url, HttpMethod.POST, null, requestParams, requestBody, response, requireBodyOnly);
    return resJson;
  }


}

3.2演示

BYTE
JSON

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮你封装一个RestTemplate工具类。下面是一个基本的RestTemplate工具类的示例代码: ```java import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; public class RestTemplateUtil { private RestTemplate restTemplate; public RestTemplateUtil() { this.restTemplate = new RestTemplate(); } public ResponseEntity<String> get(String url) { return restTemplate.exchange(url, HttpMethod.GET, null, String.class); } public ResponseEntity<String> post(String url, Object body) { HttpEntity<Object> requestEntity = new HttpEntity<>(body); return restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); } // 添加其他常用方法,如PUT、DELETE等 } ``` 上述代码封装了`RestTemplate`的GET和POST请求方法,你可以根据需要添加其他常用的HTTP方法(如PUT、DELETE等)。 使用时,你只需创建一个`RestTemplateUtil`对象,并调用相应的方法即可,示例如下: ```java RestTemplateUtil restTemplateUtil = new RestTemplateUtil(); // 发起GET请求 ResponseEntity<String> response = restTemplateUtil.get("http://example.com/api/resource"); // 发起POST请求 SomeRequestObject requestObject = new SomeRequestObject(); ResponseEntity<String> response = restTemplateUtil.post("http://example.com/api/resource", requestObject); // 处理响应 String responseBody = response.getBody(); // 其他操作... ``` 这样,你就可以方便地使用`RestTemplate`发送HTTP请求了。希望对你有所帮助!如果还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值