Spring之RestTemplate使用小结

作为一个Java后端,需要通过HTTP请求其他的网络资源可以说是一个比较常见的case了;一般怎么做呢?

可能大部分的小伙伴直接捞起Apache的HttpClient开始做,或者用其他的一些知名的开源库如OkHttp, 当然原生的HttpURLConnection也是没问题的

本篇博文则主要关注点放在Sprig的生态下,利用RestTemplate来发起Http请求的使用姿势

I. RestTempalate 基本使用

0. 目标

在介绍如何使用RestTemplate之前,我们先抛出一些小目标,至少需要知道通过RestTemplate可以做些什么,以及我们要用它来干些什么

简单的给出了一下常见的问题如下

  • 普通的Get请求获取返回数据,怎么玩?
  • post提交表达的请求,如何处理
  • post请求中RequestBody的请求方式与普通的请求方式区别
  • https/http两种访问如何分别处理
  • 如何在请求中带上指定的Header
  • 有跨域的问题么?如果有怎么解决
  • 有登录验证的请求,该怎么办,怎样携带身份信息
  • 上传文件可以支持么
  • 对于需要代理才能访问的http资源,加代理的姿势是怎样的

上面的问题比较多,目测不是一篇博文可以弄完的,因此对这个拆解一下,本篇主要关注在RestTemplate的简单Get/Post请求的使用方式上

1. 基本接口

捞出源码,看一下其给出的一些常用接口,基本上可以分为下面几种

  1. // get 请求
  2. public <T> T getForObject();
  3. public <T> ResponseEntity<T> getForEntity();
  4. // head 请求
  5. public HttpHeaders headForHeaders();
  6. // post 请求
  7. public URI postForLocation();
  8. public <T> T postForObject();
  9. public <T> ResponseEntity<T> postForEntity();
  10. // put 请求
  11. public void put();
  12. // pathch
  13. public <T> T patchForObject
  14. // delete
  15. public void delete()
  16. // options
  17. public Set<HttpMethod> optionsForAllow
  18. // exchange
  19. public <T> ResponseEntity<T> exchange()

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

2. Get请求

从上面的接口命名上,可以看出可以使用的有两种方式 getForObjectgetForEntity,那么这两种有什么区别?

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

a. 创建Get接口

为了验证RestTemplate的使用姿势,当然得先提供一个后端的REST服务,这了直接用了我个人的一个古诗词的后端接口,来作为简单的Get测试使用

请求连接: https://story.hhui.top/detail?id=666106231640

返回结果:

  1. {
  2. "status": {
  3. "code": 200,
  4. "msg": "SUCCESS"
  5. },
  6. "result": {
  7. "id": 666106231640,
  8. "title": "西塞山二首(今谓之道士矶,即兴国军大冶县",
  9. "author": "王周",
  10. "content": "西塞名山立翠屏,浓岚横入半江青。\n千寻铁锁无由问,石壁空存道者形。\n匹妇顽然莫问因,匹夫何去望千春。\n翻思岵屺传诗什,举世曾无化石人。",
  11. "explain": "",
  12. "theme": "无",
  13. "dynasty": "唐诗"
  14. }
  15. }

b. getForObject方式

首先看下完整的接口签名

  1. @Nullable
  2. public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException ;
  3. @Nullable
  4. public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException ;
  5. @Nullable
  6. public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException;

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

  1. public class RestTestmplateTest {
  2. private RestTemplate restTemplate;
  3. @Before
  4. public void init() {
  5. restTemplate = new RestTemplate();
  6. }
  7. @lombok.Data
  8. static class InnerRes {
  9. private Status status;
  10. private Data result;
  11. }
  12. @lombok.Data
  13. static class Status {
  14. int code;
  15. String msg;
  16. }
  17. @lombok.Data
  18. static class Data {
  19. long id;
  20. String theme;
  21. String title;
  22. String dynasty;
  23. String explain;
  24. String content;
  25. String author;
  26. }
  27. @Test
  28. public void testGet() {
  29. // 使用方法一,不带参数
  30. String url = "https://story.hhui.top/detail?id=666106231640";
  31. InnerRes res = restTemplate.getForObject(url, InnerRes.class);
  32. System.out.println(res);
  33. // 使用方法一,传参替换
  34. url = "https://story.hhui.top/detail?id={?}";
  35. res = restTemplate.getForObject(url, InnerRes.class, "666106231640");
  36. System.out.println(res);
  37. // 使用方法二,map传参
  38. url = "https://story.hhui.top/detail?id={id}";
  39. Map<String, Object> params = new HashMap<>();
  40. params.put("id", 666106231640L);
  41. res = restTemplate.getForObject(url, InnerRes.class, params);
  42. System.out.println(res);
  43. // 使用方法三,URI访问
  44. URI uri = URI.create("https://story.hhui.top/detail?id=666106231640");
  45. res = restTemplate.getForObject(uri, InnerRes.class);
  46. System.out.println(res);
  47. }
  48. }

