模板方法模式-简单实现和Spring中的使用分析

设计模式-总览icon-default.png?t=LBL2https://mp.csdn.net/mp_blog/creation/editor/122202507目录

一、模板方法模式

1、父类定义骨架

2、子类实现特殊的处理

3、测试调用

4、执行结果

二、Spring中的模板方法模式分析

1、AbstractApplicationContext最重要的refresh方法使用模板方法模式实现

2、SpringJDBC使用模板方法模式实现


模板方法模式应该在项目中的使用会比较广泛,父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现

好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好

子类很多时候需要特殊处理的方法,需要覆盖父类方法,分为两种(这个在项目中非常的适用):

  1、抽象方法:父类中的是抽象方法,子类必须覆盖

  2、钩子方法:父类中是一个空方法,子类继承了默认也是空的

还是先来一个简单的例子看看

一、模板方法模式

来自《Head First设计模式》中的例子(具体可以参见:github地址),这个例子觉得还是举的非常恰当的,冲泡咖啡和茶的时候大致工序都差不多,只是某些实现的具体细节不同而已。

1、父类定义骨架

    父类中需要定义所有的模板方法,并且定义需要执行的流程。泡茶或者咖啡都需要有以下步骤:

1)、将水煮沸

2)、冲泡(需要子类实现),   因为茶或者咖啡温度等不太一样,比如茶一般需要先加少量水进行冲泡

3)、将水倒入杯中

4)、添加调味料(需要子类实现)   比如柠檬茶则还需要加入一些柠檬水或者柠檬

/**
 *   模板基类(咖啡或茶的冲泡步骤模板)
 *
 * @author lihongmin
 * @date 2018/9/3 22:33
 */
public abstract class CaffeineBeverage {

    private static final boolean DEFAULT_ADD_CONDIMENTS = true;

    /**
     *  不允许改变{咖啡或茶的冲泡}执行步骤
     */
    final void prepareRecipe() {

        boilWater();

        brew();

        pourInCup();

        /**
         *  设置一个回调钩子
         */
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    /**
     *  冲泡(需要子类实现)
     */
    abstract void brew();

    /**
     *  添加调味料(需要子类实现)
     */
    abstract void addCondiments();

    /**
     *  将水煮沸
     */
    void boilWater() {
        System.out.println("将水煮沸!");
    }

    /**
     *  将水倒入杯中
     */
    void pourInCup() {
        System.out.println("将沸腾的水倒入杯中!");
    }

    boolean customerWantsCondiments() {
        return DEFAULT_ADD_CONDIMENTS;
    }
}

2、子类实现特殊的处理

public class Coffee extends CaffeineBeverage {


    public Coffee() {
        super.prepareRecipe();
    }

    @Override
    public void brew() {

        System.out.println("咖啡需要直接泡!");
    }

    @Override
    public void addCondiments() {

        System.out.println("咖啡需要白开水等!");
    }
}
public class Tea extends CaffeineBeverage {
    
    public Tea() {
        super.prepareRecipe();
    }

    @Override
    public void brew() {

        System.out.println("泡茶需要将80度左右的水将茶叶泡开了!");
    }

    @Override
    public void addCondiments() {

        System.out.println("泡茶需要柠檬水等!");
    }

}

3、测试调用

public class TestTemplateMethod {

