第1章 源码阅读
1.1 源码阅读的意义
计算机技术和通信技术的蓬勃发展催生了一批又一批的软件开发者。对于软件开发者而言,学校的教科书、网上的培训视频都是非常好的入门资料。正是这些入门资料,帮我们打下了软件开发的基础。
信息技术的飞速发展也带来了许许多多的新概念,物联网、区块链、人工智能、云计算……层出不穷的新概念为我们描绘出一幅幅壮美的蓝图。介绍这些概念的书籍也如雨后春笋般不断涌现。
然而,在基础和蓝图之间却有着巨大的知识断层:我们很容易找到用来夯实基础的入门书籍,也很容易找到用来阐述蓝图的分析文章,却鲜有资料告诉我们如何从基础开始构建出蓝图中的雄伟建筑。于是,众多的开发者迷失在了基础和蓝图的知识断层中,如同一个手握铁锤的建筑工人看着摩天大楼的规划图却不知从何下手。于是有人选择了放弃,继续在增、删、改、查中沉沦;有人选择了摸索,不断在重构改版中挣扎。
本书的目的不是帮开发者构建软件开发的基础,也不是向开发者描绘新概念的蓝图。本书是为了给开发者指引一条从基础到蓝图的前进道路,帮助开发者具备在扎实的基础上建造蓝图中雄伟建筑的能力。
源码阅读是理解和分析优秀的开源代码,并从中积累和学习的过程。就如同剖析一座摩天大楼的内部构造般去分析一个优秀开源项目的组织划分、结构设计和功能实现,进而学习、借鉴并最终应用到自己的项目中,提升自己的软件设计和开发能力。
源码阅读也是一个优秀软件开发者必备的能力。如今绝大多数软件都是团队协作的成果,只有读懂别人的代码才能继续开发新的功能。即使是单兵作战,也需要读懂自己所写的旧代码,之后才能开展新的工作。
优秀的源码是最棒的编程教材,它能将整个项目完整地呈现给我们,使我们获得全面的提升。源码阅读能让我们:
· 透彻地理解项目的实现原理;
· 接触到成熟和先进的架构方案;
· 学习到可靠与巧妙的实施技巧;
· 发现自身知识盲点,完善自身知识储备。
因此,源码阅读是软件开发者提升自身能力极为重要的手段。
1.2 源码阅读的方法
源码阅读对于提升开发者的技术能力大有裨益,可源码阅读的过程却是极为痛苦的。每一个优秀的工程项目都凝聚了众多开发者的缜密思维逻辑;每一个优秀的工程项目都经历了从雏形到成熟的曲折演化过程。最终,这些思维逻辑和演化过程都会投射和堆叠到源码上,使得源码变得复杂和难以理解。因此,源码阅读的过程是一个通过源码去逆推思维逻辑和演化过程的工作。于是有人说读懂源码比编写源码更为困难,想必也是有一定道理的。
当我们阅读一份源码时,需要面对的困难通常有:
· 难以归纳的凌乱文件;
· 稀奇古怪的类型组织;
· 混乱不堪的逻辑跳转;
· 不明其意的方法变量。
……
可是,舒适能带来的只是原地踏步。梳理这些凌乱文件、理解这些类型组织、追踪这些逻辑跳转、弄清这些方法变量的痛苦过程,才是真正能让我们获得提升的过程。
源码阅读的过程中也有一些技巧,掌握这些技巧能减少源码阅读过程中的痛苦。“授人以鱼,不如授人以渔”,本书会将源码阅读中的方法和技巧总结出来,并希望大家将它们应用在其他项目的源码阅读中。我们先将一些基本的技巧介绍如下,更多的技巧将会在源码阅读的过程中不断给出。
· 调试追踪:多数情况下,当我们对某些变量的含义产生疑惑时,借助开发工具的调试功能直接查看变量值的变化是一个非常好的方法。而且该方法还能指引代码逻辑的跳转过程,对于理解源码极为有用。
· 归类总结:优秀的源码都遵循一定的设计规则,这些规则可能是项目间通用的,也可能是项目内独有的。在源码阅读的过程中将这些设计规则总结出来,将会使源码阅读的过程越来越顺畅。
· 上下文整合:有些对象、属性、方法等,仅仅通过自身很难判断其作用和实现。此时可以结合其调用的上下文,查看对象何时被引用、属性怎样被赋值、方法为何被调用,这对于了解它们的作用和实现很有帮助。
另外,还有一点不得不提,那就是要有一套强大的开发工具。有一套支持代码高亮显示、错误提示、引用跳转、断点调试等功能的开发工具十分必要,它能让我们快速定位到所调用的方法,也能让我们快速找到当前变量的引用,这些功能是进行源码阅读所必需的。在 Java编程领域,强大的开发工具有 IDEA、Eclipse等,大家可以根据自己的喜好选用。
1.3 开源软件
开源软件(open source software)即开放源代码软件。这类软件具有极强的开放性,其源代码被公开出来供大众获取、学习、修改,甚至重新分发。也正因为其开放性,一些开源软件吸引了众多开发者参与其中,而这些开发者中不乏领域内的顶尖“大牛”。
以 Linux源代码为例,截至目前它经历过 21000多名开发者的 840000多次的提交。这充分说明了它是众多开发者智慧的结晶,也从侧面说明了该项目代码的严谨与优雅。
所以说优秀的开源软件是进行源码阅读的绝佳材料。
Github平台是全球最为知名的开源软件库,众多优秀的开源软件就是在 Github平台上协作开发的。我们可以到 Github平台寻找自己领域内的优秀开源软件,开展源码阅读工作。图1-1展示了 Java领域的一些优秀开源项目。
· apache/dubbo:一个高性能的远程过程调用框架;
· netty/netty:事件驱动的异步网络应用框架;
· spring-projects/spring-boot:一套简单易用的 Spring框架;
· alibaba/fastjson:一套快速的 JSON解析、生成组件;
· apache/kafka:一套实时数据流处理平台;
· mybatis/mybatis-3:一套强大的对象关系映射工具。
图1-1 Java领域的一些优秀开源项目
除上述项目外,Github上还有众多优秀的开源软件供大家使用、学习,甚至参与开发。
1.4 MyBatis源码
经过不断的筛选,本书最终选择了开源软件 MyBatis 作为源码阅读的材料。这主要基于以下几方面的考虑。
· MyBatis 项目悠久、成熟,且有着极广的应用范围,目前有十余万个开源项目引用它。
· MyBatis包括数据库操作、对象关系映射、配置文件解析、缓存处理等众多功能,涉及的知识面十分广泛。
· MyBatis源码的代码量比较合适,如果代码量太大,则一本书难以细致地讲完;而如果代码量太小,则不能充分暴露源码阅读过程中可能遇到的问题。
因为 MyBatis是我们源码阅读的材料,所以学完本书后,我们不仅会学到源码阅读的方法和技巧,还会对 MyBatis 的实现原理、代码结构和设计技巧等了如指掌。最终,我们会成为 MyBatis 的精通者,这算是学习本书的额外收获。因此,也可以单纯地将本书作为一本 MyBatis源码解析书来看待。
本书所使用的 MyBatis版本为最新的稳定版 3.5.2,其开源项目地址为:
建议在阅读本书时参考上述代码的中文注释版,其开源项目地址为:
该版本在 3.5.2版本的基础上增加了中文注释。由于篇幅所限,很多书中没有展示的代码及注释也能在该版本中找到。因此这是阅读本书时非常必要的辅助资料。
1.5 本书结构
在这一节中我们将对本书的结构进行简要的介绍。同时,考虑到本书会涉及 MyBatis的相关文件和大量的源码,我们也会对源码分析中涉及的术语进行规范。
1.5.1 背景知识
如果要说什么是源码阅读中最重要的因素,那应该是基础知识。
如果不了解开源项目中的设计模式,则很难理清楚源码的结构;如果不清楚开源项目中的编程知识,则很难弄明白逻辑的走向。因此,掌握好开源项目中用到的相关基础知识非常重要。
为了更好地理解源码,在每个章节开始处将章节所述源码中涉及的知识介绍给大家。这些知识包括但不限于:
· 设计模式;
· Java基础与进阶知识;
· 项目用到的外部工具包;
· 项目依赖的外部类。
可以根据自己的知识储备对这些背景知识进行学习,然后进行章节内源码的阅读。
为了能够更快地消化和吸收相关的知识,本书还准备了大量的示例,并将这些示例汇总成了一个开源项目 MyBatisDemo,其开源地址为:
1.5.2 文件的指代
使用 MyBatis时,会涉及三类文件。下面分别对这三类文件进行简要介绍,在本书后面的叙述中,将使用这些名称来指代相应的文件。
1.配置文件
MyBatis的配置文件为一个 XML文件,通常被命名为 mybatis-config.xml。该 XML文件的根节点为 configuration,根节点内可以包含的一级节点及其含义如下所示。
· properties:属性信息,相当于 MyBatis的全局变量。
· settings:设置信息,通过它对 MyBatis的功能进行调整。
· typeAliases:类型别名,在这里可以为类型设置一些简短的名字。
· typeHandlers:类型处理器,在这里可以为不同的类型设置相应的处理器。
· objectFactory:对象工厂,在这里可以指定 MyBatis创建新对象时使用的工厂。
· objectWrapperFactory:对象包装器工厂,在这里可以指定 MyBatis使用的对象包装器工厂。
· reflectorFactory:反射器工厂,在这里可以设置 MyBatis的反射器工厂。
· plugins:插件,在这里可以为 MyBatis 配置差价,从而修改或扩展 MyBatis 的行为。
· environments:环境,这里可以配置 MyBatis运行的环境信息,如数据源信息等。
· databaseIdProvider:数据库编号,在这里可以为不同的数据库配置不同的编号,这样可以对不同类型的数据库设置不同的数据库操作语句。
· mappers:映射文件,在这里可以配置映射文件或映射接口文件的地址。
同时要注意,对配置文件中的一级节点是有顺序要求的,这些节点必须按照上面列举的顺序出现。在使用中可以根据实际需要选择相应的节点依次写入配置文件。
代码1-1展示了一个简单的配置文件示例。
【代码1-1】
2.映射文件
映射文件也是一个 XML文件,用来完成 Java方法与 SQL语句的映射、Java对象与SQL参数的映射、SQL查询结果与 Java对象的映射等。通常,在一个项目中可以有多个映射文件。
映射文件的根节点为 mapper,在 mapper节点下可以包含的节点及其含义如下所示。
· cache:缓存,通过它可以对当前命名空间进行缓存配置。
· cache-ref:缓存引用,通过它可以引用其他命名空间的缓存作为当前命名空间的缓存。
· resultMap:结果映射,通过它来配置如何将 SQL查询结果映射为对象。
· parameterMap:参数映射,通过它来配置如何将参数对象映射为 SQL参数。该节点已废弃,建议直接使用内联参数。
· sql:SQL语句片段,通过它来设置可以被复用的语句片段。
· insert:插入语句。
· update:更新语句。
· delete:删除语句。
· select:查询语句。
代码1-2给出了一个简单的映射文件示例。
【代码1-2】
在映射文件中,insert、update、delete、select 节点最为常见,这类节点统称为数据库操作节点,而节点的内容是一个支持复杂语法的 SQL语句,称为数据库操作语句。数据库操作节点与数据库操作语句如图1-2所示。
图1-2 数据库操作节点与数据库操作语句
3.映射接口文件
映射接口文件是一个 Java接口文件,并且该接口不需要实现类。通常情况下,每个映射接口文件都有一个同名的映射文件与之相对应。
在映射接口文件中可以定义一些抽象方法,这些抽象方法可以分为两类:
· 第一类抽象方法与对应的映射文件中的数据库操作节点相对应。
· 第二类抽象方法通过注解声明自身的数据库操作语句。当整个接口文件中均为该类抽象方法时,则该映射接口文件可以没有对应的映射文件。
代码1-3给出了一个映射接口文件的示例。
【代码1-3】
因为映射接口文件实际是一个 Java接口,所以有时也会称其为映射接口。
1.5.3 方法的指代
1.方法名
在 Java程序中,常常会针对某一方法重载多个方法,以满足不同的使用需求。例如,代码1-4是 CacheException类的一组构造方法,共包含四个输入参数不同的方法。
【代码1-4】
在本书中,将使用 CacheException 来指代具有该方法名的上述四个方法,而使用CacheException()来特指方法一,使用 CacheException(String,Throwable)来特指方法三。
2.核心方法
在某些情况下,具有相同方法名的一组方法是为了便于外部调用而重载的,其核心实现逻辑都集中在某一个方法内,其他方法只做了转接适配的工作。
例如,代码1-5所示的三个 selectMap方法中,方法一、二中仅仅进行了默认参数的设置、转化等简单的适配操作,然后调用了方法三。方法三中则包含了核心的操作逻辑。
【代码1-5】
在本书中,将方法三这样的包含核心操作逻辑的方法称为核心方法。所以,selectMap (String,Object,String,RowBounds)就是 selectMap这一组方法中的核心方法。
非核心方法中的代码大多十分简单和易于理解,因此在后面的源码分析中,我们多围绕核心方法展开。