Java 编程中接口调用RestTempalte介绍

2019-10-14 0 By admin

Java 后端开发过程中,遇到需要使用REST 调用对方接口时,可以使用HttpClient、开源库OkHttp或者原生的HttpURLConnection。
这里介绍一下Spring 框架下的 RestTemplate 用来处理Http请求调用。

一、基本接口列表

// get 请求
public <T> T getForObject();
public <T> ResponseEntity<T> getForEntity();
 
// head 请求
public HttpHeaders headForHeaders();
 
// post 请求
public URI postForLocation();
public <T> T postForObject();
public <T> ResponseEntity<T> postForEntity();
 
// put 请求
public void put();
 
// pathch 
public <T> T patchForObject
 
// delete
public void delete()
 
// options
public Set<HttpMethod> optionsForAllow
 
// exchange
public <T> ResponseEntity<T> exchange()

上面提供的几个接口,基本上就是Http提供的几种访问方式的对应,其中exchange却又不一样。

二、Get请求

从上面的接口命名上,可以看出可以使用的有两种方式 getForObject 和 getForEntity。

从接口的签名上,可以看出一个是直接返回预期的对象,一个则是将对象包装到 ResponseEntity 封装类中。
如果只关心返回结果,那么直接用 GetForObject 即可。
如果除了返回的实体内容之外,还需要获取返回的header等信息,则可以使用 getForEntit。

2.1、getForObject方式

首先看下完整的接口签名
有三个重载的方法

@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException ;
 
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException ;
 
@Nullable
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException;

从接口上也比较容易看出如何使用,其中有点疑惑的则是第一钟,参数应该怎么传了,下面给出上面几种的使用姿势

public class RestTestmplateTest {
 private RestTemplate restTemplate;
 
 @Before
 public void init() {
 restTemplate = new RestTemplate();
 }
 
 @lombok.Data
 static class InnerRes {
 private Status status;
 private Data result;
 }
 
 @lombok.Data
 static class Status {
 int code;
 String msg;
 }
 
 @lombok.Data
 static class Data {
 long id;
 String theme;
 String title;
 String dynasty;
 String explain;
 String content;
 String author;
 }
 
 @Test
 public void testGet() {
 // 使用方法一,不带参数
 String url = "https://www.baidu.com/baidu?tn=monline_3_dg&ie=utf-8&wd=keyword";
 InnerRes res = restTemplate.getForObject(url, InnerRes.class);
 System.out.println(res);
 
 
 // 使用方法一,传参替换
 url = "https://www.baidu.com/baidu?tn=monline_3_dg&ie=utf-8&wd={?}";
 res = restTemplate.getForObject(url, InnerRes.class, "keyword");
 System.out.println(res);
 
 // 使用方法二,map传参
 url = "https://www.baidu.com/baidu?tn=monline_3_dg&ie=utf-8&wd={?}";
 Map<String, Object> params = new HashMap<>();
 params.put("id", keyword);
 res = restTemplate.getForObject(url, InnerRes.class, params);
 System.out.println(res);
 
 // 使用方法三,URI访问
 URI uri = URI.create("https://www.baidu.com/baidu?tn=monline_3_dg&ie=utf-8&wd=keyword");
 res = restTemplate.getForObject(uri, InnerRes.class);
 System.out.println(res);
 }
}

看上面的testcase,后面两个方法的使用没什么好说的,

主要看一下org.springframework.web.client.RestTemplate#getForObject(java.lang.String, java.lang.Class<T>, java.lang.Object…) 的使用姿势

根据实际传参替换url模板中的内容
使用方法一时,模板中使用 {?} 来代表坑位,根据实际的传参顺序来填充
使用方法二时,模板中使用 {xx}, 而这个xx,对应的就是map中的key

2.2、getForEntity方式

既然getForObject有三种使用方法,那么getForEntity理论上也应该有对应的三种方式

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException ;
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;

因为使用姿势和上面一致,因此只拿一个进行测试。

@Test
public void testGetForEntity() {
 String url = "https://www.baidu.com/baidu?tn=monline_3_dg&ie=utf-8&wd=keyword;
 ResponseEntity<InnerRes> res = restTemplate.getForEntity(url, InnerRes.class);
 System.out.println(res);
}

可以看出,多了两个东西
一个返回的http状态码,如200表示请求成功,500服务器错误,404not found等
一个 ResponseHeader

三、Post请求

从上面的接口说明上看,post请求除了有forObject 和 forEntity之外,还多了个forLocation;
其次post与get一个明显的区别就是传参的姿势问题,get的参数一般会待在url上;post的则更常见的是通过表单的方式提交

因此接下来关注的重点在于forLocation是什么,以及如何传参

3.1、post接口mock

首先创建一个简单的提供POST请求的REST服务,基于Spring-boot简单搭建一个,如下

@ResponseBody
@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST})
public String post(HttpServletRequest request,
 @RequestParam(value = "email", required = false) String email,
 @RequestParam(value = "nick", required = false) String nick) {
 Map<String, Object> map = new HashMap<>();
 map.put("code", "200");
 map.put("result", "add " + email + " # " + nick + " success!");
 return JSON.toJSONString(map);
}

3.2、postForObject方法

首先看一下接口签名

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException ;
 
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
 
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType) throws RestClientException ;

上面的三个方法,看起来和前面并没有太大的区别,只是多了一个request参数,那么具体的使用如何呢?

下面分别给出使用用例