    public static void main(String[] args) {
        System.out.println("tea ......");
        Tea tea = new Tea();
        System.out.println("tea is OK !");

        System.out.println("---------------------------------------------");

        System.out.println("tea ......");
        Coffee coffee = new Coffee();
        System.out.println("tea is OK !");
    }
}

4、执行结果

tea ......
将水煮沸!
泡茶需要将80度左右的水将茶叶泡开了!
将沸腾的水倒入杯中!
泡茶需要柠檬水等!
tea is OK !
---------------------------------------------
coffee ......
将水煮沸!
咖啡需要直接泡!
将沸腾的水倒入杯中!
咖啡需要白开水等!
coffee is OK !

思考:其实项目里面可能用到模板的地方还是很多的,比如

1、商品系统,创建商品时(一个spu对应多个sku,并且spu和sku其实处理方式都差不多)。

2、订单系统,创建线上或者线下订单(大致流程差不多,具体实现有些不同,或者省去某些步骤)

例子还是很多的,只要有差不多的流程都可能用到模板方法模式。

二、Spring中的模板方法模式分析

1、AbstractApplicationContext最重要的refresh方法使用模板方法模式实现

Spring 的ApplicationContext的最重要的几个实现类型:ClassPathXmlApplicationContext、AnnotationConfigApplicationContext、AnnotationConfigWebApplicationContext等,初始化都调用父类AbstractApplicationContext的refresh()模板方法。

父类中定义了refresh的整个执行流程,但是我没想通为什么refresh方法没有加 final 处理。如下:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		// startupShutdownMonitor为刷新和销毁的公共锁对象
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			// 1、准备刷新的上下文,系统属性以及环境变量等
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 2、始化BeanFactory并进行XML文件的读取, ApplicationContext包含了XmlBeanFactory的所有过程,该方法就会包含其中的
			// 读取配置类型,根据Xml获取和解析bean的配置信息等
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 3、对BeanFactory的功能进行补充,都是我们进行扩展的点需要好好熟悉,强大的功能来自于此,我靠,太多了。。。
			// 空了再好好研究吧, Aware 的相关接口实现的初始化
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				// 4、子类覆盖方法做额外的处理,不实现则可以看到的是protected的空方法
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 5、激活各种beanFactory处理器 ,容器级别的配置, invokeBeanFactoryPostProcessors(beanFactory)
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 6、注册拦截Bean创建Bean的处理器,这里只是注册,真正的调用是getBean, 依赖于上一步的处理
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 7、为上下文初始化message源,国际化支持
				initMessageSource();

				// Initialize event multicaster for this context.
				// 8、初始化应用消息广播,并放入 applicationEventMulticaster 的Bean中
				// 监听,观察者模式。我们可以实现ApplicationListener,使用监听回调通知
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 9、留给子类初始化其他的Bean , 里面居然是空方法
				onRefresh();

				// Check for listener beans and register them.
				// 10、在所有注册的Bean中查找Listener的Bean, 注册到消息广播器中
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 11、初始化剩下的非惰性单实例, 厉害了,都在这里的最后一步
				// DefaultListableBeanFactory.preInstantiateSingletons方法
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 12、完成刷新过程,通知生命周期处理器,lifecycleProcessor 刷新过程,同时发出ContextRefreshEvent 通知别人
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				// 13、发生异常需要, 销毁已经创建的单例,以避免悬空资源
				destroyBeans();

				// Reset 'active' flag.
				// 14、重置 active 属性标识
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				// finally中需要,在Spring的内核中重置常见的内省缓存 可能再也不需要单例bean的元数据了……
				resetCommonCaches();
			}
		}
	}

分析: postProcessBeanFactory(beanFactory)就是放父类进行覆盖的 protected 空方法

ClassPathXmlApplicationContext类型的容器,没有对这一个步骤有特殊定制,直接调用父类AbstractApplicationContext的空方法

所有web类型的则会实现自己的方法,如 XmlWebApplicationContext 或者 AnnotationConfigWebApplicationContext 都继承自己父类 AbstractRefreshableWebApplicationContext 中的重写postProcessBeanFactory方法

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, 
            this.servletConfig));
        beanFactory.ignoreDependencyInterface(ServletContextAware.class);
        beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, 
            this.servletContext);
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, 
            this.servletContext, this.servletConfig);
    }
 

其中 prepareRefresh()、prepareBeanFactory()、onRefresh()、finishRefresh()步骤基本都使用AbstractApplicationContext中的方法,处理 ReactiveWebServerApplicationContextServletWebServerApplicationContext 会实现自己的上述方法

比如finishRefresh()方法,父类中实现为:

protected void finishRefresh() {
        this.clearResourceCaches();
        this.initLifecycleProcessor();
        this.getLifecycleProcessor().onRefresh();
        this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
        LiveBeansView.registerApplicationContext(this);
    }

ServletWebServerApplicationContext 中的实现为:

protected void finishRefresh() {
    super.finishRefresh();
    WebServer webServer = this.startWebServer();
    if (webServer != null) {
        this.publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}

2、SpringJDBC使用模板方法模式实现

我们可以直接看一下SpringTemplate中的query(final String sql, final ResultSetExtractor<T> rse)方法,过个重载方法都会执行到:

public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws 
DataAccessException {
        Assert.notNull(sql, "SQL must not be null");
        Assert.notNull(rse, "ResultSetExtractor must not be null");
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Executing SQL query [" + sql + "]");
        }

        class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
            QueryStatementCallback() {
            }

            @Nullable
            public T doInStatement(Statement stmt) throws SQLException {
                ResultSet rs = null;

                Object var3;
                try {
                    rs = stmt.executeQuery(sql);
                    var3 = rse.extractData(rs);
                } finally {
                    JdbcUtils.closeResultSet(rs);
                }

                return var3;
            }

            public String getSql() {
                return sql;
            }
        }

        return this.execute((StatementCallback)(new QueryStatementCallback()));
    }

