背景

业务方经过某些业务处理之后,带过来了一系列参数。其中,有个url参数供回调使用,但这个url经业务方处理后很特殊,带过来回调时频频出错,无法正常回调给业务方。于是,通过分析部分源码,剖析一下回调的流程。

编码 解码

说到url的回调,很容易联想到decode、encode,即编码解码操作。

通常来说,需要编码操作即意味着不适合直接传输,某些字符可能有歧义,如中文、特殊字符等等。
而通过编码之后(如 utf-8),传输给服务端,服务端自行解码,便不会出现一系列乱码问题。
复制代码

说明

按照如上所说,业务方带过来了处理后的url,另附上说明,url无需解码,直接回调即可,但事情往往没那么顺利。不管是使用spring5 webclient,还是restTemplate以get请求回调业务方,均会出错。由于最终使用了restTemplate解决了问题,对此本次剖析一下restTemplate。

注:WebClient同样可以解决问题,剖析restTemplate的因由在于综合分析了项目的引用和耦合度,不适合改动webclient模块,故而拓展了restTemplate。

剖析

  1. 先写个极其简单的单测入口,进行RestTemplate的源码分析。

  2. 紧接着集中分析RestTemplate类

  • 请求头、请求体是由RequestCallback来处理的,请求结果是由ResponseExtractor来处理。

  • RestTemplate默认已经初始化添加了许多对象读写转换器。

  1. 进入execute这个重要的方法继续分析。

  2. 可以看出,先用UriTemplateHandler构建URI,再走关键逻辑。

  • UriTemplateHandler build URI

  • 通过构造函数的初始化,可以看到使用的是默认builder工厂

  • 从类继承图可以看出DefaultUriBuilderFactory实现了UriBuilderFactory,继承了UriTemplateHandler

  • expand(String var1, Object... var2)是UriTemplateHandler接口的方法

  1. 继续分析UriTemplateHandler接口expand方法源码
  • 到这里我们可以发现DefaultUriBuilderFactory默认builder工厂存在一个内部类DefaultUriBuilder

  • 接着进入分析一下UriComponentsBuilder类的源码,从类名可以猜想到这个UriComponentsBuilder构建器可能用于创建UriComponents实例

  • 顾名思义,UriComponents包含了构成URI的组件,以及一系列的getter and setter方法

  • 继续着刚才初始化UriComponentsBuilder的方法

  • 符合了内置的url pattern之后继续构建UriComponentsBuilder,通过构造器可以看出默认字符集是utf-8

  • 可以清晰看出初始化之后的构建器

  1. 接下来出来外层的包裹,分析build方法
  • 分析DefaultUriBuilderFactory build的过程 划重点!!! 因为uriVariableValues为空,不关心这个expand方法,此时注意build方法。

  • 可以看到,build UriComponents,有个encoded参数,默认为false 先不透露具体细节,只需先记住构建这次请求的UriComponents,encoded参数为false!!!

  • 接着看encoded参数为false,会影响到哪些参数值 由encodedfalse可得到UriComponentsBuilder.EncodingHint为NONE

public UriComponents build(boolean encoded) {return this.buildInternal(encoded ? UriComponentsBuilder.EncodingHint.FULLY_ENCODED : (this.encodeTemplate ? UriComponentsBuilder.EncodingHint.ENCODE_TEMPLATE : UriComponentsBuilder.EncodingHint.NONE));
}
复制代码
  • 再接着就是构造HierarchicalUriComponents
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment, this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, hint == UriComponentsBuilder.EncodingHint.FULLY_ENCODED);
复制代码
  • 最后可以看到HierarchicalUriComponents.EncodeState为RAW 这里划重点!!!
  1. 继续看外层的包裹,构建完之后的UriComponents
  • 继续分析createUri,由于匹配到是默认的URI_COMPONENT,则走encode方法

  • 本次剖析很重要的一个方法,终于要接近问题的真相了!!!

  • 从上个提示重要的剖析点得知,构建UriComponents,encoded参数默认为false,进而从源码可得知,HierarchicalUriComponents.EncodeState的值为RAW。那么就会走下面的encodeUriComponent的方法(至于怎么encode暂不在分析范围内,看源码貌似是用字节流操作来encode,有兴趣可以阅读)

  • 接着对segment,queryParams等进行编码

  • 参数编码的细节,勾出来的是Java8的Function,实现就不贴了,源码比较简单

  • 最终,通过比对参数ext_info的值,便得知参数被主动encode了

解决方案

ok~~~ 分析完后,知道问题的根源在不该被encode的URL却在restTemplate的深处被动encode了,因此导致了回调的失败。既然明确问题所在,也分析过源码,解决起来就很简单了。如下:

构建UriComponentsBuilder,builder构建时传入encoded值为true,并转URI形式入参,问题解决。

枚举状态总结

为了帮助大家记忆,简单理一下状态。

  1. UriComponentsBuilder.EncodingHint

UriComponentsBuilder构建器有个枚举EncodingHint,UriComponentsBuilder构建时encoded默认为false。

  • 若encoded为false,则看有没有提供另外的encodeTemplate; 若有则值为ENCODE_TEMPLATE,否则为NONE
  • 若encoded为true时,值为FULLY_ENCODED
  1. HierarchicalUriComponents.EncodeState

