原来Spring源码中的模板方法设计模式可以这么简单!GET到了!
Posted 非科班程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原来Spring源码中的模板方法设计模式可以这么简单!GET到了!相关的知识,希望对你有一定的参考价值。
坚持&原创 丨 作者 /JohnLeung
这是非科班程序员分享的第10篇原创文章
以前经常被问到:你知道哪些设计模式?比如模板方法模式在Spring源码中哪里用到了?
我:这个.......
当然不能就此放弃治疗,毕竟研究框架源码,设计模式这关迟早是要过,那我们就从“类行为型模式”里的模板方法模式(Template Method Pattern)开始讲起吧。
如果我们想设计一个制作水果饮料的流程,我们希望将其先后定义为以下几步(这里只是简单举例哈):
加水:必须步骤
加糖:可选步骤
加水果:留给子类加具体某种水果
那么我们将这个制作流程分析如下:
父类定义好制作主体流程:加水、加糖、加水果;
为了实现可扩展性,加水果这一步,我们希望子类可以自己选择加哪种水果(苹果汁加苹果,橘子汁加橘子等),即子类必须覆盖实现;
加糖这一步希望由子类决定是否需要这一步,即子类根据实际情况选择性覆盖;
最后,我们可能需要下面这样的父类:
/**
* 制作水果饮料
*
* @author JohnLeung
* @date 2020/12/19
*/
public abstract class Juice {
/**
* 制作果汁总体步骤
* (这就是一个模板方法)
*/
public void makingJuice() {
System.out.println("开始制作果汁...");
// 1. 加水(必须)
addWater();
// 2. 加糖(子类可选)
addSugar();
// 3. 加水果(子类必须覆盖)
addFruit();
System.out.println("果汁制作完成!");
}
/**
* 加水步骤,必须步骤(由父类实现)
*/
private void addWater() {
System.out.println("加水...");
}
/**
* 加糖步骤,父类什么也步骤,子类可选择覆盖此方法添加特定行为
* (这是一个钩子方法)
*/
protected void addSugar() {
// do nothing
}
/**
* 加水果步骤,留给子类自己覆盖实现
*/
protected abstract void addFruit();
}
此时,如果我们想实现一个制作苹果汁的类(不加糖),再实现一个制作橘子汁的类(加糖),那么这个子类可能是下面这样的:
// 子类:制作苹果汁
public class AppleJuice extends Juice{
public void addFruit() {
System.out.println("添加苹果...");
}
}
// 子类:制作橘子汁
public class OrangeJuice extends Juice{
/**
* 添加具体水果
*/
public void addFruit() {
System.out.println("添加橘子...");
}
/**
* 添加糖,可选步骤
*/
public void addSugar() {
System.out.println("橘子饮料,想加糖...");
}
}
我想大家应该很快就明白了,从这个样例中,我们其实就用到了模板方法模式。
模板模式的目的很简单:我们定义了一个算法流程,一方面希望所有的子类都遵守这个主体流程,另一方面也希望有些流程不要定死,而是留个子类具体实现。
模板方法:定义算法流程(父类中,如上述的
makingJuice
方法);通用方法:被模板方法调用,所有父类子类都会执行的方法,一般不能被扩展,如上述样例的
addWater
方法;钩子方法:被模板方法调用,父类会实现该方法,一般是个空方法,子类可以决定是否覆盖添加新的行为,如上述样例的
addSugar
方法;抽象方法:被模板方法调用,在父类中是个抽象方法,留个子类具体实现,如上述样例的
addFruit
方法;
2.2 UML图示
用上述的样例,画个UML可能更好懂一些,理清父类和子类关系和上述的几个概念:
在之前几篇分析SpringBoot的文章(见文末链接)中,我们其实也讲到过,Spring Bean的初始化过程中,主要是org.springframework.context.support.AbstractApplicationContext#refresh
这个方法实现的。
其实refresh方法就是一个模板方法: 该方法里定义了一系列Bean初始化应该有的步骤,而把有些步骤留个了子类实现(抽象方法),有些步骤子类可选择性扩展(钩子方法)。
我们来简单看看这个方法的源码(本次只简单讨论其模版方法设计,不讨论其他具体实现细节,关注①②③④处即可):
/**
* AbstractApplicationContext#refresh()
* ① Bean 初始化核心方法(这是一个模板方法)
*
*/
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
prepareRefresh();
// 注意:obtainFreshBeanFactory 方法内部调用了两个抽象方法 refreshBeanFactory() 和 getBeanFactory()
// ② 调用抽象方法(延迟到子类实现):将这两个方法的具体实现留给了子类控制
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
prepareBeanFactory(beanFactory);
try {
// Bean 转成 BeanDefinition 后,初始化前
// ③ 钩子方法:子类可以选择覆盖扩展,添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
postProcessBeanFactory(beanFactory);
// 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 BeanPostProcessor 的实现类
registerBeanPostProcessors(beanFactory);
// 初始化当前 ApplicationContext 的 MessageSource
initMessageSource();
// 初始化当前 ApplicationContext 的事件广播器
initApplicationEventMulticaster();
// 具体的子类可以在这里初始化一些特殊的 Bean
// ④ 钩子方法:子类可以选择性覆盖实现,初始化一些特殊的 Bean
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
// ... 略
}
而模板方法refresh()中涉及的其他方法(如抽象方法、钩子方法)如下:
// AbstractApplicationContext#refresh()中的 obtainFreshBeanFactory()方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
// AbstractApplicationContext#refreshBeanFactory 抽象方法,子类必须覆盖
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
// AbstractApplicationContext#getBeanFactory 抽象方法,子类必须覆盖
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
// AbstractApplicationContext#postProcessBeanFactory 空方法,留给子类选择性覆盖
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
// AbstractApplicationContext#onRefresh 空方法,留给子类选择性覆盖
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
我们看到:
① refresh()
: 是一个模板方法,定义了一个Bean初始化流程的整体过程;② obtainFreshBeanFactory()
: 调用了两个抽象方法(refreshBeanFactory
和getBeanFactory
),使子类可以通过扩展这两个方法的方式,自定义子类的具体行为;③ postProcessBeanFactory(beanFactory)
和④ onRefresh()
: 是一个钩子方法,父类中是一个空方法,留个子类选择是否扩展实现;
怎么样?模板方法思想其实很简单吧。我们简单总结一下:
什么时候使用:当需要定义一个流程,其中某些步骤想延迟到子类自己实现时
需要怎么做:延迟实现的方法定义为抽象方法(这样父类也就成为了抽象类),子类可选择性扩展的方法,父类就实现为空方法
这样以后再遇到这样的问题,我们就能答上一二了。当然,还有其他设计模式,我们后面再慢慢安排上吧。还有,设计模式还是要多用才能融会贯通哦!
(本篇完)
往期原创精选
非科班程序员(noncoder.cn)
与你一起成长
扫描上方二维码,一起报团取暖!!!
客官,点个赞或者在看呗
以上是关于原来Spring源码中的模板方法设计模式可以这么简单!GET到了!的主要内容,如果未能解决你的问题,请参考以下文章
建议收藏 | SpringBoot 元数据配置原来可以这么拓展!