这里先在预制回调函数doInPreparedStatement,

真正的查找在execute()中:

@Nullable
    public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) 
throws DataAccessException {
        Assert.notNull(psc, "PreparedStatementCreator must not be null");
        Assert.notNull(action, "Callback object must not be null");
        if (this.logger.isDebugEnabled()) {
            String sql = getSql(psc);
            this.logger.debug("Executing prepared SQL statement" + (sql != null ? " [" 
                + sql + "]" : ""));
        }
        // 1、获取链接
        Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
        PreparedStatement ps = null;

        Object var13;
        try {
            // 2、获取statement
            ps = psc.createPreparedStatement(con);
            // 3、获取resultset
            this.applyStatementSettings(ps);
            // 4、遍历resultset并封装成集合
            T result = action.doInPreparedStatement(ps);
            // 5、异常处理
            this.handleWarnings((Statement)ps);
            var13 = result;
        } catch (SQLException var10) {
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer)psc).cleanupParameters();
            }

            String sql = getSql(psc);
            psc = null;
            JdbcUtils.closeStatement(ps);
            ps = null;
            DataSourceUtils.releaseConnection(con, this.getDataSource());
            con = null;
            throw this.translateException("PreparedStatementCallback", sql, var10);
        } finally {
            // 6、依次关闭connection,statement等资源
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer)psc).cleanupParameters();
            }
            
            JdbcUtils.closeStatement(ps);
            DataSourceUtils.releaseConnection(con, this.getDataSource());
        }

        return var13;
    }

如上所述,模板方法流程大致与 jdbc一致,分别为:

// 1、获取链接
// 2、获取statement
// 3、获取resultset
// 4、遍历resultset并封装成集合
T result = action.doInPreparedStatement(ps);
// 5、异常处理

// 6、依次关闭connection,statement等资源

该模板执行流程中,除了第4步可能会发生变化,并且在该步骤回调了之前预制的回调方法,按照上面Spring AbstractApplicationContext中的,那么子类不同的处理需要各自重新自己的doInPreparedStatement()方法  也就是说我们可以写一个自己的 UserJdbcTemplate并重新改方法,但是Spring Jdbc实现的比较优雅,使用 RowCallbackHandler的时候又进行了一次钩子回调,我们再进行分析,如下:

我们的调用方式如下:

User user = new User();
        jdbcTemplate.query("select * from user where id = " + id, new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet resultSet) throws SQLException {
                user.setId(resultSet.getLong("id"));
                user.setName(resultSet.getString("name"));
            }
        });
        return user;

我们往下继续:

public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
        this.query((String)sql, (ResultSetExtractor)(new JdbcTemplate.RowCallbackHandlerResultSetExtractor(rch)));
    }

又回到上面的 SpringTemplate中的query(final String sql, final ResultSetExtractor<T> rse)方法,我们再看query里面的doInPreparedStatement回调方法发现其本身就是一个模板方法,

public T doInStatement(Statement stmt) throws SQLException {
    ResultSet rs = null;

    Object var3;
    try {
        // 1、执行查询
        rs = stmt.executeQuery(sql);
        // 2、执行额外的操作
        var3 = rse.extractData(rs);
    } finally {
        // 3、关闭ResultSet
        JdbcUtils.closeResultSet(rs);
    }
    return var3;
}

模板方法执行流程如下: 

// 1、执行查询
// 2、执行额外的操作
// 3、关闭ResultSet

其中1和2步骤都可能存在变化,比如Hikari数据库连接池的 HikariProxyCallableStatement 就重写了第一步,而就在第二步回调了预制的 我们写的代码的回调函数 processRow

总结:Spring JdbcTemplate的query方法总体结构是一个模板方法 + 回调函数,query方法中调用的execute(StatementCallback<T> action) 是一个模板方法,而预制的回调 doInStatement(Statement var1) 方法也是一个模板方法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值