• 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 responseEntity =
                restTemplate.getForEntity("http://localhost:8080/pic/pic1?name={name}&sex={sex}", byte[].class,
                        "名字", "性别");
        ServletOutputStream os = response.getOutputStream();
        os.write(responseEntity.getBody());
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    2.1.2@PathVariable案例

    大同小异

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

    2.2post携带请求体

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

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

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

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

    2.2.2 不能使用HashMap

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

    例如:

         HashMap hashMap = new HashMap<>();
        hashMap.put("name","张三");
        hashMap.put("age","1");
        hashMap.put("tall","181");
    
        //最后一个可变数组的形参不用指定
        ResponseEntity res =
                restTemplate.postForEntity("http://localhost:8080/pic/people/pic1", hashMap, byte[].class);
        ServletOutputStream os = response.getOutputStream();
        os.write(res.getBody());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.2.3 使用SpringMVC下的map

    MultiValueMap map = new LinkedMultiValueMap();

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

        MultiValueMap map = new LinkedMultiValueMap();
        map.add("name","张三");
        map.add("age","11");
        map.add("tall","181");
    
        //最后一个可变数组的形参不用指定
        ResponseEntity res =
                restTemplate.postForEntity("http://localhost:8080/pic/people/pic1", map, byte[].class);
        ServletOutputStream os = response.getOutputStream();
        os.write(res.getBody());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    • 请求体写在MultiValueMap

    • 请求头写在HttpHeaders

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

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

    2.2.5同时请求体 请求参数

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

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

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

    HashMap urls = new HashMap<>();
    urls.put("param1","测试1");
    
    ResponseEntity res = restTemplate.exchange("http://localhost:8080/pic/withbody", HttpMethod.POST,
        httpEntity, byte[].class, urls
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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 requestHeaders,
          @Nullable Map requestParams,
          @Nullable Map requestBody,
          HttpServletResponse response,
          @Nullable Boolean requireBodyOnly) {
        if (requireBodyOnly == null) {
          requireBodyOnly = true;
        }
        MultiValueMap params  = new LinkedMultiValueMap();
        MultiValueMap body    = new LinkedMultiValueMap();
        HttpHeaders                   headers = new HttpHeaders();
    
        //1.设置请求参数
        if (requestParams != null && !requestParams.isEmpty()) {
          Iterator> paramsIterator = requestParams.entrySet().iterator();
          Entry           nextParam      = null;
          while (paramsIterator.hasNext()) {
            nextParam = paramsIterator.next();
            params.add(nextParam.getKey(), nextParam.getValue());
          }
        }
        //2.设置请求头
        if (requestHeaders != null && !requestHeaders.isEmpty()) {
          Iterator> headersIterator = requestHeaders.entrySet().iterator();
          Entry           nextHeader      = null;
          while (headersIterator.hasNext()) {
            nextHeader = headersIterator.next();
            headers.add(nextHeader.getKey(), nextHeader.getValue());
          }
        }
        //3.设置请求体
        if (requestBody != null && !requestBody.isEmpty()) {
          Iterator> bodyIterator = requestBody.entrySet().iterator();
          Entry           bodyNext     = null;
          while (bodyIterator.hasNext()) {
            bodyNext = bodyIterator.next();
            body.add(bodyNext.getKey(), bodyNext.getValue());
          }
        }
        //4.请求体 请求头封装到HttpEntity
        HttpEntity> entity = new HttpEntity<>(body, headers);
        //5.执行
        RestTemplate         restTemplate = getSingleRestTemplate();
        BufferedOutputStream bos          = null;
        try {
          ResponseEntity 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>> resHeadersIterator = resHeaders.entrySet().iterator();
            while (resHeadersIterator.hasNext()) {
              Entry> 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 requestHeaders,
          @Nullable Map requestParams,
          @Nullable Map requestBody,
          @Nullable HttpServletResponse response,
          @Nullable Boolean requireBodyOnly) {
        if (requireBodyOnly == null) {
          requireBodyOnly = true;
        }
        MultiValueMap params  = new LinkedMultiValueMap();
        MultiValueMap body    = new LinkedMultiValueMap();
        HttpHeaders                   headers = new HttpHeaders();
    
        //1.设置请求参数
        if (requestParams != null && !requestParams.isEmpty()) {
          Iterator> paramsIterator = requestParams.entrySet().iterator();
          Entry           nextParam      = null;
          while (paramsIterator.hasNext()) {
            nextParam = paramsIterator.next();
            params.add(nextParam.getKey(), nextParam.getValue());
          }
        }
        //2.设置请求头
        if (requestHeaders != null && !requestHeaders.isEmpty()) {
          Iterator> headersIterator = requestHeaders.entrySet().iterator();
          Entry           nextHeader      = null;
          while (headersIterator.hasNext()) {
            nextHeader = headersIterator.next();
            headers.add(nextHeader.getKey(), nextHeader.getValue());
          }
        }
        //3.设置请求体
        if (requestBody != null && !requestBody.isEmpty()) {
          Iterator> bodyIterator = requestBody.entrySet().iterator();
          Entry           bodyNext     = null;
          while (bodyIterator.hasNext()) {
            bodyNext = bodyIterator.next();
            body.add(bodyNext.getKey(), bodyNext.getValue());
          }
        }
        //4.请求体 请求头封装到HttpEntity
        HttpEntity> entity = new HttpEntity<>(body, headers);
        //5.执行
        RestTemplate restTemplate = getSingleRestTemplate();
        String       bodyJson     = "";
        try {
          ResponseEntity 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>> resHeadersIterator = resHeaders.entrySet().iterator();
            while (resHeadersIterator.hasNext()) {
              Entry> 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 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 requestParams,
          @Nullable Map 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 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 requestParams,
          @Nullable Map requestBody,
          @Nullable Boolean requireBodyOnly) {
        if (requireBodyOnly == null) {
          requireBodyOnly = true;
        }
        String resJson = baseJsonTracktor(url, HttpMethod.POST, null, requestParams, requestBody, response, requireBodyOnly);
        return resJson;
      }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297

    3.2演示

    BYTE
    JSON

  • 相关阅读:
    为了让线上代码可追溯, 我开发了这个vite插件
    每日一题——LeetCode1496.判断路径是否相交
    算法基础学习|前缀和差分
    C++文件的读取和写入
    Https握手过程
    uniapp获取公钥、MD5,‘keytool‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。
    Linux驱动开发(九)---树莓派I2C设备驱动开发(BME280)
    Python基础入门例程3-NP3 读入字符串
    Python自学笔记8:实操案例五(循环输出26个字母对应的ASCII码值,模拟用户登录,猜数游戏,计算100-999之间的水仙花数)
    salesforce点击classic按钮调用lightning组件
  • 原文地址:https://blog.csdn.net/m0_56079407/article/details/126054219