代码是形式,逻辑是神韵。html
引子
在 “解锁优秀源代码的基本方法与技巧” 一文中,探讨了阅读优秀源码的基本步骤、方法、技巧、所面临的障碍及克服之策。多加训练,应该能够达成以下目标:程序员
- 可以读懂独立类和基本容器的实现;
- 可以读懂小型的基础库和框架;
- 经过源码阅读来调试和解决实际中的问题。
本文进一步探索如何阅读成熟框架的源码。
web
舒适提示
欲速则不达。阅读源码很容易理解为就是直接去阅读代码自己。实际上,代码只是形式,逻辑才是神韵。面试
凡有助于去理解逻辑,理解其原理、架构、实现的,都是值得阅读的。包括而不限于官方文档和 API 文档、架构设计分析文章、原理分析文章、源码阅读分析文章。磨刀不误砍柴工。准备工做作充足,充分借助各类资源辅助,阅读源码才能事半功倍。算法
预思考
有需求才有目标,有目标才有设计,有设计才有框架。在阅读某个源码模块以前,思考若干基本问题是必要的。编程
- 需求是什么?用一句话说清楚;
- 设计目标是什么?用一句话说清楚;
- 核心优点和适用场景是什么?分别用一句话说清楚;
- 基本原理是怎样的?先本身思考怎么实现,而后阅读框架原理文章;
- 总体设计是怎样的? 先本身思考怎么设计,而后阅读架构设计的文章;
- 技术难点是什么?先本身思考其中的难点及解决方案,而后阅读相关文章;
- 数据结构及算法流程是如何设计的?阅读框架的源码解析文章。
好比 SpringBean 模块:设计模式
- 需求:有一套通用机制去建立和装配应用所须要的完整的 Bean 实例,使得应用无需关注 Bean 实例的建立和管理,只要按需获取;
- 设计目标:根据指定的配置文件或注解,生成和存储应用所须要的装配完整的 Bean 实例,并提供多种方式来获取 Bean 实例;
- 核心优点:支持多种装配方式、自动装配、依赖关系自动注入;支持不一样做用域的 Bean 实例建立和获取;稳定高效;
- 适用场景:有大量的 Bean 须要建立,这些 Bean 存在复杂的依赖关系;
- 基本原理:反射机制 + 缓存;
- 算法流程:建立 bean 工厂对象 -> 扫描资源路径,得到 bean 的 class 文件 -> 生成 bean 定义的 beanDefinition 实例 -> 根据 beanDefinitioin 实例建立 bean 实例并缓存到 bean 工厂对象 -> 依赖自动注入 -> 执行钩子方法 -> 完整的 bean 实例准备就绪。
- 技术难点:依赖自动装配、循环引用;解决自动依赖注入和循环引用问题须要用到缓存机制。
需求与目标缓存
需求与目标每每容易混为一谈。但需求不等于目标。网络
- 需求是宽泛的,目标是具体的;
- 目标是需求的一种实现途径,每每是设计一个具有某些关键特性的系统或产品。
目标是功能与质量的结合体;除了功能部分,肯定质量指标也是尤其关键的。质量指标可参阅:“Web服务端软件的服务品质概要”数据结构
对于某个框架来讲,需求、适用场景和核心优点,都是能够直接在官网或项目主页获取到的。如何还原框架的设计目标呢?能够从核心优点中获取基本说明,更多的就要从 API 文档里来提炼了。
方法
不少开发童鞋可能对阅读源码心生畏惧。其实读源码既不神秘也不复杂:写个 Demo,打断点,运行,而后细细揣摩。阅读源码就是观摩高手出招的过程。
- 确立目标,一般是理解某个模块的原理、设计或者为了解决实际问题;
- 写个 demo,可以将主流程运行起来;
- 找到框架运行的入口点,经过静态代码分析,大体了解整个实现流程;
- 在预估会通过的关键地方打断点,单步调试;
- 仔细查看主流程通过的主路径、每个主要对象及其成员变量的值及变化,细细揣摩其设计意图和方法技巧;
- 绘制总体流程框图和类的交互图;
- 学习和理解关键类及关键方法及实现(代码)。
阅读源码,要把握主要与扩展:
- 首先把主流程及涉及到的主要类弄透彻;
- 理解其扩展机制;
- 理解主要扩展实现(须要的时候徐图之)。
阅读源码,经常要将“静态代码分析”和“单步调试”结合起来使用。
静态代码分析
静态代码分析,就是沿着方法调用链,“顺藤摸瓜”一路点击下去。一般可以对总体流程有一个大概的了解。
因为框架实现经常基于接口编程,有时会遇到有多个实现的情形。这时,能够根据直觉和经验,选择一个最有可能的默认实现继续跟下去,或者经过单步调试来弄清楚是哪一个具体实现。
单步调试
单步调试,是看似笨拙却很实用的源码阅读方法。单步调试在如下情形尤为有用:
- 接口调用有多个实现,难以肯定是哪一个是具体实现时;
- 查看某个比较复杂的具体类的成员时;
- 理解实现细节时。
框架解析
框架的设计实现一般包括三层:
- 问题域及解决方案构成的抽象层,解决问题的核心部分;
- 封装和交互构成的设计层,确保灵活性、可扩展性和应用集成;
- 各类细节构成的实现层,用于保证性能和容错等。
阅读顺序是:抽象层 -> 封装与交互层 -> 细节实现层 或者 抽象层 -> 细节实现层 -> 封装与交互层。抽象层比如匣中的宝珠,不能干买椟还珠的事情。
抽象层
抽象层便是问题求解层。技术面试中问到的原理或实现机制,一般都属于这一层。
因为封装和交互、实现细节的大量代码每每会将用于解决问题的核心代码“淹没”,所以,在探索抽象层时,要学会大胆过滤封装和细节,直接跳过大量的分支条件语句,暂时跳过使人疑惑的地方,始终聚焦和直击解决问题的核心部分。用于解决基本问题的核心代码一般是很少的。
好比,Bean 实例建立的核心代码是 ClassPathBeanDefinitionScanner.doScan(扫描资源路径,生成 beanDefinition 对象) 和AbstractAutowireCapableBeanFactory.doCreateBean 方法(根据 beanDefinition 建立 bean 实例)。
设计层
要弄明白设计层,就要先弄清楚框架的总体设计:
- 有哪些子模块,子模块的设计意图是什么;
- 子模块之间的关联是怎样的,如何串联成一个完整的设计意图。
框架的设计实现经常会用到设计模式。
- 经常使用设计模式:工厂、单例、外观、策略、适配器、装饰、代理、模板、组合、观察者、迭代器;
- 不一样问题域可能会用到的设计模式,好比 DB 驱动接口实现会用到生成器模式和桥接模式,web 请求处理用到职责链模式。
经常使用设计模式的使用场景:
- 若是须要建立实例,则一般离不开工厂和单例模式;
- 若是涉及较为复杂的算法流程,部分算法须要在子类实现,则会用到模板方法模式;
- 若是须要多种实现,并依据特定场景来选取使用,则会用到策略模式;
- 若是要将客户端接口及实现与框架的调用隔离,则会用到动态代理模式;
- 若是要灵活叠加多种功能,则会用到装饰器模式;
- 若是涉及到事件机制,则离不开观察者模式;
- 若是须要在库实现的基础上提供简洁接口,则一般用到外观模式;
- 若是要将多种实现与多种接口定义进行链接,则会用到桥接模式;
- 若是须要涉及大量配置(规格)并生成实例,则一般用到生成器模式;
- 若是涉及容器元素访问,则离不开迭代器模式;
- 若是须要以统一接口访问总体与部分的行为,且总体由部分组成,则一般用到组合模式。
理解基本设计模式的特征和适用场景,识别设计模式的使用,能够更自如地在框架源码之间穿梭。
细节层
细节是最考验源码阅读的心性了。细节藏魔鬼。关键细节考虑不周全,可能会致使整个设计的失败。所以,细节层也是值得仔细推敲的。技术面试中也经常考察实现细节。若是可以回答上来,大几率会让面试官眼前一亮。
有时,一些实现细节可能让人摸不到头脑。此时,能够上网搜索一下,每每会“茅塞顿开”。
克服障碍
阅读成熟框架源码,遇到的一大挑战就是对象之间的错综复杂的交互关系。使人生畏。这实际上考验着开发者的抽象和建模能力。
原理流程图
原理流程图很是重要,就像地图同样,指引人更容易地在“代码迷宫”中穿行而不迷失方向。
在阅读源码以前,设法弄到并理解框架的原理流程图,每每能起到事半功倍的效果。就如行兵打仗,先弄清楚天时与地形。不可不重视之。
概念图景
优秀的软件设计,每每是先创建一个比较完整的概念图景。概念图景,就是关于某个问题域的概念及其关联关系的总体图。
譬如盖房子吧。有的人盖房子就是:砌砖!砌砖!!砌砖!!!要安装窗户怎么办?把其中一大块砖墙锤空了再安。
有的人,则会“设计先行”:
- 原材料 => 子部件 => 组合与集成。
- 原材料:砖、石、木、铝、铜、玻璃等;
- 子部件:墙、窗框、窗户、门、地板、楼梯、锁、通道等;
- 房子:由子部件进行组合和集成而成;
- 机制:子部件的组合与集成的原理支撑,好比形状的组合与契合、承压计算等。
如何理清其中的复杂交互关系,从而理解其中蕴含的设计思想呢?须要先理清楚框架的概念图景。
有两种技巧能够结合使用:
- 因为接口定义了具体类的行为规范,能够经过阅读接口定义及文档来了解其设计思路和骨架;
- 查看具体类的实例成员(暂不涉及方法),根据经验揣摩其设计意图。
核心类成员
要深刻到具体实现,则没法避免核心类的阅读。核心类每每拥有十几个甚至几十个成员及方法,展现出了十足的源码阅读劝退诚意。面对这种状况怎么办呢? 有三个技巧能够结合使用:
- 按快捷键 Alt+7,能够查看该类的全部成员及方法,概览一下,大体猜想其意图;
- 首先只关注那些对核心问题求解有重要影响的成员,暂时忽略那些用来提高性能、可扩展性等方面的成员;
- 单步调试,仔细看看运行时的成员对象如何,这样会更加直观具体一些。好比 DefaultListableBeanFactory 这个类,单步调试后获得以下图示:
技术难点
技术难点也是理解源码实现的一个主要障碍。技术难点主要有三类:
- 数据结构与算法:好比 HashMap 用到了哈希表和红黑树,须要先阅读文献(好比《算法导论》)理解其结构与算法;
- 原理机制:好比 IO 读写、内存管理、文件系统、编译原理、网络协议,先学习相关的原理机制,夯实基础;
- 编程模型:特别的编程手法和技巧,好比读 hystrix 源码,就要先熟悉函数式编程和响应式编程。
如何找到论述原理机制的相关文献呢?有一些基本方法可循:
- 经典书籍:好比数据结构与算法,就有《算法导论》、《算法》、《计算机程序设计艺术》(排序与查找)等;
- 经典论文:一些还没来得及写入书籍的论文解读,好比 Raft 算法等;
- 技术书籍:好比 Linux 操做系统内核实现,TCP 协议详解等;
- 官方文档:优秀项目主页的文档部分,每每有相关原理机制的介绍,好比 ES 的官方文档;
- JavaDoc:优秀源码的 Java Doc 每每会引用相关出处,好比 AQS 的源码;
- 优秀博文:优秀博文每每有一些文献引用,能够阅读相关文献引用;
- 百科与搜索:在维基百科上搜索出处和引用来源;或者使用搜索引擎。
越到后面,就会发现,真正须要仔细阅读和钻研的书籍和论文,其实并很少。花费不少时间阅读网络文章,这些偷懒和捷径,反而是走了弯路。
耐心与意志
阅读框架源码须要很大的耐心和意志。有点像蚕宝宝吃桑叶,须要一点一点地啃。各个击破。在这个过程当中,须要克服很多障碍,才能“修得正果”。
可使用多种辅助手段:
- 边听音乐边阅读代码;
- 拉取代码分支,边读边作标记并提交;
- 阅读原理、架构及源码分析文章。
小结
源码阅读技能,能够说是程序员的“内功心法”之一。如果能读通优秀源码,则应对平常编程工做游刃有余,而应对难题则有路可循。
路漫漫其修远兮,吾将上下而求索。