【重写SpringFramework】从零开始构建Spring框架:序言

1. 概述

在如今的 Java 后端开发中,引入几个依赖,添加几个注解,一个 web 应用就能启动起来。但我们能说开发者真的掌握相关技术了吗?高度封装的框架有利有弊,一方面提高了开发的效率,另一方面屏蔽了大量的实现细节,开发者只知其然而不知其所以然。**对于开发人员来说,框架就是产品,提供的是成熟的服务。**一般说掌握了某某框架,只是会使用而已,并不代表了解其原理。

要想更深入地理解框架,就需要阅读源码,这是横亘在每个开发者面前的大山。只有跨过了这道坎,编程水平会得到长足的进步,技术视野也会变得更加广阔。所谓他山之石,可以攻玉。通过对优秀框架的研究,掌握高明的设计理念和编程技巧,对于自身技术能力的提升大有裨益。

Spring 框架作为 Java 语言最流行最重要的开源框架,对于开发者来说不仅是使用工具,也是学习对象。我们将通过造轮子的方式重新构建整个框架,全面而深入地理解 Spring 框架的核心理念以及主体流程。

2. 面临的问题

2.1 内容庞杂

阅读源码的第一个问题是面对庞杂的体系,无处下手。即使阅读过一些书籍和文章,研究问题时往往也是一叶障目,不见泰山。这一现象产生的原因是缺乏系统化、模块化的思维方式,不能从全局视角进行整体把握。

目前市面上充斥着关于 Spring 框架的书籍和文章,往往有深度的无广度,有广度的无深度。一部分号称面面俱到,实际上蜻蜓点水,没有触及问题的核心。另外一部分对某些问题有比较深入的研究,但缺乏横向的联系,形成了一个个信息孤岛。要知道一个严密的系统是环环相扣的,仅对个别问题的研究并不能使我们认识到庐山真面目。

2.2 细节地狱

在阅读源码时,面对某一个功能,很容易陷入分支流程的无尽迷宫,以及异常繁琐的细节实现。我们将这种困境称为细节地狱(particulars hell),这种情况应当设法避免,应对的方法包括两个方面。

首先需要对功能进行划分,将一个大问题分解成若干个小问题。每个小问题都只关注一个方面,把代码量控制在一定的范围内。其次,阅读源码并不意味着一定要把所有的细节搞清楚,关键是掌握一个度。比如 JDK 原生的 API 会使用就足够了,当然对于一些重要的部分掌握其原理更好。Spring 核心包与 JDK 类似,提供了一些基础功能。因此我们并不打算深究其实现细节,直接引入 Spring 核心包作为整个项目的基石。

2.3 代码瘦身

Spring 框架演变至今,已经从一个功能单一的框架发展到了一个庞大的生态。即便仅考虑微服务体系的技术栈,也包括了几十个模块。对模块的选择是第一步,具体内容见下文的学习目标。仅对模块进行精简还是不够,代码量依然很大。虽然是造轮子,但并不是一比一复制,这样做既无可能也无必要。因此我们需要对代码进行精简,在这一过程中需要遵循一定的原则,要做到涵盖核心功能,确保主体流程的完整。

在当今的社会实践中,二八原则作为一项重要理论指导生产生活,其基本表述为少数实体占据多数资源。同样地,二八原则也适用于软件领域。对于一个框架来说,核心代码只占两成,另外八成是为了处理特殊情况,以保证代码的健壮性和扩展性。如果贪多求全,代码量会急剧增长,想独立完成 Spring 框架的重建几乎是不可能的。鉴于此,我们在代码层面上会进行一定的精简,但也不是那种浮于表面的 demo。我们关注的是 Spring 框架的核心流程,用尽可能少的代码来实现功能。

3. 学习目标

3.1 总体规划

本教程的学习计划分为三个阶段,首先是 Spring Framework,构建一个传统的 web 应用。其次,在 Spring Framework 的基础上,结合嵌入式 servlet 容器,构建 Spring Boot 应用。第三,以 Spring Boot 为基础,实现分布式的微服务体系。三个阶段环环相扣,通过逐步学习,对 Spring 框架的核心功能有全面而深入的理解。

在这里插入图片描述

第一阶段的目标是构建一个传统 web 应用,将项目打包成一个 war 包,然后置于 servlet 容器(比如 Tomcat)的工作目录下。当 servlet 容器启动时,运行 web 应用。本阶段我们将实现 Spring Framework 的六个模块,包括 beans、aop、context、tx、web、webmvc 等。

第二阶段的目标是构建一个 Spring Boot 应用,最大的特点是将 servlet 容器嵌入到 web 应用中,实现开箱即用、约定大于配置等特性。本阶段我们将实现 Spring Boot 的四个模块,包括 tomcat-embed、mybatis、boot、boot-autoconfigure 等。其中 tomcat-embed 是嵌入式 servlet 容器,mybatis 是流行的 ORM 框架。它们都是第三方框架,属于选学部分。

