• RestTemplate.exchange各种用法(包括泛型等 --全)



    前言

    在我们日常开发中,无论是内部服务之间的调用,还是调用第三方服务,都免不了发起Http请求,在Java中发起Http请求常见的方式大致有原生HttpURLConnection、Apache的HttpClient、Spring的RestTemplate等,如果您基于Spring框架,那么强烈推荐使用RestTemplate,理由很简单:非常符合我们发起http请求的习惯,就像使用postman,只需要关心具体的url、header、body等即可,对于繁琐的细节RestTemplate都帮我们安排(封装)的明明白白,无关的细节我们统统不用操心! 尤其是RestTemplate.exchange方法,可以称的上是单靠一招就可以吊打其它方式。。。 所以本文就来详细介绍一下RestTemplate.exchange各种用法,力求覆盖日常开发中的各种场景,Let’s start~~

    exchange方法简介

    exchange有多个重载,我们常用的掌握以下这两个就够了

    • responseType是Class
      源码截图:
      在这里插入图片描述
    <T> ResponseEntity<T> exchange(String url
    	, HttpMethod method
    	, @Nullable HttpEntity<?> requestEntity
    	, Class<T> responseType
    	, Object... uriVariables) throws RestClientException;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    参数说明
    url调用的url地址
    method枚举值,HTTP方法:GET、POST、PUT、DELETE等
    requestEntity发起请求时携带的对象:请求头header 和/或 请求体body
    responseType请求响应对象的类型
    uriVariables就是针对url中的@PathVariable参数,可变长度参数列表
    • responseType是ParameterizedTypeReference
      源码截图:
      在这里插入图片描述
    <T> ResponseEntity<T> exchange(String url
    	, HttpMethod method
    	, @Nullable HttpEntity<?> requestEntity
    	, ParameterizedTypeReference<T> responseType
    	, Object... uriVariables) throws RestClientException;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    与上面重载的唯一区别responseType类型变成了ParameterizedTypeReference,其它参数说明不变.
    设计这个类的目的:是允许传递泛型类型。用法建议是使用匿名类,像这样:

    ParameterizedTypeReference<List<String>> typeRef = new ParameterizedTypeReference<List<String>>() {};
    
    • 1

    好了,不多做介绍,我们直接上案例:

    1. Get请求

    这里准备了5个常见场景:
    1.1 返回基本类型
    1.2 返回自定义对象类型
    1.3 返回List类型
    1.4 返回Map类型
    1.5 返回自定义泛型类型

    1.1 返回基本类型

    • 场景模拟根据用户id获取name

    • 发起exchange的代码

        // 1.1 get请求返回基本类型
        @GetMapping("/name")
        public String getName(@RequestParam("id") Integer id) {
            String url = "http://localhost:8080/demo/name/mock?id=" + id;
            return restTemplate.exchange(url, HttpMethod.GET, null, String.class).getBody();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 被调用的代码
        @GetMapping("/name/mock")
        public String mockName(@RequestParam("id") Integer id) {
            return "天罡" + id;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 验证:
      如期望一样,不得不说就应该这么简洁,good~~ 💪💪💪

      • 请求url地址: http://localhost:8080/demo/name?id=123
      • 返回: 天罡123

    这里用的重载是:responseType是Class

    1.2 返回自定义对象类型

    其实自定义的对象和String调用是一样的,只需要将返回类型String.class改成DTO.class即可.

    • 场景模拟根据用户id获取用户信息
      新建一个UserDto对象:
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class UserDto implements Serializable {
        private Integer id;
        private String name;
        private Integer age;
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date birthday;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 发起exchange的代码
        // 1.2 get请求返回对象类型
        @GetMapping("/user")
        public UserDto getUser(@RequestParam("id") Integer id) {
            String url = "http://localhost:8080/demo/user/mock?id=" + id;
            return restTemplate.exchange(url, HttpMethod.GET, null, UserDto.class).getBody();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 被调用的代码
        @GetMapping("/user/mock")
        public UserDto mockUser(@RequestParam("id") Integer id) {
            return UserDto.builder().id(id)
                    .name("天罡" + id)
                    .age(id + 18)
                    .birthday(new Date()).build();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 验证:
      如期望一样,good~~ 💪💪💪
      • 请求url地址: http://localhost:8080/demo/user?id=1
      • 返回
        {
        	"id": 1,
        	"name": "天罡",
        	"age": 19,
        	"birthday": "2022-11-06 05:35:43"
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6

    这里用的重载也是:responseType是Class

    1.3 返回List类型

    对于泛型类型,我们需要使用exchange的另一个针对泛型的重载方法,即将responseType换成ParameterizedTypeReference

    • 场景模拟根据用户name 查找 匹配的用户
      这里可能返回多条结果,所以返回类型我们使用泛型List

    • 发起exchange的代码
      通过ParameterizedTypeReference指定返回的List

        // 1.3 get请求返回List类型
        @GetMapping("/user/list")
        public List<UserDto> getUserList(@RequestParam("name") String name) {
            String url = "http://localhost:8080/demo/user/list/mock?name=" + name;
            ParameterizedTypeReference<List<UserDto>> responseBodyType = new ParameterizedTypeReference<List<UserDto>>() {};
            return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 被调用的代码
        @GetMapping("/user/list/mock")
        public List<UserDto> mockUserList(@RequestParam("name") String name) {
            List<UserDto> list = new ArrayList<>();
            for (int i = 1; i < 3; i++) {
                list.add(UserDto.builder().id(i)
                        .name(name + i)
                        .age(i + 10)
                        .birthday(new Date()).build());
            }
            return list;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 验证:
      如期望一样,good~~ 💪💪💪
      • 请求url地址: http://localhost:8080/demo/user/list?name=天罡
      • 返回
        [{
        	"id": 1,
        	"name": "天罡1",
        	"age": 11,
        	"birthday": "2022-11-06 21:44:24"
        },
        {
        	"id": 2,
        	"name": "天罡2",
        	"age": 12,
        	"birthday": "2022-11-06 21:44:24"
        }]
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12

    1.4 返回Map 类型

    List只有一个参数,我们来试试有两个参数的Map

    • 场景模拟根据关键字查找,不同的类型,返回不同字段
      因为返回结果字段不固定,所以万能的Map绝对是首选,来吧~

    • 发起exchange的代码
      依然通过ParameterizedTypeReference指定返回的Map

        // 1.4 get请求返回Map类型
        @GetMapping("/user/map")
        public Map<String, Object> getUserMap(@RequestParam(value = "type", required = true) Integer type, @RequestParam("key") String key) {
            String url = "http://localhost:8080/demo/user/map/mock?type=" + type + "&key=" + key;
            ParameterizedTypeReference<Map<String, Object>> responseBodyType = new ParameterizedTypeReference<Map<String, Object>>() {};
            return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 被调用的代码
        @GetMapping("/user/map/mock")
        public Map<String, Object> mockUserMap(@RequestParam(value = "type", required = true) Integer type, @RequestParam("key") String key) {
            Map<String, Object> map = new HashMap<>();
            if (type.equals(1)) {
                map.put("id", 1);
                map.put("name" + type, "hello" + key);
            } else {
                map.put("id", 2);
                map.put("name" + type, "hello" + key);
            }
            return map;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 验证:
      根据不同类型返回不同字段了, 漂亮~~💪💪💪 仔细看两次请求返回的字段,一次是name1,一次是name2

      示例1:请求类型=1

      • 请求url地址1: http://localhost:8080/demo/user/map?type=1&key=123
      • 返回
        {
        	"id": 1,
        	"`name1`": "hello123"
        }
        
        • 1
        • 2
        • 3
        • 4

      示例2:请求类型=2

      • 请求url地址2: http://localhost:8080/demo/user/map?type=2&key=456
      • 返回
        {
        	"id": 2,
        	"`name2`": "hello456"
        }
        
        • 1
        • 2
        • 3
        • 4

    1.5 返回自定义泛型类型

    这应该是在项目中用的最多的一种,就是自定义包装类型,而不是直接返回单一对象,而是返回统一的对象。

    我们在1.2 返回自定义对象类型模拟过根据用户id获取用户信息的场景,但未处理非法请求、异常等情况,所以接下来我们自定一个通用的自定义Code的泛型返回结果,对1.2做一下增强。

    • 场景模拟根据用户id获取用户信息,根据不同情况返回不同编码

    我们新建一个Result类:

    @Data
    public class Result<T extends Serializable> implements Serializable {
        private boolean success;
        private String code;
        private String message;
        private T data;
    
        public static <T extends Serializable> Result<T> success(String code, String message, T data) {
            Result<T> result = new Result<>();
            result.setCode(code);
            result.setMessage(message);
            result.setData(data);
            result.setSuccess(true);
            return result;
        }
    
        public static <T extends Serializable> Result<T> success(T data) {
            return success("200", "成功", data);
        }
    
        public static <T extends Serializable> Result<T> fail(String code, String message) {
            Result<T> result = new Result<>();
            result.setCode(code);
            result.setMessage(message);
            result.setSuccess(false);
            return result;
        }
    }
    
    • 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
    • 发起exchange的代码
      依然通过ParameterizedTypeReference指定返回的Result
        // 1.5 get请求返回自定义泛型类型
        @GetMapping("/user/result")
        public Result<UserDto> getUserResult(@RequestParam("id") Integer id) {
            String url = "http://localhost:8080/demo/user/result/mock?id=" + id;
            ParameterizedTypeReference<Result<UserDto>> responseBodyType = new ParameterizedTypeReference<Result<UserDto>>() {};
            return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 被调用的代码
    	@GetMapping("/user/result/mock")
        public Result<UserDto> mockUserResult(@RequestParam("id") Integer id) {
            if (id == null || id <= 0) {
                return Result.fail("400", "id不合法!");
            }
            if (id % 2 == 0) {
                // 这里只是模拟异常情况
                return Result.fail("500", "操作失败,访问量太大了!");
            }
            UserDto userDto = UserDto.builder().id(id)
                    .name("天罡" + id)
                    .age(id + 18)
                    .birthday(new Date()).build();
            return Result.success("200", "成功", userDto);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 验证:
      正是我们想要的,完全符合预期!💪💪💪

      示例1:请求返回400

      • 请求url地址1: http://localhost:8080/demo/user/result?id=0
      • 返回
         {
        	"success": false,
        	"code": "400",
        	"message": "id不合法!",
        	"data": null
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6

      示例2:请求返回200

      • 请求url地址2: http://localhost:8080/demo/user/result?id=1
      • 返回
        {
        	"success": true,
        	"code": "200",
        	"message": "成功",
        	"data": {
        		"id": 1,
        		"name": "天罡1",
        		"age": 19,
        		"birthday": "2022-11-07 04:03:09"
        	}
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11

      示例3:请求返回500

      • 请求url地址3: http://localhost:8080/demo/user/result?id=2
      • 返回
        {
        	"success": false,
        	"code": "500",
        	"message": "操作失败,访问量太大了!",
        	"data": null
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6

    2.Post请求

    实际上对于exchange来说,POST与GET的使用方式基本一样,所以这里只准备2个demo主要演示如何传header和body
    2.1 传header+body返回对象类型
    2.2 传header+body返回自定义泛型类型

    2.1 传header+body返回对象类型

    • 场景模拟通过@RequestBody传参,[可选]指定header,获取用户信息
    • 发起exchange的代码
    	@GetMapping("/user/body")
        public UserDto postUser(@RequestParam("id") Integer id) {
            String url = "http://localhost:8080/demo/user/body/mock";
            UserDto body = UserDto.builder().id(id)
                    .name("body" + id)
                    .age(id + 18)
                    .birthday(new Date()).build();
            // header根据实际情况设置,没有就空着
            HttpHeaders headers = new HttpHeaders();
            headers.add("AccessKey", "自定义的API访问key");
            headers.add("Content-Type", "application/json");
            HttpEntity<?> requestEntity = new HttpEntity<>(body, headers);
            return restTemplate.exchange(url, HttpMethod.POST, requestEntity, UserDto.class).getBody();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 被调用的代码
        @PostMapping("/user/body/mock")
        public UserDto mockPostUser(@RequestBody UserDto userParam) {
            return userParam;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 验证:
      如期望一样,good~~ 💪💪💪

      • 请求url地址: http://localhost:8080/demo/user/body?id=1
      • 返回
        {
        	"id": 1,
        	"name": "body1",
        	"age": 19,
        	"birthday": "2022-11-06 21:20:41"
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6

    2.2 传header+body返回自定义泛型类型

    和返回普通类型的区别还是将responseType换成ParameterizedTypeReference

    • 场景模拟通过@RequestBody传参,[可选]指定header,获取自定义包装类型的用户信息

    • 发起exchange的代码

    @GetMapping("/user/result/body")
    public Result<UserDto> postUserResult(@RequestParam("id") Integer id) {
        String url = "http://localhost:8080/demo/user/result/body/mock";
        UserDto body = UserDto.builder().id(id)
                .name("body" + id)
                .age(id + 10)
                .birthday(new Date()).build();
        // header根据实际情况设置,没有就空着
        HttpHeaders headers = new HttpHeaders();
        headers.add("AccessKey", "自定义的API访问key");
        headers.add("Content-Type", "application/json");
        HttpEntity<?> requestEntity = new HttpEntity<>(body, headers);
        ParameterizedTypeReference<Result<UserDto>> responseBodyType = new ParameterizedTypeReference<Result<UserDto>>(){};
        return restTemplate.exchange(url, HttpMethod.POST, requestEntity, responseBodyType).getBody();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 被调用的代码
        @PostMapping("/user/result/body/mock")
        public Result<UserDto> mockPostUserResult(@RequestBody UserDto userParam) {
            return Result.success("200", "成功", userParam);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 验证:
      不出所料,如期望一样,good~~ 💪💪💪

      • 请求url地址: http://localhost:8080/demo/user/result/body?id=1
      • 返回
        {
        	"success": true,
        	"code": "200",
        	"message": "成功",
        	"data": {
        		"id": 1,
        		"name": "body1",
        		"age": 11,
        		"birthday": "2022-11-06 21:25:25"
        	}
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11

    3. 异常情况处理

    上面例子均未做异常处理,在这项目中使用难免不够健壮,所以我们通常会处理两种异常情况:

    1. 本身抛出的 throws RestClientException
    2. 返回的ResponseEntity的Code不等于200
    • 普通类型:
    public <T> T exchangeForEntity(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
    			, Class<T> responseType) {
        HttpEntity<?> requestEntity = null;
        if (headers != null || body != null) {
            requestEntity = new HttpEntity<>(body, headers);
        }
        try {
            ResponseEntity<T> responseEntity = restTemplate.exchange(url, httpMethod, requestEntity, responseType);
            if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
                return responseEntity.getBody();
            } else {
                // 处理Code不等于200的情况, 这里只简单打印,你需要根据你们项目的情况修改合适的处理方式
                System.out.println("返回结果不等于200:code=" + responseEntity.getStatusCode().value()
                        + " reason=" + responseEntity.getStatusCode().getReasonPhrase());
            }
        } catch (RestClientException e) {
            // 处理RestClientException
            e.printStackTrace();
        }
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 泛型类型:
      只需要将普通类型的入参Class改成 ParameterizedTypeReference
    public <T> T exchangeForWarpEntity(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
    				, ParameterizedTypeReference<T> responseBodyType) {
        HttpEntity<?> requestEntity = null;
        if (headers != null || body != null) {
            requestEntity = new HttpEntity<>(body, headers);
        }
        try {
            ResponseEntity<T> responseEntity = restTemplate.exchange(url, httpMethod, requestEntity, responseBodyType);
            if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
                return responseEntity.getBody();
            } else {
                // 处理Code不等于200的情况, 这里只简单打印,你需要根据你们项目的情况修改合适的处理方式
                System.out.println("返回结果不等于200:code=" + responseEntity.getStatusCode().value()
                        + " reason=" + responseEntity.getStatusCode().getReasonPhrase());
            }
        } catch (RestClientException e) {
            // 处理RestClientException, 这里只简单打印
            e.printStackTrace();
        }
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    如果觉得这样重复代码太多了,那就在内部实现一个方法,对外开放两个方法即可,内部实现的方法类似这样,具体不做赘述!

    private <T> T exchangeInternal(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
    				, Class<T> responseType1, ParameterizedTypeReference<T> responseType2)
    
    • 1
    • 2

    4. RestTemplate配置@Bean

    @Configuration
    public class RestTemplateConfig {
        @Bean
        public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
            RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
            restTemplate.getMessageConverters()
                    .stream()
                    .filter(MappingJackson2HttpMessageConverter.class::isInstance)
                    .map(MappingJackson2HttpMessageConverter.class::cast)
                    .findFirst()
                    .map(MappingJackson2HttpMessageConverter::getObjectMapper)
                    .ifPresent(objectMapper -> {
                        // 去掉默认的时间戳格式
                        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
                        // 设置为东八区
                        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
                        // 序列化时,日期的统一格式
                        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                        // 忽略大小写
                        objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
                    });
    
            return restTemplate;
        }
    
        @Bean
        public ClientHttpRequestFactory clientHttpRequestFactory(){
            // 如果使用okHttpClient需要引入jar包:okhttp
            // OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(15000);
            factory.setReadTimeout(30000);
            return factory;
        }
    }
    
    • 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

    相关博文

    在实际开发中,如果想自定义RestTemplate序列化,或者对于结果有相关处理,我们也可以取出来结果,然后再自己做序列化或验证,可以参考这位大佬的优质文章:RestTemplate使用实战-exchange方法讲解
    对于更多原理性分析,可以参考这位大佬的优质文章:RestTemplate总结

    最后

    除了Get和Post,我们常用的还有Put和Delete,由于Delete可以参考Get的用法,Put可以参考Post的用法,所以就不做赘述,如果您觉得还有哪些场景是没有覆盖到的,欢迎留言或私信~~

    注:如果本篇博客有任何错误和建议,欢迎评论指正!

    如果感觉不错,请收藏
    关注我 天罡gg 分享更多干货: https://blog.csdn.net/scm_2008
    大家的「关注 + 点赞 + 收藏」就是我创作的最大动力!谢谢大家的支持,我们下文见!


  • 相关阅读:
    开源AGV调度系统OpenTCS中的路由器(router)详解
    比nestjs更优雅的ioc:跨模块访问资源
    C#里创建配置文件并保存
    设计模式——单例模式详解
    视频集中存储/直播点播平台EasyDSS点播文件分类功能新升级
    admin后台管理
    【DevPress】帮助客户在CSDN社区之上,建立完全自主可控的开发者社区
    3A通过PMP考试有多大比例?
    docker-compose 微服务编排部署
    strongSwan对接H3C
  • 原文地址:https://blog.csdn.net/scm_2008/article/details/127682333