0


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> 源码截图:在这里插入图片描述
<T>ResponseEntity<T>exchange(String url
    ,HttpMethod method
    ,@NullableHttpEntity<?> requestEntity
    ,Class<T> responseType
    ,Object... uriVariables)throwsRestClientException;

参数说明url调用的url地址method枚举值,HTTP方法:GET、POST、PUT、DELETE等requestEntity发起请求时携带的对象:请求头header 和/或 请求体bodyresponseType请求响应对象的类型uriVariables就是针对url中的@PathVariable参数,可变长度参数列表

  • responseType是ParameterizedTypeReference<T> 源码截图:在这里插入图片描述
<T>ResponseEntity<T>exchange(String url
    ,HttpMethod method
    ,@NullableHttpEntity<?> requestEntity
    ,ParameterizedTypeReference<T> responseType
    ,Object... uriVariables)throwsRestClientException;

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

匿名类

**,像这样:

ParameterizedTypeReference<List<String>> typeRef =newParameterizedTypeReference<List<String>>(){};

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

1. Get请求

1.1 返回基本类型

  • 场景模拟根据用户id获取name
  • 发起exchange的代码
// 1.1 get请求返回基本类型@GetMapping("/name")publicStringgetName(@RequestParam("id")Integer id){String url ="http://localhost:8080/demo/name/mock?id="+ id;return restTemplate.exchange(url,HttpMethod.GET,null,String.class).getBody();}
  • 被调用的代码
@GetMapping("/name/mock")publicStringmockName(@RequestParam("id")Integer id){return"天罡"+ id;}

这里用的重载是:responseType是Class<T>

1.2 返回自定义对象类型

其实自定义的对象和String调用是一样的,只需要将返回类型

String.class

改成

DTO.class

即可.

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

这里用的重载也是:responseType是Class<T>

1.3 返回List类型

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

将responseType

换成

ParameterizedTypeReference<T>

  • 场景模拟根据用户name 查找 匹配的用户 这里可能返回多条结果,所以返回类型我们使用泛型List
  • 发起exchange的代码通过ParameterizedTypeReference指定返回的List
// 1.3 get请求返回List<T>类型@GetMapping("/user/list")publicList<UserDto>getUserList(@RequestParam("name")String name){String url ="http://localhost:8080/demo/user/list/mock?name="+ name;ParameterizedTypeReference<List<UserDto>> responseBodyType =newParameterizedTypeReference<List<UserDto>>(){};return restTemplate.exchange(url,HttpMethod.GET,null, responseBodyType).getBody();}
  • 被调用的代码
@GetMapping("/user/list/mock")publicList<UserDto>mockUserList(@RequestParam("name")String name){List<UserDto> list =newArrayList<>();for(int i =1; i <3; i++){
            list.add(UserDto.builder().id(i).name(name + i).age(i +10).birthday(newDate()).build());}return list;}
  • 验证: 如期望一样,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.4 返回Map 类型

List<T>只有一个参数,我们来试试有两个参数的Map<K,V>

  • 场景模拟根据关键字查找,不同的类型,返回不同字段 因为返回结果字段不固定,所以万能的Map绝对是首选,来吧~
  • 发起exchange的代码依然通过ParameterizedTypeReference指定返回的Map
// 1.4 get请求返回Map类型@GetMapping("/user/map")publicMap<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 =newParameterizedTypeReference<Map<String,Object>>(){};return restTemplate.exchange(url,HttpMethod.GET,null, responseBodyType).getBody();}
  • 被调用的代码
