设计模式-总览
一、模板方法模式
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
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中的方法,处理 ReactiveWebServerApplicationContext 和 ServletWebServerApplicationContext
比如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) 方法也是一个模板方法