1.1 简述
最近项目中需要使用http
的形式进行调用第三方的服务,我们项目中使用的是RestTemplate
进行交互调用。不像我们以前写的HttpClient
,需要写很多的工具类,RestTemplate
基本上是开箱即用,本文主要是介绍其在spring boot项目中的基本使用,对于日常的开发等,基本上是没有问题。
1.1.1 Rest
当谈论REST
时,有一种常见的错误就是将其视为“基于URL
的Web
服务”——将REST
作为另一种类型的远程过程调用(remote procedure call
,RPC
)机制,就像SOAP
一样,只不过是通过简单的HTTP URL
来触发,而不是使用SOAP
大量的XML
命名空间
恰好相反,REST
与RPC
几乎没有任何关系。RPC
是面向服务的,并关注于行为和动作;而REST
是面向资源的,强调描述应用程序的事物和名词。
更简洁地讲,REST
就是将资源的状态以最适合客户端或服务端的形式从服务器端转移到客户端(或者反过来)。
在REST
中,资源通过URL
进行识别和定位。至于RESTful URL
的结构并没有严格的规则,但是URL
应该能够识别资源,而不是简单的发一条命令到服务器上。再次强调,关注的核心是事物,而不是行为。
1.1.2 Spring
中如何使用Rest
资源
借助 RestTemplate
,Spring
应用能够方便地使用REST
资源
Spring
的 RestTemplate
访问使用了模版方法的设计模式
模版方法将过程中与特定实现相关的部分委托给接口,而这个接口的不同实现定义了接口的不同行为
RestTemplate
定义了36
个与REST
资源交互的方法,其中的大多数都对应于HTTP
的方法。
其实,这里面只有11
个独立的方法,其中有十个有三种重载形式,而第十一个则重载了六次,这样一共形成了36
个方法。
-
delete()
在特定的URL
上对资源执行HTTP DELETE
操作 -
exchange()
在URL
上执行特定的HTTP
方法,返回包含对象的ResponseEntity
,这个对象是从响应体中映射得到的 -
execute()
在URL
上执行特定的HTTP
方法,返回一个从响应体映射得到的对象 -
getForEntity()
发送一个HTTP GET
请求,返回的ResponseEntity
包含了响应体所映射成的对象 -
getForObject()
发送一个HTTP GET
请求,返回的请求体将映射为一个对象 -
postForEntity()
POST
数据到一个URL
,返回包含一个对象的ResponseEntity
,这个对象是从响应体中映射得到的 -
postForObject()
POST
数据到一个URL
,返回根据响应体匹配形成的对象 -
headForHeaders()
发送HTTP HEAD
请求,返回包含特定资源URL
的HTTP
头 -
optionsForAllow()
发送HTTP OPTIONS
请求,返回对特定URL
的Allow
头信息 -
postForLocation()
POST
数据到一个URL
,返回新创建资源的URL
-
put()
PUT
资源到特定的URL
实际上,由于Post
操作的非幂等性,它几乎可以代替其他的CRUD
操作
1.2 Get
请求
RestTemplate
的get
方法有以上几个,可以分为两类: getForEntity()
和 getForObject()
首先看 getForEntity()
的返回值类型 ResponseEntity
<T> ResponseEntity<T> getForEntity()
看一下 ResponseEntity
的文档描述:
可以看到 它继承了HttpEntity
,封装了返回的响应信息,包括 响应状态,响应头 和 响应体
在测试之前我们首先 创建一个Rest
服务,模拟提供Rest
数据,这里给出Controller
层代码:
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "getAll")
public List<UserEntity> getUser() {
List<UserEntity> list = userService.getAll();
return list;
}
@RequestMapping("get/{id}")
public UserEntity getById(@PathVariable(name = "id") String id) {
return userService.getById(id);
}
@RequestMapping(value = "save")
public String save(UserEntity userEntity) {
return "保存成功";
}
@RequestMapping(value = "saveByType/{type}")
public String saveByType(UserEntity userEntity,@PathVariable("type")String type) {
return "保存成功,type="+type;
}
}
1.2.1 测试: getForEntity
无参数的 getForEntity
方法
@RequestMapping("getForEntity")
public List<UserEntity> getAll2() {
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://localhost/getAll", List.class);
HttpHeaders headers = responseEntity.getHeaders();
HttpStatus statusCode = responseEntity.getStatusCode();
int code = statusCode.value();
List<UserEntity> list = responseEntity.getBody();
System.out.println(list.toString());
return list;
}
有参数的 getForEntity
请求,参数列表,可以使用{}
进行url
路径占位符
//有参数的 getForEntity 请求,参数列表
@RequestMapping("getForEntity/{id}")
public UserEntity getById2(@PathVariable(name = "id") String id) {
ResponseEntity<UserEntity> responseEntity = restTemplate.getForEntity("http://localhost/get/{id}", UserEntity.class, id);
UserEntity userEntity = responseEntity.getBody();
return userEntity;
}
有参数的 get
请求,使用map
封装参数
//有参数的 get 请求,使用map封装参数
@RequestMapping("getForEntity/{id}")
public UserEntity getById4(@PathVariable(name = "id") String id) {
HashMap<String, String> map = new HashMap<>();
map.put("id",id);
ResponseEntity<UserEntity> responseEntity = restTemplate.getForEntity("http://localhost/get/{id}", UserEntity.class, map);
UserEntity userEntity = responseEntity.getBody();
return userEntity;
}
因此我们可以获取Http
请求的全部信息.
但是,通常情况下我们并不想要Http
请求的全部信息,只需要相应体即可,对于这种情况,RestTemplate
提供了 getForObject()
方法用来只获取 响应体信息
getForObject
和 getForEntity
用法几乎相同,指示返回值返回的是 响应体,省去了我们 再去 getBody()
1.2.2 测试: getForObject
无参数的 getForObject
请求
//无参数的 getForObject 请求
@RequestMapping("getAll2")
public List<UserEntity> getAll() {
List<UserEntity> list = restTemplate.getForObject("http://localhost/getAll", List.class);
System.out.println(list.toString());
return list;
}
有参数的 getForObject
请求,使用参数列表
//有参数的 getForObject 请求
@RequestMapping("get2/{id}")
public UserEntity getById(@PathVariable(name = "id") String id) {
UserEntity userEntity = restTemplate.getForObject("http://localhost/get/{id}", UserEntity.class, id);
return userEntity;
}
有参数的 get
请求,使用map
封装请求参数
//有参数的 get 请求,使用map封装请求参数
@RequestMapping("get3/{id}")
public UserEntity getById3(@PathVariable(name = "id") String id) {
HashMap<String, String> map = new HashMap<>();
map.put("id",id);
UserEntity userEntity = restTemplate.getForObject("http://localhost/get/{id}", UserEntity.class, map);
return userEntity;
}
1.3 Post
请求
了解了get
请求后,Post
请求就变得很简单了,我们可以看到post
有如下方法:
1.3.1 测试:postForEntity
post
请求,保存UserEntity
对像
//post 请求,提交 UserEntity 对像
@RequestMapping("saveUser")
public String save(UserEntity userEntity) {
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost/save", userEntity, String.class);
String body = responseEntity.getBody();
return body;
}
浏览器访问: http://localhost/saveUser?username=itguang&password=123456&age=20&email=123@123.com
我们再次断点调试,查看 responseEntity
中的信息
有参数的 postForEntity
请求
// 有参数的 postForEntity 请求
@RequestMapping("saveUserByType/{type}")
public String save2(UserEntity userEntity,@PathVariable("type")String type) {
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost/saveByType/{type}", userEntity, String.class, type);
String body = responseEntity.getBody();
return body;
}
// 有参数的 postForEntity 请求,使用map封装
@RequestMapping("saveUserByType2/{type}")
public String save3(UserEntity userEntity,@PathVariable("type")String type) {
HashMap<String, String> map = new HashMap<>();
map.put("type", type);
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost/saveByType/{type}", userEntity, String.class,map);
String body = responseEntity.getBody();
return body;
}
我们浏览器访问: localhost/saveUserByType/120?username=itguang&password=123456&age=20&email=123@123.com
就会返回:保存成功,type=120
对与其它请求方式,由于不常使用,所以这里就不再讲述
1.4 踩坑记录:restTemplate
的exchange
方法get
请求报400 Bad request【restTemplate Bug】
的解决办法
1.4.1 填坑记录
如下代码,url=http://www.baidu.com
时请求报400
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Authorization", authorization);
requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
MultiValueMap<String, Object> requestBody = new LinkedMultiValueMap<>();
if (map != null) {
map.forEach((k, v) -> {
if (StringUtils.isNotBlank(v)) {
requestBody.add(k,v);
}
});
}
HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(requestBody, requestHeaders);
try {
ResponseEntity<String> res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
return new HttpResDTO(res.getStatusCodeValue(), res.getBody());
} catch (HttpClientErrorException ex) {
log.error("rest template for face exception, error code: {}, error message: {}", ex.getRawStatusCode(), ex.getMessage());
return new HttpResDTO(ex.getRawStatusCode(), ex.getResponseBodyAsString());
} catch (UnknownHttpStatusCodeException e) {
String responseBodyAsString = e.getResponseBodyAsString();
log.warn("UnknownHttpStatusCodeException:{}",responseBodyAsString);
JSONObject parse = JSONObject.parseObject(responseBodyAsString);
throw new RuntimeException(parse.get("message").toString());
}
1.4.2 解决方案
get
请求直接在url
上拼参数,如?type=1&name=xx
String uri =tempUrl + “?keys={keys}”
;通过map
传递keys
Map<String, String> map = new HashMap<>(1);
map.put(“keys”, “xdfdf”);
ResponseEntity res = restTemplate.getForEntity(url, String.class, map);