@Test
public void testPost() {
 String url = "http://localhost:8080/post";
 String email = "admin@admin.com";
 String nick = "superadmin";
 
 MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
 request.add("email", email);
 request.add("nick", nick);
 
 // 使用方法三
 URI uri = URI.create(url);
 String ans = restTemplate.postForObject(uri, request, String.class);
 System.out.println(ans);
 
 // 使用方法一
 ans = restTemplate.postForObject(url, request, String.class);
 System.out.println(ans);
 
 // 使用方法一,但是结合表单参数和uri参数的方式,其中uri参数的填充和get请求一致
 request.clear();
 request.add("email", email);
 ans = restTemplate.postForObject(url + "?nick={?}", request, String.class, nick);
 System.out.println(ans);
 
 
 // 使用方法二
 Map<String, String> params = new HashMap<>();
 params.put("nick", nick);
 ans = restTemplate.postForObject(url + "?nick={nick}", request, String.class, params);
 System.out.println(ans);
}

复制代码上面分别给出了三种方法的调用方式,其中post传参区分为两种,一个是uri参数即拼接在url中的,还有一个就是表单参数
uri参数,使用姿势和get请求中一样,填充uri中模板坑位
表单参数,由MultiValueMap封装,同样是kv结构

3.3、postForEntity

和前面的使用姿势一样,无非是多了一层包装而已,略过不讲

3.4 postForLocation

这个与前面有点区别,从接口定义上来说,主要是
POST 数据到一个URL,返回新创建资源的URL
同样提供了三个接口,分别如下,需要注意的是返回结果,为URI对象,即网络资源

public URI postForLocation(String url, @Nullable Object request, Object... uriVariables)
 throws RestClientException ;
 
public URI postForLocation(String url, @Nullable Object request, Map<String, ?> uriVariables)
 throws RestClientException ;
 
public URI postForLocation(URI url, @Nullable Object request) throws RestClientException ;

那么什么样的接口适合用这种访问姿势呢?

想一下我们一般登录or注册都是post请求,而这些操作完成之后呢?大部分都是跳转到别的页面去了,这种场景下,就可以使用 postForLocation 了,提交数据,并获取返回的URI,一个测试如下

首先mock一个后端接口

@ResponseBody
@RequestMapping(path = "success")
public String loginSuccess(String email, String nick) {
 return "welcome " + nick;
}
 
@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST})
public String post(HttpServletRequest request, @RequestParam(value = "email", required = false) String email,
  @RequestParam(value = "nick", required = false) String nick) {
 return "redirect:/success?email=" + email + "&nick=" + nick + "&status=success";
}

访问的测试用例,基本上和前面的一样,没有什么特别值得一说的

@Test
public void testPostLocation() {
 String url = "http://localhost:8080/post";
 String email = "test@hhui.top";
 String nick = "一灰灰Blog";
 
 MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
 request.add("email", email);
 request.add("nick", nick);
 
 // 使用方法三
 URI uri = restTemplate.postForLocation(url, request);
 System.out.println(uri);
}

执行结果如下

获取到的就是302跳转后端url,细心的朋友可能看到上面中文乱码的问题,如何解决呢?

一个简单的解决方案就是url编码一下

@RequestMapping(path = "post", method = {RequestMethod.GET, RequestMethod.OPTIONS, RequestMethod.POST},
   produces = "charset/utf8")
public String post(HttpServletRequest request, @RequestParam(value = "email", required = false) String email,
  @RequestParam(value = "nick", required = false) String nick) throws UnsupportedEncodingException {
 return "redirect:/success?email=" + email + "&nick=" + URLEncoder.encode(nick, "UTF-8") + "&status=success";
}

四、总结

上面目前只给出了Get/Post两种请求方式的基本使用方式,并没有涉及到更高级的如添加请求头,添加证书,设置代理等,高级的使用篇等待下一篇出炉,下面小结一下上面的使用姿势

1. Get请求

get请求中,参数一般都是带在url上,对于参数的填充,有两种方式,思路一致都是根据实际的参数来填充url中的占位符的内容;根据返回结果,也有两种方式,一个是只关心返回对象,另一个则包含了返回headers信心
参数填充

1、形如 http://story.hhui.top?id={0} 的 url

调用 getForObject(String url, Class<T> responseType, Object… uriVariables)
模板中的0,表示 uriVariables 数组中的第0个, i,则表示第i个
如果没有url参数,也推荐用这个方法,不传uriVariables即可

2、形如 http://story.hhui.top?id={id} 的 url

调用 getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
map参数中的key,就是url参数中 {} 中的内容

其实还有一种传参方式,就是path参数,填充方式和上面一样,并没有什么特殊的玩法,上面没有特别列出

返回结果

直接获取返回的数据 getForObject
获取待responseHeader的数据 getForEntity

2. Post请求

post请求的返回也有两种,和上面一样
post请求,参数可以区分为表单提交和url参数,其中url参数和前面的逻辑一致
post表单参数,请包装在 MultiValueMap 中,作为第二个参数 Request 来提交
post的方法,还有一个 postForLocation,返回的是一个URI对象,即适用于返回网络资源的请求方式

3. 其他

最前面提了多点关于网络请求的常见case,但是上面的介绍,明显只处于基础篇,我们还需要关注的有

如何设置请求头?
有身份验证的请求,如何携带身份信息?
代理的设置
文件上传可以怎么做?
post提交json串(即RequestBody) 又可以怎么处理

上面可能还停留在应用篇,对于源码和实现有兴趣的话,问题也就来了

RestTemplaet的实现原理是怎样的
前面url参数的填充逻辑实现是否优雅
返回的对象如何解析