Spring作为Java世界当之无愧的No.1开源框架,其架构设计非常优秀,使用很多设计模式。
注:
- 本文参考Spring-6.1.5版本源码;
- 本文目录设置参考设计模式三大类来划分,Spring家族包括Spring、Spring MVC、Spring Boot、Spring Cloud、Spring Data等,总之Spring源码博大精深;
- 某一种设计模式会在若干个Spring接口、类、或模块里有所体现,不可能列举全部;
- 部分模式对应的小标题暂时置空,不代表Spring家族里没有这种设计模式的体现,后续大概率会补齐;
- 行文过长,会拆分几篇来讲。
创建型
简单工厂
又叫做静态工厂方法模式,不属于GoF23种设计模式之一,是编程中的一种惯用法。
实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。工厂类通常只有一个静态方法,根据不同的参数返回不同的产品实例。
优点:简单易用,适合产品数量少且变化不频繁的场景。
缺点:工厂类的职责过重,增加类的复杂度,一旦产品种类增多或产品变更,工厂类的代码将难以维护。
适用场景:当产品数量较少,且客户端只需知道工厂类即可创建产品时。
Spring中的BeanFactory就是一个例子。根据传入的一个唯一标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建,则根据具体情况来定。
Bean容器的启动阶段:
- 读取Bean的xml配置文件,将Bean元素分别转换成一个BeanDefinition对象;
- 通过BeanDefinitionRegistry将这些Bean注册并保存到BeanFactory中的一个ConcurrentHashMap中;
- BeanDefinition注册到BeanFactory后,Spring提供一个扩展的切口,允许通过实现接口BeanFactoryPostProcessor,在此时插入自定义的代码或行为。
PropertyPlaceholderConfigurer(Spring 5.2后废弃),在配置数据库的dataSource时使用到的占位符的值,就是它注入进去的。
容器中Bean的实例化阶段:
实例化阶段主要是通过反射或CGLIB对Bean进行实例化,此阶段Spring又暴露很多的扩展点:
- Aware:如BeanFactoryAware,对于实现这些Aware接口的Bean,在实例化Bean时Spring会注入对应的BeanFactory的实例;
- BeanPostProcessor:实现BeanPostProcessor接口的Bean,在实例化Bean时Spring会调用接口中的方法;
- InitializingBean:实现InitializingBean接口的Bean,在实例化Bean时Spring会调用接口中的方法;
- DisposableBean:实现BeanPostProcessor接口的Bean,在该Bean死亡时,Spring会调用接口中的方法。
工厂方法
FactoryMethod,工厂方法模式,定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类。
Spring里典型案例就是FactoryBean。实现FactoryBean接口的Bean是一类叫做factory的Bean,Spring会在使用getBean()调用获得该Bean时,会自动调用该Bean的getObject()方法,所以返回的不是factory这个Bean,而是bean.getOjbect()
方法的返回值。
抽象工厂
AbstractFactory,提供一个接口,用于创建一系列相关或依赖的对象,而无需指定它们的具体类。
优点:
- 能够确保客户端始终使用同一系列的对象,符合产品族的一致性
- 良好的扩展性,当需要增加新的产品族时,只需增加新的工厂类。
缺点:产品族扩展非常方便,但扩展单个产品的种类较为复杂,需要修改抽象工厂的接口。
应用场景:
- 当需要创建一系列相关或相互依赖的对象;
- 系统中存在多个产品族,而客户端只使用其中一个产品族。
对比
区别
- 简单工厂:一个工厂类负责生产不同的产品,通过传参决定返回哪一个产品实例;
- 工厂方法:每个具体工厂负责生产一个具体产品,通过不同的工厂创建不同的产品实例,符合开闭原则;
- 抽象工厂:不仅定义创建单个产品的方法,还定义一族相关产品的创建方法,能够创建一系列相关的产品。
单例
参考单例模式(Java)。
Spring依赖注入Bean实例默认是单例的。Spring的依赖注入,包括lazy-init
方式,都是发生在AbstractBeanFactory.getBean
方法里,getBean
方法调用doGetBean
方法,doGetBean
方法调用DefaultSingletonBeanRegistry.getSingleton
进行Bean的创建:
全静态方法实现的工具类和单例模式的工具类的区别
- 静态方法工具类是包含以下特征的静态方法的集合,它的静态方法是独立的,无任何外部依赖的。
- 单例模式工具类是系统的, 依赖外部资源或初始化的。
原型
Prototype
构造器
Builder,有好几个不同的翻译(叫法),构建器,建造者,建设者,构造方法,构造函数。是创建对象模式三剑客(工厂方法模式、构造器模式、原型模式)其一,用于简化复杂对象的构造。
构造器隐藏对象构造的复杂性,内部静态类支持链式方法的调用。参考Builder模式。
Spring里提供的BeanDefinitionBuilder就是一个例子,提供几个方法,为AbstractBeanDefinition抽象类的相关实现设置值,如作用域,工厂方法,属性等。
结构型
代理
Proxy,为其他对象提供一种代理以控制对这个对象的访问。
Proxy和Decorator
从结构上来看比较类似,但Proxy是控制,强调的是对功能的限制,而Decorator是增加职责。
Spring核心功能AoP基于代理,如JdkDynamicAopProxy,Cglib2AopProxy。
参考代理模式。
适配器
Adapter。参考设计模式之Decorator装饰者、Facade外观、Adapter适配器(Java)。
装饰者
Decorator
外观
Facade
桥接
Bridge
享元
Flyweight
组合
Composite,
行为型
模版方法
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点:封装性好、复用性好、屏蔽细节、便于维护。
缺点:继承只能是单个的,重构时会遇到困难
Spring源码中使用最多的设计模式应该就是模板方法,不过和GoF不是严格一样。参考Spring系列之模版方法汇总。
命令
Command,命令模式允许将请求封装在一个对象内并附加一个回调动作。请求被封装在命令对象之下,而请求的结果被发送到接收者。命令本身不是由调用者执行。
中介者
Mediator
责任链
观察者
Observer,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
Spring的事件机制(也叫事件驱动模型)使用的是观察者模式。ApplicationListener就是一个典型的案例。
事件机制包括三个部分(角色):
- 事件源:Event Source。比如点击按钮,则按钮就是事件源,要想使按钮对某些事件做出响应,就需要注册特定的Listener;
- 事件:Event Object,事件状态对象,用于Listener的相应方法中;
- 事件监听器:Event Listener:对每个明确的事件的发生,都相应地定义一个明确的方法。这些方法都集中定义在事件监听者EventListener接口中,这个接口要继承
java.util.EventListener
。实现事件监听者接口中一些或全部方法的类就是事件监听者。
具体到Spring里,ApplicationEvent抽象类是事件,继承自JDK的EventObject,所有事件都需要继承ApplicationEvent,且通过构造器参数source得到事件源。实现类ApplicationContextEvent表示ApplicaitonContext的容器事件。
ApplicationListener接口就是事件监听器,继承自JDK的EventListener,所有的监听器都要实现这个接口,接口的onApplicationEvent
方法接受一个ApplicationEvent或其子类对象作为参数,在方法体中可以通过对不同Event类的判断来进行相应的处理,当事件触发时所有监听器都会收到消息。
ApplicationContext接口是事件源,继承ApplicationEventPublisher接口,是Spring中的全局容器,即应用上下文。
状态
State。
定义:在不同的状态下,对同一行为有不同的响应。状态模式把对象的行为包装在不同的状态中,每一个状态的对象都有一个相同的抽象状态基类,并实现基类对应的方法。这样当一个对象的状态发生改变时,其行为也会随之改变。
使用场景:当一个对象的行为受其对应的状态的影响时。如:手机的飞行模式有开启和关闭两个状态,飞行模式关闭时可发短信打电话,飞行模式开启时,不能发短信打电话。
如在淘宝购物时,在用户未登录状态下只能进行商品的浏览,如果点击购买按钮,则会跳转到注册/登录界面;登陆后才可进行商品购买操作。
备忘录
Memento
访问者
Visitor
Spring提供的BeanDefinitionVisitor,用于解析Bean元数据并将其解析为 String(例如:具有作用域或工厂方法名称的XML属性)或 Object(例如构造函数定义中的参数)。已解析的值在与分析的Bean关联的BeanDefinition实例中进行判断设置。visitBeanDefinition方法源码:
迭代器
Iterator
策略
Strategy,定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换,使得算法可独立于客户端而变化。策略模式是指有一定行动内容的相对稳定的策略名称。
优点:
- 策略模式提供管理相关的算法族的办法。策略类的等级结构定义一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,避免重复的代码。
- 策略模式提供可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
- 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。即,策略模式只适用于客户端知道所有的算法或行为的情况。
- 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
Spring提供的InstantiationStrategy接口就使用策略模式:
InstantiationStrategy有两个实现类,SimpleInstantiationStrategy和CglibSubclassingInstantiationStrategy。
SimpleInstantiationStrategy里重写的instantiate方法则说明策略模式的具体使用情况:
另外,Spring的Resource接口提供强大的资源访问能力,Spring框架本身大量使用Resource接口来访问底层资源。
Resource 接口介绍
source 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。
Resource 接口源码如下:
主要提供如下几个方法:
- getInputStream:定位并打开资源,返回资源对应的输入流。每次调用都返回新的输入流。调用者须负责关闭输入流;
- exists:返回Resource所指向的资源是否存在;
- isOpen:返回资源文件是否打开,如果资源文件不能多次读取,每次读取结束应该显式关闭,以防止资源泄漏;
- getDescription:返回资源的描述信息,通常用于资源处理出错时输出该信息,通常是全限定文件名或实际URL;
- getFile:返回资源对应的File对象;
- getURL:返回资源对应的URL对象。
Resource接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring将会提供不同的Resource实现类,不同的实现类负责不同的资源访问逻辑:
- UrlResource:访问网络资源的实现类;
- ClassPathResource:访问类加载路径里资源的实现类;
- FileSystemResource:访问文件系统里资源的实现类;
- ServletContextResource:访问相对于ServletContext路径里的资源的实现类;
- InputStreamResource:访问输入流资源的实现类;
- ByteArrayResource:访问字节数组资源的实现类。
解释器
Interpreter,提供如何定义语言的文法,以及对语言句子的解释方法,包括表达式和评估器两部分。
SpEL,Spring Expression Language,可理解为一种解释器模式的体现。
参考