@GetMapping("/user/map/mock")publicMap<String,Object>mockUserMap(@RequestParam(value ="type", required =true)Integer type,@RequestParam("key")String key){Map<String,Object> map =newHashMap<>();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.5 返回自定义泛型类型

这应该是在项目中用的最多的一种,就是

自定义包装类型

,而不是直接返回单一对象,而是返回统一的对象。

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

我们新建一个Result<T>类:

@DatapublicclassResult<TextendsSerializable>implementsSerializable{privateboolean success;privateString code;privateString message;privateT data;publicstatic<TextendsSerializable>Result<T>success(String code,String message,T data){Result<T> result =newResult<>();
        result.setCode(code);
        result.setMessage(message);
        result.setData(data);
        result.setSuccess(true);return result;}publicstatic<TextendsSerializable>Result<T>success(T data){returnsuccess("200","成功", data);}publicstatic<TextendsSerializable>Result<T>fail(String code,String message){Result<T> result =newResult<>();
        result.setCode(code);
        result.setMessage(message);
        result.setSuccess(false);return result;}}
  • 发起exchange的代码依然通过ParameterizedTypeReference指定返回的Result<T>
// 1.5 get请求返回自定义泛型类型@GetMapping("/user/result")publicResult<UserDto>getUserResult(@RequestParam("id")Integer id){String url ="http://localhost:8080/demo/user/result/mock?id="+ id;ParameterizedTypeReference<Result<UserDto>> responseBodyType =newParameterizedTypeReference<Result<UserDto>>(){};return restTemplate.exchange(url,HttpMethod.GET,null, responseBodyType).getBody();}
  • 被调用的代码
@GetMapping("/user/result/mock")publicResult<UserDto>mockUserResult(@RequestParam("id")Integer id){if(id ==null|| id <=0){returnResult.fail("400","id不合法!");}if(id %2==0){// 这里只是模拟异常情况returnResult.fail("500","操作失败,访问量太大了!");}UserDto userDto =UserDto.builder().id(id).name("天罡"+ id).age(id +18).birthday(newDate()).build();returnResult.success("200","成功", userDto);}
  • 验证: 正是我们想要的,完全符合预期!💪💪💪示例1:请求返回400- 请求url地址1http://localhost:8080/demo/user/result?id=0- 返回{"success": false, "code":"400", "message":"id不合法!", "data": null}示例2:请求返回200- 请求url地址2http://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"}}示例3:请求返回500- 请求url地址3http://localhost:8080/demo/user/result?id=2- 返回{"success": false, "code":"500", "message":"操作失败,访问量太大了!", "data": null}

2.Post请求

2.1 传header+body返回对象类型

  • 场景模拟通过@RequestBody传参,[可选]指定header,获取用户信息
  • 发起exchange的代码
@GetMapping("/user/body")publicUserDtopostUser(@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(newDate()).build();// header根据实际情况设置,没有就空着HttpHeaders headers =newHttpHeaders();
        headers.add("AccessKey","自定义的API访问key");
        headers.add("Content-Type","application/json");HttpEntity<?> requestEntity =newHttpEntity<>(body, headers);return restTemplate.exchange(url,HttpMethod.POST, requestEntity,UserDto.class).getBody();}
  • 被调用的代码
@PostMapping("/user/body/mock")publicUserDtomockPostUser(@RequestBodyUserDto userParam){return userParam;}

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

和返回普通类型的区别还是

将responseType换成ParameterizedTypeReference
  • 场景模拟通过@RequestBody传参,[可选]指定header,获取自定义包装类型的用户信息
  • 发起exchange的代码
@GetMapping("/user/result/body")publicResult<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(newDate()).build();// header根据实际情况设置,没有就空着HttpHeaders headers =newHttpHeaders();
    headers.add("AccessKey","自定义的API访问key");
    headers.add("Content-Type","application/json");HttpEntity<?> requestEntity =newHttpEntity<>(body, headers);ParameterizedTypeReference<Result<UserDto>> responseBodyType =newParameterizedTypeReference<Result<UserDto>>(){};return restTemplate.exchange(url,HttpMethod.POST, requestEntity, responseBodyType).getBody();}
  • 被调用的代码
@PostMapping("/user/result/body/mock")publicResult<UserDto>mockPostUserResult(@RequestBodyUserDto userParam){returnResult.success("200","成功", userParam);}
  • 验证: 不出所料,如期望一样,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"}}

3. 异常情况处理

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

  1. 本身抛出的 throws RestClientException
  2. 返回的ResponseEntity的Code不等于200
  • 普通类型:
public<T>TexchangeForEntity(HttpMethod httpMethod,String url,HttpHeaders headers,Object body
            ,Class<T> responseType){HttpEntity<?> requestEntity =null;if(headers !=null|| body !=null){
        requestEntity =newHttpEntity<>(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();}returnnull;}
  • 泛型类型:只需要将普通类型的入参Class<T>改成 ParameterizedTypeReference<T>
public<T>TexchangeForWarpEntity(HttpMethod httpMethod,String url,HttpHeaders headers,Object body
                ,ParameterizedTypeReference<T> responseBodyType){HttpEntity<?> requestEntity =null;if(headers !=null|| body !=null){
        requestEntity =newHttpEntity<>(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();}returnnull;}

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

private<T>TexchangeInternal(HttpMethod httpMethod,String url,HttpHeaders headers,Object body
                ,Class<T> responseType1,ParameterizedTypeReference<T> responseType2)

4. RestTemplate配置@Bean

@ConfigurationpublicclassRestTemplateConfig{@BeanpublicRestTemplaterestTemplate(ClientHttpRequestFactory clientHttpRequestFactory){RestTemplate restTemplate =newRestTemplate(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(newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"));// 忽略大小写
                    objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES,true);});return restTemplate;}@BeanpublicClientHttpRequestFactoryclientHttpRequestFactory(){// 如果使用okHttpClient需要引入jar包:okhttp// OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();SimpleClientHttpRequestFactory factory =newSimpleClientHttpRequestFactory();
        factory.setConnectTimeout(15000);
        factory.setReadTimeout(30000);return factory;}}

相关博文

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

最后

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

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

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



本文转载自: https://blog.csdn.net/scm_2008/article/details/127682333
版权归原作者 天罡gg 所有, 如有侵权,请联系我们删除。

“RestTemplate.exchange各种用法(包括泛型等 --全)”的评论:

还没有评论