看上面的testcase,后面两个方法的使用没什么好说的,主要看一下org.springframework.web.client.RestTemplate#getForObject(java.lang.String, java.lang.Class<T>, java.lang.Object...) 的使用姿势

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

上面执行后的截图如下

c. getForEntity方式

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

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

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

  1. @Test
  2. public void testGetForEntity() {
  3. String url = "https://story.hhui.top/detail?id=666106231640";
  4. ResponseEntity<InnerRes> res = restTemplate.getForEntity(url, InnerRes.class);
  5. System.out.println(res);
  6. }

对这个,我们主要关注的就是ResponseEntity封装中,多了哪些东西,截图如下

从上面可以看出,多了两个东西

  • 一个返回的http状态码,如200表示请求成功,500服务器错误,404not found等
  • 一个 ResponseHeader

3. Post请求

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

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

a. post接口mock

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

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

b. postForObject方法

首先看一下接口签名

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

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

下面分别给出使用用例

  1. @Test
  2. public void testPost() {
  3. String url = "http://localhost:8080/post";
  4. String email = "test@hhui.top";
  5. String nick = "一灰灰Blog";
  6. MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
  7. request.add("email", email);
  8. request.add("nick", nick);
  9. // 使用方法三
  10. URI uri = URI.create(url);
  11. String ans = restTemplate.postForObject(uri, request, String.class);
  12. System.out.println(ans);
  13. // 使用方法一
  14. ans = restTemplate.postForObject(url, request, String.class);
  15. System.out.println(ans);
  16. // 使用方法一,但是结合表单参数和uri参数的方式,其中uri参数的填充和get请求一致
  17. request.clear();
  18. request.add("email", email);
  19. ans = restTemplate.postForObject(url + "?nick={?}", request, String.class, nick);
  20. System.out.println(ans);
  21. // 使用方法二
  22. Map<String, String> params = new HashMap<>();
  23. params.put("nick", nick);
  24. ans = restTemplate.postForObject(url + "?nick={nick}", request, String.class, params);
  25. System.out.println(ans);
  26. }

上面分别给出了三种方法的调用方式,其中post传参区分为两种,一个是uri参数即拼接在url中的,还有一个就是表单参数

  • uri参数,使用姿势和get请求中一样,填充uri中模板坑位
  • 表单参数,由MultiValueMap封装,同样是kv结构

c. postForEntity

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

d. postForLocation

这个与前面有点区别,从接口定义上来说,主要是

POST 数据到一个URL,返回新创建资源的URL

同样提供了三个接口,分别如下,需要注意的是返回结果,为URI对象,即网络资源

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

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

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

首先mock一个后端接口

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

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

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

执行结果如下

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

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

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

II. 小结

上面目前只给出了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即可
  1. 形如 http://story.hhui.top?id={id} 的 url
  • 调用 getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
  • map参数中的key,就是url参数中 {} 中的内容

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

返回结果

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

2. Post请求

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

3. 其他

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

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

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

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

小小的一个工具类,其实东西还挺多的,接下来的小目标,就是针对上面提出的点,逐一进行研究

III. 其他

1. 一灰灰Bloghttps://liuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

小灰灰Blog&公众号

知识星球

