最近看了一些 Spring 源码,发现源码分析的文章很多,而底层思想分析的文章比较少,这个系列文章准备总结一下Spring中给我的启示,包括设计模式思想、SOLID设计原则等,涉及一些编程的基本原则,虽然看似简单,实则“小道理、大学问”。

我尽量遇到的问题谈起,再说解决方案,同时至少举两个例子。

这些方法都是基于我遇到的一些实际代码,掌握了基本思想,就可以举一反三。

让人头晕眼花的跳转

如果你通过某些培训机构的源码课,就会发现他们的老师在讲源码的时候在类之间、方法之间不停地跳,学员一脸懵逼。因为如果不理解老师讲课的思路,或者是稍微走一下神,就会觉得自己跟不上了。

其实,问题就在于需要理解源码的基本流程和继承链的这种单一职责原则。

读懂继承链

面向对象的特点是封装继承和多态,封装体现在私有方法。

在Spring中,常见的模式是顶层为接口,然后是抽象类,最后是各个实现类。

接口负责对外暴露,符合面向接口编程的准则,在更改实现类时,可以减小对于代码的改动,保证了代码依赖于抽象(接口)。.

抽象类一般使用模板模式,实现了逻辑的组装,把子类中的公用逻辑抽取出来,方便子类编写;模板方法一般为固定的执行链,我们读源码时,可以予以关注。比如常见的AbstractApplicationContext::refresh 方法,AbstractBeanFactory::doGetBean方法。由于接口一般只能暴露方法声明,抽象类可以实现一些状态的getter,settter,这样子类访问这些状态数据只需要调用方法即可。

实现类通常有多个,比如todo。有些时候当实现类只有一个或者有一个默认实现类时,常常使用default命名,比如DefaultListableBeanFactory. 读源码时可以选择地阅读默认实现。

Java 只支持单继承,这种语言层面的规范方便了我们阅读源码,一个方法的实现必然在一条继承链中。

举例1:

Spring中BeanFactory的默认实现是DefaultListableBeanFactory, 通过继承图可以看出,有多个抽象类,从上到下分别实现了以下的能力:

  • 别名注册(SimpleAliasRegistry
  • 单例注册(DefaultSingletonBeanRegistry
  • FactoryBean 注册(FactoryBeanRegistrySupport
  • bean工厂接口(主要就是getBean相关的一些方法)(AbstractBeanFactory
  • 自动装配(AbstractAutowireCapableBeanFactory

同时我们还可以看到,基本上每一个抽象类都对应一个实现的接口:

AliasRegistrySimpleAliasRegistry

SingletonBeanRegistryDefaultSingletonBeanRegistry

AbstractBeanFactoryConfigurableBeanFactory

AbstractAutowireCapableBeanFactoryAutowireCapableBeanFactory

注意到 DefaultListableBeanFactory 实现了ConfigurableListableBeanFactory,这个接口实现了一些简化配置 beanFactory 的方法,是一个常用的基础设施类(接口)。

实际上,这个设计也是不得已而为之。Java只支持单继承,理想的情况下,DefaultListableBeanFactory 需要继承不同的trait,即单例注册、FactoryBean注册等功能模块,Configurable, Listable, **Capable恰好是这种设计思想的体现。如果最终的DefaultListableBeanFactory写成一个类,一定是巨大的,但是假如我们将BeanFactory不同的特性做拆分的话,就会得到如图所示的看似复杂的接口继承关系。

这种松散的接口继承关系正是我们需要的,举例来说,autowireCapable 和 hierarchical并没有实际上的联系,一个关注属性注入,另一个则关注bean工厂的层级关系(可以有父工厂)。

假设每一个松散的接口都有几个或多个实现,不管其是否是抽象类或者具体实现,我们只有通过多继承或者委托模式组装得到DefaultListableBeanFactory

这就是矛盾的地方,单一的继承链和松散的接口,其结果就是抽象类具有了一些不必要的功能。比如AbstractAutowireCapableBeanFactory 具有了Configurable、aliasRegistry等能力。这种情况是我们阅读源码是需要注意的。

Spring通过将单继承链分解为6个类,将DefaultListableBeanFactory 进行了功能拆分,符合开闭原则,每个类也符合单一职责原则的要求。

通过以上分析,打开DefaultListableBeanFactory 的源码,虽然有2000多行,我们可以清楚地看出类的结构,包括1. 继承链相关:不同抽象方法的实现、未在抽象类中实现的接口方法的实现。2. BeanDefinitionRegistry 3. ConfigurableListableBeanFactory 4. Serializable

举例2:

类似如上的分析,AnnotationConfigApplicationContext 的继承链如下:

DefaultResourceLoader → AbstractApplicationContext → GenericApplicationContext → AnnotationConfigApplicationContext

每个类实现的功能即其直接实现的接口,有些类通过类名也可以快速得知其实现的功能。不再赘述。

GenericApplicationContext 实现了BeanDefinitionRegistry,直接委托BeanFactory的实现给DefaultListableBeanFactory,子类包括AnnotationConfigApplicationContext 和mvc容器等。

在抽象类AbstractApplicationContext可以看到大家耳熟能详的refresh方法。

ApplicationContext更是重量级,作为应用容器,拥有BeanFactory外的事件广播、国际化、资源读取等能力。

由于ApplicatoinContext是大接口,是不同功能的最终整合,所以我们看到的接口继承关系并不复杂。

举例3:

我们知道MVC模型中具有中央调度器,在SpringMvc中体现为DispatcherServlet,其继承链如图。

了解过servlet的人都知道HttpServlet具有doGet、doPost等方法,子类重新后所有方法都转发到DispatcherServlet中,doService→doDispatch执行了我们熟知的分发模板逻辑:简单来说就是

getHandlergetHandlerAdapterapplyPreHandle**handle**applyPostHandleprocessDispatchResult

processDispatchResult中包含异常处理,render和afterCompletion

我们随便选择一个方法,比如初始化mvc容器,其必在继承链上的某个类中进行实现,通过分析源码可以看出:

initWebApplicationContext在FrameworkServlet中实现。

GenericServlet暴露init方法。

HttpServletBean实现了init方法,在init方法中暴露initServletBean方法。

FrameworkServlet实现initServletBean方法,其中实现了initWebApplicationContext方法。

虽然执行初始化mvc容器方法需要在继承链上来回跳转,但是其实现了单一职责原则,每一个类负责实现了特定的功能,模板类实现了模板流程,实现类实现具体实现。

从Spring中学到的【1】--读懂继承链的更多相关文章

  1. 夯实Java基础系列23:一文读懂继承、封装、多态的底层实现原理

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  2. 从源码入手,一文带你读懂Spring AOP面向切面编程

    之前<零基础带你看Spring源码--IOC控制反转>详细讲了Spring容器的初始化和加载的原理,后面<你真的完全了解Java动态代理吗?看这篇就够了>介绍了下JDK的动态代 ...

  3. 一篇文章,读懂Netty的高性能架构之道

    一篇文章,读懂Netty的高性能架构之道 Netty是由JBOSS提供的一个java开源框架,是一个高性能.异步事件驱动的NIO框架,它提供了对TCP.UDP和文件传输的支持,作为一个异步NIO框架, ...

  4. 一文读懂分布式任务调度平台XXL-JOB

    本文主要介绍分布式任务调度平台XXL-JOB(v2.1.0版本),包括功能特性.实现原理.优缺点.同类框架比较等 基本介绍 项目开发中,常常以下场景需要分布式任务调度: 同一服务多个实例的任务存在互斥 ...

  5. 一文读懂Java动态代理

    作者 :潘潘 日期 :2020-11-22 事实上,对于很多Java编程人员来说,可能只需要达到从入门到上手的编程水准,就能很好的完成大部分研发工作.除非自己强主动获取,或者工作倒逼你学习,否则我们好 ...

  6. 读懂UI设计的心理学

    好文转载,版权归原作者 作为UI设计师,对待用户就像对待婴儿,知道如何通过界面设计诱导用户非常重要,这就需要了解心理学方面的知识了.今天分享一篇日本设计师的好文,结合心理学与设计,教你读懂心理学,提高 ...

  7. 一文读懂UGC:互联网上的生态秘密

    转载自近乎: UGC(User- Generated Content)用户原创生产内容,它是相对于PGC(Professionally-produced Content)专业生产内容的一种内容来源,简 ...

  8. 读懂IL代码就这么简单(三)完结篇

    一 前言 写了两篇关于IL指令相关的文章,分别把值类型与引用类型在 堆与栈上的操作区别详细的写了一遍 这第三篇也是最后一篇,之所以到第三篇就结束了,是因为以我现在的层次,能理解到的都写完了,而且个人认 ...

  9. 读懂IL代码就这么简单(二)

    一 前言 IL系列 第一篇写完后 得到高人指点,及时更正了文章中的错误,也使得我写这篇文章时更加谨慎,自己在了解相关知识点时,也更为细致.个人觉得既然做为文章写出来,就一定要保证比较高的质量,和正确率 ...

随机推荐

  1. 【python基础】第07回 运算符和流程控制 2

    本章内容概要 1.逻辑运算符补充 2.循环结构 本章内容详解 1.逻辑运算符补充 两边都不为0的情况 or 直接取前面的值 and 直接取后面的值如果存在0的情况 and 直接取0 or 直接取非0 ...

  2. ShardingSphere-proxy-5.0.0建立mysql读写分离的连接(六)

    一.修改配置文件config-sharding.yaml,并重启服务 # # Licensed to the Apache Software Foundation (ASF) under one or ...

  3. Docker 与 K8S学习笔记(二十五)—— Pod的各种调度策略(上)

    上一篇,我们学习了各种工作负载的使用,工作负载它会自动帮我们完成Pod的调度和部署,但有时我们需要自己定义Pod的调度策略,这个时候该怎么办呢?今天我们就来看一下如何定义Pod调度策略. 一.Node ...

  4. 图片管够!用Python做了个图片识别系统(附源码)

    本项目将使用python3去识别图片是否为色情图片,会使用到PIL这个图像处理库,并且编写算法来划分图像的皮肤区域 介绍一下PIL: PIL(Python Image Library)是一种免费的图像 ...

  5. vue在Docker上运行

    Dockerfile # 设置基础镜像 FROM nginx:latest # 定义作者 MAINTAINER test # 将dist文件中的内容复制到 /etc/nginx/html/ 这个目录下 ...

  6. HBuilderX配置外部服务器(tomcat)查看编辑jsp界面

    HBuilderX配置外部服务器(tomcat)查看编辑jsp界面 一.第一种方法,通过启动本地tomcat,查看jsp 在tomcat的webapps目录下创建文件夹HBuilderX 打开HBui ...

  7. MarkDown语法——更好地写博客

    MarkDown语法--更好地写博客 我们在学习过程中要尽量养成编写博客的 好习惯:一方面方便自己在学习之后进行一次汇总,其次自己书写的文章可以在以后的时间里反复查看以便于巩固,在找工作时博客也是被招 ...

  8. Three.js系列: 在元宇宙看电影,享受 VR 视觉盛宴

    本文 gihtub 地址: https://github.com/hua1995116/Fly-Three.js 最近元宇宙的概念很火,并且受到疫情的影响,我们的出行总是受限,电影院也总是关门,但是在 ...

  9. JavaWEB-02-MySQL高级

    内容 约束 多表关系 一对一 一对多 多对多 多表联查 ==多表联查== 事务 1. 约束 1.1 概念 限制,在数据库中是对某一列(多列)进行限制. 对表中的数据进行限定,保证正确性.有效性.完整性 ...

  10. display: table-cell里面文字打点的方法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...