第三阶段的目标是构建一组基于 Spring Boot 应用的微服务体系,包括注册中心、网关服务、业务服务(business service)等经典角色,实现服务治理、负载均衡、断路保护、高可用等分布式环境下的功能。本阶段我们将实现 Spring Cloud 的五大组件,包括 Eureka、Ribbon、Hystrix、Feign、Zuul 等,构建一个完整的微服务体系。

3.2 最终目标

本教程的最终目标是构建一个微服务体系,包括注册中心、网关服务,以及两个业务服务,即会员服务和订单服务。进一步,我们可以构建多组微服务,各组之间通过注册中心进行通信,从而形成高可用的分布式系统。

在这里插入图片描述

对于一个微服务应用来说,需要引入若干依赖。以下是本教程所涉及的模块,按照三个阶段进行分组。其中,绿色标识属于 Spring Framework 的相关依赖,蓝色标识为 Spring Boot 的相关依赖,红色标识为 Spring Cloud 的相关依赖。

在这里插入图片描述

在项目构建方面,我们严格按照 Spring Cloud 的标准方式来构建。包括使用 starter 引入依赖包、配置文件、声明注解来启动服务等。以网关服务为例进行说明,以下是 pom.xml 引入的依赖项:

在这里插入图片描述

yaml 的配置包括服务名、端口号、与注册中心的通信、路由规则等,与 Zuul 服务的配置完全一致。

在这里插入图片描述

应用主类声明了 @SpringBootApplication@EnableZuulProxy 注解,并以 Spring Boot 应用的方式启动。

在这里插入图片描述

在 IDE 中启动四个微服务应用,控制台打印启动日志,如下所示。

在这里插入图片描述

3.3 本阶段目标

在第一阶段,我们的目标是实现 Spring Framework 六个模块,各模块之间的依赖关系如下所示。首先,我们需要依赖 Spring 核心包,这是唯一引入的 Spring 原生包。然后是 beans 和 aop 模块,它们提供核心的 IOC 和 AOP 功能。接下来是 context 模块,作为门面与用户(包括其他模块)打交道,比如大名鼎鼎的 ApplicationContext 就属于 context 模块。

接下来出现了两个分支,它们是 Spring Framework 的两个应用方向,通常会结合起来使用。在一个典型的 web 应用中,至少要引入 webmvc 和 jdbc 模块,然后通过依赖关系引入其他模块。

  • tx 和 jdbc 模块是为访问数据库提供服务的,由于 jdbc 模块的代码较少,我们将其归入了 tx 模块中。

  • web 模块提供了 web 应用的基础组件,比如 WebApplicationContext 表示运行在 web 环境中的 Spring 容器。理论上我们可以利用这些组件构建 web 应用,实际上很难直接使用。

  • webmvc 模块引入了 MVC 架构,极大地提高了 web 应用的开发效率。事实上,构建 web 应用可以选择其他 MVC 架构,比如以前比较流行的 Struts2 框架。

在这里插入图片描述

4. 本教程的特点

4.1 分层方法论

前面分析了阅读源码时的种种困难,那么正确的做法是什么,我们针对高中低不同的层次总结了三条原则。

  • 在顶层设计方面,做到高屋建瓴,俯瞰全局。这一层次主要是抽象的理念上的认识,比如 Spring Framework、Spring Boot、Spring Cloud 三者之间的关系是如何组织的,各模块的定位以及模块之间的依赖是如何处理的。

  • 在中间架构方面,做到提纲挈领,纲举目张。对于一个业务范畴,比如 IOC 和 AOP 机制、数据库事务等,它们的主体结构是如何建立的,可以分解为多少个具体的功能,其中用到了哪些编程思想和设计模式。一个模块往往包含一个或几个业务范畴,抓住了这些关键节点,对于整个模块的理解会变得事半功倍。

  • 在底层实现方面,做到条分缕析,举一反三。我们需要了解具体的功能用到了哪些技术,这些技术的特点以及各自的适用情况。特别是很多功能之间有着清晰的演进脉络,比如一个功能看上去很复杂,但如果了解前置的技术,再来看该功能,大部分是对原有技术的整合,还有一部分新内容,总体而言更容易理解。

如果我们把 Spring 框架看作一个大型建筑,那么顶层设计就是项目蓝图,规划了建筑的形制与功能。中间架构是地基和框架,承载了整个建筑的主体结构。底层实现是砖石材料,填充完善细节部分。三者不可偏废,互相配合,最终造就了 Spring 这座宏伟的殿堂。

4.2 渐进式开发