180813-Spring之RestTemplate使用小结一的更多相关文章

  1. 对Spring 的RestTemplate进行包装

    Spring的RestTemplate及大地简化了REST Client的开发,但每次还要编写大量的模板代码,代码不够简洁.我对他进行了一次包装,采用接口来声明REST接口,使用Annotation对 ...

  2. Spring’s RestTemplate

    Spring’s RestTemplate /** * After the word document is generated in memory we can upload it to the s ...

  3. 还不知道spring的RestTemplate的妙用吗

    为什么要使用RestTemplate? 随着微服务的广泛使用,在实际的开发中,客户端代码中调用RESTful接口也越来越常见.在系统的遗留代码中,你可能会看见有一些代码是使用HttpURLConnec ...

  4. Spring的RestTemplate

    Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便.RestTemplate并没有限定Http的客户端类型 ...

  5. Spring中RestTemplate进行Http调用

    Spring中的RestTemplate类源自spring-web,http调用中设置超时时间.设置连接池管理等非常重要,保证了系统的可用性,避免了长时间连接不上或者等待数据返回,拖垮系统. 现贴出工 ...

  6. spring的RestTemplate使用指南

    前言:现在restful接口越来越广泛,而如今很多接口摒弃了传统的配置复杂的webService开发模式,在java领域只需要很简单的springMvc就可以声明为一个控制器,再加上service层, ...

  7. Spring中RestTemplate的使用方法

    一.REST 在互联网中,我们会通过请求url来对网络上的资源做增删改查等动作,这里的请求包含两部分:动词,主要包括增.删.改.查:名词,就是网络中的各种资源.传统的非REST风格的请求方式是把动词和 ...

  8. Spring boot ----RestTemplate学习笔记

    ****spring boot-----restTemplate 封装了HttpURLConnection,HttpClient,Netty等接口访问实现库 restTemplet包含以下部分 Htt ...

  9. 使用Spring的RestTemplate进行接口调用

    引自:http://www.zimug.com/ 1.常见的http服务的通信方式 经常使用的方式有HttpClient.OkHttp.RestTemplate.其中RestTemplate是一种更优 ...

随机推荐

  1. RTCM32转码至RTCM23,再次测试,一些收获

    RTCM32是2013年发布的,RTCM23是2001年发布,两者相隔十多年,某些软件已经不支持RTCM32的解码.故在此对RTCM32的编码进行转换,使用2018年4月9日天宝接收机数据.编码格式为 ...

  2. NTRIP协议学习(一)

    这篇博客讲得很清晰.  https://blog.csdn.net/sinat_19447667/article/details/67637167 可以参考的文献包括:<多系统GNSS实时数据质 ...

  3. 手写阻塞队列(Condition实现)

    自己实现阻塞队列的话可以采用Object下的wait和notify方法,也可以使用Lock锁提供的Condition来实现,本文就是自己手撸的一个简单的阻塞队列,部分借鉴了JDK的源码.Ps:最近看面 ...

  4. 【转载】Java 集合框架

    http://wangkuiwu.github.io/2012/02/03/collection-03-arraylist/ 网上比较全的Java集合框架教程. 注:transient是Java语言的 ...

  5. 用python解析word文件(一):paragraph

    太长了,我决定还是拆开三篇写.   (一)段落篇(paragraph)(本篇) (二)表格篇(table) (三)样式篇(style) 选你所需即可.下面开始正文. 最近公司的项目,需要在页面上显示w ...

  6. [luogu2312] 解方程

    题面 ​ 秦九韶公式 ​ 看了上面这个之后大家应该都会了, 就是读入的时候边读入边取模, 从\(1\)到\(m\)间将每一个数带进去试一下就可以了, 复杂度是\(O(nm)\)的. ​ 古人的智慧是无 ...

  7. 《信息安全技术》实验一 PGP的原理与使用

    <信息安全技术>实验一 PGP的原理与使用(macOS High Sierra下实现) 实验目的 理解传统加密.公钥加密.混合加密.数字签名等概念 理解公钥.私钥.会话密钥.对称密钥等概念 ...

  8. 《Java程序设计》第15周课堂实践总结

    <Java程序设计>第15周课堂实践总结 实践一 教材代码检查-p242 要求 在IDEA中或命令行中运行P242 StackTraceDemo2.java 代码运行结果和教材一致吗?为什 ...

  9. 【图像处理】Schmid滤波器

    Schmid也是一种类Gabor图像滤波器,在这篇文章[1]中有详细推导和介绍. 一种更简洁的表达公式是: 当中,r为核半径,Z为归一化參数,τ和σ是比較重要的參数,在ReID提取TextFeatur ...

  10. oracle快速学习