HierarchicalUriComponents组件有个枚举EncodeState,其值的影响在于以上的EncodingHint。 源码的encode过滤也在于这个组件的EncodeState值!!!

  • 若EncodingHint为FULLY_ENCODED,则EncodeState为FULLY_ENCODED
  • 若EncodingHint不为FULLY_ENCODED(ENCODE_TEMPLATE或NONE),则EncodeState为RAW

总结

  1. 多多阅读源码,只会使用还远远不够。
  2. 带着问题阅读源码,是种不错的学习方式。

RestTemplate源码剖析:Encode相关推荐

  1. 源码剖析Django REST framework的认证方式及自定义认证

    源码剖析Django REST framework的认证方式 由Django的CBV模式流程,可以知道在url匹配完成后,会执行自定义的类中的as_view方法. 如果自定义的类中没有定义as_vie ...

  2. Python envoy 模块源码剖析

    Kenneth Reitz 是公认的这个世界上 Python 代码写得最好的人之一.抱着学习的心态,我阅读了 Reitz 写的 envoy 模块的源码,将笔记记录如下. 介绍 和 requests 模 ...

  3. 老李推荐:第14章8节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-获取控件列表并建立控件树 1...

    老李推荐:第14章8节<MonkeyRunner源码剖析> HierarchyViewer实现原理-获取控件列表并建立控件树 poptest是国内唯一一家培养测试开发工程师的培训机构,以学 ...

  4. 【Redis源码剖析】 - Redis内置数据结构之压缩列表ziplist

    在前面的一篇文章[Redis源码剖析] - Redis内置数据结构之双向链表中,我们介绍了Redis封装的一种"传统"双向链表list,分别使用prev.next指针来指向当前节点 ...

  5. 【知其然,知其所以然】配置中心 Apollo源码剖析

    第2章 Apollo源码剖析 能力目标 能够基于Git导入Apollo源码 能够基于IDEA实现DEBUG分析APP创建 掌握Namespace创建过程 掌握Item创建过程 掌握灰度发布创建过程 1 ...

  6. 【云原生微服务八】Ribbon负载均衡策略之WeightedResponseTimeRule源码剖析(响应时间加权)

    文章目录 一.前言 二.WeightedResponseTimeRule 1.计算权重? 1)如何更新权重? 2)如何计算权重? 3)例证权重的计算 2.权重的使用 1)权重区间问题? 一.前言 前置 ...

  7. 【C++标准库】std::string用法指南源码剖析

    文章目录 1.ASCII码 (1)计算机如何表达字符 2.C 语言中的字符类型 char (1)思想:char 即整数 (3)C 语言帮手函数 (4)C语言中的字符串 (4)C 语言转义符 3.C++ ...

  8. SpringBoot (一) 入门、配置、自动配置源码剖析理解

    文章目录 0 Spring Boot 1 Overview 1.1 Introduce **Spring** SpringBoot 微服务 1.2 快速上手 Hello World pom.xml s ...

  9. 【Redis源码剖析】 - Redis持久化之RDB

    原创作品,转载请标明:http://blog.csdn.net/xiejingfa/article/details/51553370 Redis源码剖析系列文章汇总:传送门 Redis是一个高效的内存 ...

  10. 02 Dask源码剖析-Dask的数据模型-Bag

    Dask源码剖析是一个专栏,更多章节请点击文章列表查看.后续我会更新更多内容上来. 文章目录 Collection:Bag Bag的创建 从内存序列创建(from_sequence) 从文本文件创建( ...

最新文章

  1. python基础 while循环练习
  2. 034_Unicode标准
  3. php amqp rabbitmq 介绍和使用
  4. 网页交互动画终极指南
  5. 架构:Android 组件化开发
  6. word List 07
  7. 配置yum,nc,telnet
  8. Java中的局部变量表及使用jclasslib进行查看
  9. 真实AIS数据,解码,可视化
  10. 编写Python高质量代码,资深程序员的 91 个建议
  11. 中消协发布疫情期间消费维权热点:口罩类投诉最多
  12. java定时器每一分钟执行一次_2行代码搞定一个定时器
  13. es2015(es6)基础知识整理(更新中...)
  14. slowfast 跑多卡的时候遇到问题
  15. 常用化学消毒剂使用方法
  16. UIPIckerView现实城市选择
  17. php文件开头加数据,在PHP中附加到文件的开头
  18. linux pid t 头文件_Linux中 pid_t 类型的定义.
  19. 数字通信第六章——信道模型和信道容量
  20. 服务器集群虚拟化区别,服务器集群与虚拟化

热门文章

  1. 多多情报通:拼多多推广账户金额可以通用吗?里面钱可以提出来吗?
  2. esp32测试wifi速率
  3. cesium 隐藏entity_Cesium中Entity讲解
  4. MySQL基础学习_第016章节_简单的查询语句
  5. libnids中TCP/IP栈实现细节分析——TCP会话重组
  6. C#实战004:Excel操作-读取Excel工作簿
  7. 我梦想中的学习组织-勤学会
  8. 强大的 Stream 函数式编程
  9. Web前端页面访问权限控制总结
  10. 本周AI热点回顾:政府数据将开放共享、生命游戏发明者逝世、百度Apollo对外发布“ACE 交通引擎”