文章目录
前言
在我们日常开发中,无论是内部服务之间的调用,还是调用第三方服务,都免不了发起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
, @Nullable HttpEntity<?> requestEntity
, Class<T> responseType
, Object... uriVariables) throws RestClientException;
参数 | 说明 |
---|---|
url | 调用的url地址 |
method | 枚举值,HTTP方法:GET、POST、PUT、DELETE等 |
requestEntity | 发起请求时携带的对象:请求头header 和/或 请求体body |
responseType | 请求响应对象的类型 |
uriVariables | 就是针对url中的@PathVariable参数,可变长度参数列表 |
- responseType是ParameterizedTypeReference<T>
源码截图:
<T> ResponseEntity<T> exchange(String url
, HttpMethod method
, @Nullable HttpEntity<?> requestEntity
, ParameterizedTypeReference<T> responseType
, Object... uriVariables) throws RestClientException;
与上面重载的唯一区别是responseType类型变成了ParameterizedTypeReference<T>,其它参数说明不变.
设计这个类的目的:是允许传递泛型类型。用法建议是使用匿名类
,像这样:
ParameterizedTypeReference<List<String>> typeRef = new ParameterizedTypeReference<List<String>>() {};
好了,不多做介绍,我们直接上案例:
1. Get请求
这里准备了5个常见场景:
1.1 返回基本类型
1.2 返回自定义对象类型
1.3 返回List<T>类型
1.4 返回Map<K,V>类型
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();
}
- 被调用的代码:
@GetMapping("/name/mock")
public String mockName(@RequestParam("id") Integer id) {
return "天罡" + id;
}
-
验证:
如期望一样,不得不说就应该这么简洁,good~~ 💪💪💪请求url地址
: http://localhost:8080/demo/name?id=123返回
: 天罡123
这里用的重载是:responseType是Class<T>
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;
}
- 发起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();
}
- 被调用的代码:
@GetMapping("/user/mock")
public UserDto mockUser(@RequestParam("id") Integer id) {
return UserDto.builder().id(id)
.name("天罡" + id)
.age(id + 18)
.birthday(new Date()).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")
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();
}
- 被调用的代码:
@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;
}
- 验证:
如期望一样,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")
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();
}
- 被调用的代码:
@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;
}
-
验证:
根据不同类型返回不同字段了, 漂亮~~💪💪💪 仔细看两次请求返回的字段,一次是name1,一次是name2,示例1:请求类型=1
请求url地址1
: http://localhost:8080/demo/user/map?type=1&key=123返回
:{ "id": 1, "`name1`": "hello123" }
示例2:请求类型=2
请求url地址2
: http://localhost:8080/demo/user/map?type=2&key=456返回
:{ "id": 2, "`name2`": "hello456" }
1.5 返回自定义泛型类型
这应该是在项目中用的最多的一种,就是
自定义包装类型
,而不是直接返回单一对象,而是返回统一的对象。
我们在1.2 返回自定义对象类型模拟过根据用户id获取用户信息的场景,但未处理非法请求、异常等情况
,所以接下来我们自定一个通用的自定义Code的泛型返回结果,对1.2做一下增强。
- 场景模拟:
根据用户id获取用户信息,根据不同情况返回不同编码
我们新建一个Result<T>类:
@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;
}
}
- 发起exchange的代码:
依然通过ParameterizedTypeReference指定返回的Result<T>
// 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();
}
- 被调用的代码:
@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:请求返回400
请求url地址1
: http://localhost:8080/demo/user/result?id=0返回
:{ "success": false, "code": "400", "message": "id不合法!", "data": null }
示例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" } }
示例3:请求返回500
请求url地址3
: http://localhost:8080/demo/user/result?id=2返回
:{ "success": false, "code": "500", "message": "操作失败,访问量太大了!", "data": null }
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();
}
- 被调用的代码:
@PostMapping("/user/body/mock")
public UserDto mockPostUser(@RequestBody UserDto userParam) {
return userParam;
}
-
验证:
如期望一样,good~~ 💪💪💪请求url地址
: http://localhost:8080/demo/user/body?id=1返回
:{ "id": 1, "name": "body1", "age": 19, "birthday": "2022-11-06 21:20:41" }
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();
}
- 被调用的代码:
@PostMapping("/user/result/body/mock")
public Result<UserDto> mockPostUserResult(@RequestBody UserDto userParam) {
return Result.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. 异常情况处理
上面例子均未做异常处理,在这项目中使用难免不够健壮,所以我们通常会处理两种异常情况:
- 本身抛出的 throws RestClientException
- 返回的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;
}
- 泛型类型:
只需要将普通类型的入参Class<T>改成 ParameterizedTypeReference<T>
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;
}
如果觉得这样重复代码太多了,那就在内部实现一个方法,对外开放两个方法即可,内部实现的方法类似这样,具体不做赘述!
private <T> T exchangeInternal(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
, Class<T> responseType1, ParameterizedTypeReference<T> responseType2)
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;
}
}
相关博文
在实际开发中,如果想自定义RestTemplate序列化,或者对于结果有相关处理,我们也可以取出来结果,然后再自己做序列化或验证,可以参考这位大佬的优质文章:RestTemplate使用实战-exchange方法讲解
对于更多原理性分析,可以参考这位大佬的优质文章:RestTemplate总结
最后
除了Get和Post,我们常用的还有Put和Delete,由于Delete可以参考Get的用法,Put可以参考Post的用法,所以就不做赘述,如果您觉得还有哪些场景是没有覆盖到的,欢迎留言或私信~~
注:如果本篇博客有任何错误和建议,欢迎评论指正!
如果感觉不错,请收藏。
关注我 天罡gg 分享更多干货: https://blog.csdn.net/scm_2008
大家的「关注 + 点赞 + 收藏」就是我创作的最大动力!谢谢大家的支持,我们下文见!