原来Spring源码中的模板方法设计模式可以这么简单!GET到了!

Posted 非科班程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原来Spring源码中的模板方法设计模式可以这么简单!GET到了!相关的知识,希望对你有一定的参考价值。



坚持&原创 丨 作者 /JohnLeung

这是非科班程序员分享的第10篇原创文章


以前经常被问到:你知道哪些设计模式?比如模板方法模式在Spring源码中哪里用到了?

我:这个.......


当然不能就此放弃治疗,毕竟研究框架源码,设计模式这关迟早是要过,那我们就从“类行为型模式”里的模板方法模式(Template Method Pattern)开始讲起吧。


1. 先讲个栗子



如果我们想设计一个制作水果饮料的流程,我们希望将其先后定义为以下几步(这里只是简单举例哈):


  1. 加水:必须步骤

  2. 加糖:可选步骤

  3. 加水果:留给子类加具体某种水果


那么我们将这个制作流程分析如下:

  • 父类定义好制作主体流程:加水、加糖、加水果;

  • 为了实现可扩展性,加水果这一步,我们希望子类可以自己选择加哪种水果(苹果汁加苹果,橘子汁加橘子等),即子类必须覆盖实现;

  • 加糖这一步希望由子类决定是否需要这一步,即子类根据实际情况选择性覆盖;


最后,我们可能需要下面这样的父类

/** * 制作水果饮料 * * @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{ @Override public void addFruit() { System.out.println("添加苹果..."); }}
// 子类:制作橘子汁public class OrangeJuice extends Juice{ /** * 添加具体水果 */ @Override public void addFruit() { System.out.println("添加橘子..."); }
/** * 添加糖,可选步骤 */ @Override public void addSugar() { System.out.println("橘子饮料,想加糖..."); }}

我想大家应该很快就明白了,从这个样例中,我们其实就用到了模板方法模式。

模板模式的目的很简单:我们定义了一个算法流程,一方面希望所有的子类都遵守这个主体流程,另一方面也希望有些流程不要定死,而是留个子类具体实现


2. 说说模板方法模式


2.1 几个概念

  • 模板方法:定义算法流程(父类中,如上述的makingJuice方法);

  • 通用方法:被模板方法调用,所有父类子类都会执行的方法,一般不能被扩展,如上述样例的addWater方法;

  • 钩子方法:被模板方法调用,父类会实现该方法,一般是个空方法,子类可以决定是否覆盖添加新的行为,如上述样例的addSugar方法;

  • 抽象方法:被模板方法调用,在父类中是个抽象方法,留个子类具体实现,如上述样例的addFruit方法;


2.2 UML图示



用上述的样例,画个UML可能更好懂一些,理清父类和子类关系和上述的几个概念:



3. Spring源码中的模板方法模式(举栗)


在之前几篇分析SpringBoot的文章(见文末链接)中,我们其实也讲到过,Spring Bean的初始化过程中,主要是org.springframework.context.support.AbstractApplicationContext#refresh这个方法实现的。

其实refresh方法就是一个模板方法: 该方法里定义了一系列Bean初始化应该有的步骤,而把有些步骤留个了子类实现(抽象方法),有些步骤子类可选择性扩展(钩子方法)。

我们来简单看看这个方法的源码(本次只简单讨论其模版方法设计,不讨论其他具体实现细节,关注①②③④处即可):


/** * AbstractApplicationContext#refresh() * ① Bean 初始化核心方法(这是一个模板方法) * */ @Override 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(): 调用了两个抽象方法(refreshBeanFactorygetBeanFactory),使子类可以通过扩展这两个方法的方式,自定义子类的具体行为;

  • ③ postProcessBeanFactory(beanFactory)④ onRefresh(): 是一个钩子方法,父类中是一个空方法,留个子类选择是否扩展实现;


怎么样?模板方法思想其实很简单吧。我们简单总结一下:

  • 什么时候使用:当需要定义一个流程,其中某些步骤想延迟到子类自己实现时

  • 需要怎么做:延迟实现的方法定义为抽象方法(这样父类也就成为了抽象类),子类可选择性扩展的方法,父类就实现为空方法


这样以后再遇到这样的问题,我们就能答上一二了原来Spring源码中的模板方法设计模式可以这么简单!GET到了!。当然,还有其他设计模式,我们后面再慢慢安排上吧。还有,设计模还是要多用才能融会贯通哦!

(本篇完)



原来Spring源码中的模板方法设计模式可以这么简单!GET到了!



往期原创精选






非科班程序员(noncoder.cn)

与你一起成长

扫描上方二维码,一起报团取暖!!!

客官,点个或者在看

以上是关于原来Spring源码中的模板方法设计模式可以这么简单!GET到了!的主要内容,如果未能解决你的问题,请参考以下文章

建议收藏 | SpringBoot 元数据配置原来可以这么拓展!

Spring Security源码:设计模式在框架中的应用

设计模式----模板方法模式

Spring中的设计模式:模板模式

Spring 之 BeanUtils.copyProperties(...) 源码简读

Spring Boot : Mybatis极简配置