本教程的出发点是从框架编写者的角度考虑问题,而不是从使用者的角度考虑问题。面向对象编程的核心是组织和调度,单独编写一个功能对开发者来说很简单,但要将若干个功能整合在一起,从而构成一个庞大的、严密的、完备的体系,就必须遵循一定的规律。因此我们采取的是渐进式开发,先从最基础的功能做起,从简单到复杂,从单一功能到统一的有机的整体结构。我们将按照模块的依赖关系进行讲解,先介绍底层模块,然后是上层模块,以此类推。大多数情况下,我们会遵循这一原则,当然也有例外,届时会进行说明。

举个例子,在介绍 IOC 容器时,通常是把 BeanFactoryApplicationContext 结合起来一起讲。实际上,前者属于 beans 模块,后者属于 context 模块。我们会先介绍 BeanFactory 的基本功能,再介绍 ApplicationContext 的作用,至于两者之间的区别和联系,这又牵扯到 beans 和 context 模块各自的定位,这些问题将在学习的过程中予以解答。

4.3 测试用例

本教程提供了丰富的测试内容,每一节都会设计若干测试用例,使得读者在读完本节后,知道该做什么。而不是像某些纯源码读物,看了之后云山雾里,不知所云。出于控制代码量的考虑,测试用例仅覆盖重点介绍的核心功能。此外,还有一些常用但比较次要的功能,该部分的实现和测试交由读者来完成,进一步巩固学习效果。

5. 版本选择

为了方便读者对照源码,我们在版本的选择上进行一定的考量。Spring Cloud 的版本选择 Edgware 或 Dalston,对应的 Spring Boot 版本是 1.5.x。Spring Boot 的版本选择 1.5.22.RELEASE,这是 1.5.x 的最后一个发布版。与之对应的是 Spring Framework 的版本是 4.3.25.RELEASE,这也是 Spring4 的最后一个发布版。

在这里插入图片描述

为什么选择低版本的 Spring Cloud?一开始 Spring Cloud 是对 Netflix 的微服务组件进行封装,后来 Netflix 停止了相关组件的开源。如果使用高版本的 Spring Cloud,由于 Netflix 停止了对 Zuul、Feign 等组件的支持,Spring Cloud 不得不使用 Gateway 和 OpenFeign 来代替。此外,Eureka 也可以使用 Nacos、Appllo 等注册中心组件来替代。我们来分析一下这些组件的影响:

  • Gateway 使用 Webflux 代替了 SpringMVC 框架,使用了响应式编程的代码风格,增加了理解的难度。更重要的是,嵌入式的 Tomcat 服务替换成了 Netty 框架。而 Spring Boot 使用 Tomcat 作为 servlet 容器,我们不希望增加学习的成本,因此选择 Zuul 作为网关服务。
  • OpenFeign 大体上继承了原本 Feign 组件的逻辑,区别不大。
  • Eureka 虽然也有替代的 Nacos 等注册中心,但它们毕竟是第三方框架,不便研究源码。

此外,Spring Framework 版本的选择也有一定的考量。Spring5 相比 Spring4 来说,增加了很多新东西。比如对 Koltin 语言的支持,很多地方的代码使用函数式编程的风格重写,增加了阅读源码的难度。因此,选择 Spring4 的最后一个发行版,既保证了稳定性,又与 Spring Boot 版本保持一致,可谓一举两得。

总的来说,我们的目标从整体层面了解微服务的治理,为了分析核心组件的原理,因此选择较低版本的 Spring Cloud。至于 Spring Framework 和 Spring Boot 则选择了大版本的最后一个发布版,我们认为这一版本的完成度和稳定性是很高的,可以作为源码的参考依据。

6. 结语

庖丁解牛是《庄子•养生主》中记载的一则脍炙人口的故事。庖丁为文惠君表演杀牛的技艺,文惠君对庖丁高超的技巧叹为观止,并询问是如何做到的。庖丁解释说自己一开始杀牛时,看到的只是眼前的牛。三年之后,就看不到全牛了,是因为自己做到了以神遇而不以目视,官知止而神欲行。这是说掌握了事物的运行规律,就能做到胸有成竹,随心所欲。具体的做法是彼节者有间,而刀刃者无厚。以无厚入有间,恢恢乎其于游刃必有余地矣。这是说牛的骨骼肌肉是有间隙的,但刀刃很薄几乎没有厚度。把没有厚度的刀刃放到骨肉的间隙中,自然就游刃有余了。

文惠君从庖丁解牛的过程中领悟了养生的道理,同样地,我们也可以用来研究编程的问题。Spring 框架对我们来说是一个庞然大物,但再大的系统也是有间隙的,是由一个个基本单元组成的。我们只要合理运用编程思想、设计模式等工具,对复杂系统进行分解,把每一个基本单元搞清楚,也能达到「以无厚入有间,游刃有余」的境界。


项目地址:https://gitee.com/stimd/spring-wheel
扫码关注公众号,加群一起讨论。
在这里插入图片描述

  • 29
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值