这是 Spring 面试题系列的第二篇 【Spring 中的设计模式】 下半部分,

本篇只回答一个问题:

Spring 中使用了哪些设计模式?分别都是如何实现的?

回顾一下上一篇的概述,从整体上讲,SpringFramework 中使用了 11 种设计模式:

  • 单例模式+原型模式
  • 工厂模式
  • 代理模式
  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 适配器模式
  • 装饰者模式
  • 外观模式
  • 委派模式(不属于GoF23)

这一篇咱把下面的 6 个设计模式也详细解析一下。

喜欢本文的小伙伴不要忘记点赞呀 ~ ~ ~ 三克油!!!

模板方法模式

模板方法模式,在 SpringFramework 中使用的那是相当的多啊!即便是初学 SpringFramework ,在学习到 jdbc 部分,也应该接触过一个模板方法模式的体现,就是 JdbcTemplate 的回调机制:(贴两个比较常见的经典方法)

1
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
2
    return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
3
}
4
5
public List<Map<String, Object>> queryForList(String sql) throws DataAccessException {
6
    return query(sql, getColumnMapRowMapper());
7
}

在调用时,只需要一句话就够了:

1
List<Map<String, Object>> datas = jdbcTemplate.queryForList("select * from user");

小伙伴们可能会一脸懵,这么个方法就体现模板方法模式了吗?我也看不出来啊?莫慌,下面咱先回顾下原生 jdbc 的操作步骤,慢慢自然就明白了。

原生jdbc使用步骤

原生的 jdbc 操作需要以下这么多步骤:

1
// 注册驱动
2
Class.forName("com.mysql.jdbc.Driver");
3
// 获取连接
4
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
5
// 编写SQL获取预处理对象
6
PreparedStatement statement = connection.prepareStatement("select * from user");
7
// 执行SQL获取结果集
8
ResultSet resultSet = statement.executeQuery();
9
// 封装结果数据
10
List<Map<String, Object>> datas = new ArrayList<>();
11
while (resultSet.next()) {
12
    Map<String, Object> data = new HashMap<>();
13
    data.put("id", resultSet.getString("id"));
14
    data.put("name", resultSet.getString("name"));
15
    datas.add(data);
16
}
17
// 关闭连接
18
resultSet.close();
19
statement.close();
20
connection.close();
21
// 返回/输出数据
22
System.out.println(datas);

但是,这里面的操作有很多是重复的,所以这一段代码可以抽取为几个方法:

1
// 获取连接
2
public Connection getConnection() throws Exception {
3
    Class.forName("com.mysql.jdbc.Driver");
4
    return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
5
}
6
7
// 封装数据
8
public List<Map<String, Object>> encapsulate(ResultSet resultSet) throws SQLException {
9
    List<Map<String, Object>> datas = new ArrayList<>();
10
    while (resultSet.next()) {
11
        Map<String, Object> data = new HashMap<>();
12
        data.put("id", resultSet.getString("id"));
13
        data.put("name", resultSet.getString("name"));
14
        datas.add(data);
15
    }
16
    return datas;
17
}
18
19
// 关闭连接
20
public void close(Connection connection, PreparedStatement statement, ResultSet resultSet) throws SQLException {
21
    resultSet.close();
22
    statement.close();
23
    connection.close();
24
}

如此下来,主干代码就只剩下这些了:

1
public void query() throws Exception {
2
    Connection connection = getConnection();
3
    // 编写SQL获取预处理对象
4
    PreparedStatement statement = connection.prepareStatement("select * from user");
5
    // 执行SQL获取结果集
6
    ResultSet resultSet = statement.executeQuery();
7
    // 封装结果数据
8
    List<Map<String, Object>> datas = encapsulate(resultSet);
9
    close(connection, statement, resultSet);
10
    // 返回/输出数据
11
    System.out.println(datas);
12
}

那最后稍微调整一下,就可以成为一个通用的方法:

1
public List<Map<String, Object>> query(String sql, List<Object> params) throws Exception {
2
    Connection connection = getConnection();
3
    // 编写SQL获取预处理对象
4
    PreparedStatement statement = connection.prepareStatement(sql);
5
    // 填充占位符参数
6
    for (int i = 0; i < params.size(); i++) {
7
        statement.setObject(i + 1, params.get(i));
8
    }
9
    // 执行SQL获取结果集
10
    ResultSet resultSet = statement.executeQuery();
11
    // 封装结果数据
12
    List<Map<String, Object>> datas = encapsulate(resultSet);
13
    close(connection, statement, resultSet);
14
    // 返回数据
15
    return datas;
16
}

目前这个方法已经可以通用了,不过只能返回 Map ,如果想返回模型类对象怎么办呢?这样咱就可以扩展一个自定义结果集封装的方法,让调用者自己决定如何封装。再次改动的代码可以像如下优化:

1
public <T> List<T> query(String sql, List<Object> params, Function<ResultSet, List<T>> encapsulate) throws Exception {
2
    Connection connection = getConnection();
3
    // 编写SQL获取预处理对象
4
    PreparedStatement statement = connection.prepareStatement(sql);
5
    // 填充占位符参数
6
    for (int i = 0; i < params.size(); i++) {
7
        statement.setObject(i + 1, params.get(i));
8
    }
9
    // 执行SQL获取结果集
10
    ResultSet resultSet = statement.executeQuery();
11
    // 封装结果数据(借助Function接口实现结果集封装)
12
    List<T> datas = encapsulate.apply(resultSet);
13
    close(connection, statement, resultSet);
14
    // 返回数据
15
    return datas;
16
}

看最后一个参数,我让方法的调用者传一个 Function<ResultSet, List<T>> 类型的自定义处理逻辑,这样返回的数据究竟是什么类型我也就不用管了,完全由调用者自己决定。

到这里,模板方法模式就得以体现了,整个查询的动作我只需要让方法调用者传 SQL 、SQL 中可能出现的参数,以及自定义的结果集封装逻辑,其余的动作都不需要方法调用者关注。

可是,这跟 JdbcTemplate 又有什么关系呢???

JdbcTemplate中体现的模板方法

我上面不是摘出来一个 JdbcTemplate 的方法嘛:

1
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
2
    return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
3
}

咱点进去看 query 方法的实现:

1
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
2
    // assert logger ......
3
4
    class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
5
        @Override
6
        @Nullable
7
        public T doInStatement(Statement stmt) throws SQLException {
8
            ResultSet rs = null;
9
            try {
10
                rs = stmt.executeQuery(sql);
11
                return rse.extractData(rs);
12
            }
13
            finally {
14
                JdbcUtils.closeResultSet(rs);
15
            }
16
        }
17
        @Override
18
        public String getSql() {
19
            return sql;
20
        }
21
    }
22
23
    return execute(new QueryStatementCallback());
24
}

注意看方法参数列表的最后一个:ResultSetExtractor ,它是一个函数式接口,而且它只有一个方法,就是根据结果集返回泛型类型的数据

1
@FunctionalInterface
2
public interface ResultSetExtractor<T> {
3
	@Nullable
4
	T extractData(ResultSet rs) throws SQLException, DataAccessException;
5
}

是不是这个设计跟我上面演示的思路基本一致了?这就是 JdbcTemplate 中体现的模板方法模式。

除了这些 xxxTemplate 中有体现,其实在 SpringFramework 中的最底层 IOC 容器模型,BeanFactoryApplicationContext ,以及 SpringWebMvc 中的 HandlerMappingHandlerAdapter 等等,都有很多模板方法模式的体现。下面咱也举两个例子来体会。

ApplicationContext中体现的模板方法

说到 ApplicationContext ,最经典的方法莫过于 AbstractApplicationContext 中的 refresh :(只截取很少的一部分)

1
public void refresh() throws BeansException, IllegalStateException {
2
    synchronized (this.startupShutdownMonitor) {
3
        // ......
4
        try {
5
            // ......
6
            // Initialize other special beans in specific context subclasses.
7
            onRefresh();
8
            // ......
9
        } // catch finally ......
10
    }
11
}

在这个方法中有一个动作是 onRefresh 方法,点进去发现它是一个空方法:

1
/**
2
 * Template method which can be overridden to add context-specific refresh work.
3
 * Called on initialization of special beans, before instantiation of singletons.
4
 * <p>This implementation is empty.
5
 * @throws BeansException in case of errors
6
 * @see #refresh()
7
 */
8
protected void onRefresh() throws BeansException {
9
    // For subclasses: do nothing by default.
10
}

注意看文档注释的前两个单词:Template method !!!文档注释已经告诉咱这是一个模板方法了!而且方法体中也说了,它默认不做任何事情,留给子类扩展使用。由此咱就可以了解 ApplicationContext 中最经典的模板方法模式设计之一了。

HandlerMapping中体现的模板方法

DispatcherServlet 接收到客户端的请求,要进行实际处理时,需要先根据 uri 寻找能匹配的 HandlerMapping ,这一步它会委托 HandlerMapping 帮忙找(这里面涉及到委派了,下面会讲到),而这个寻找 HandlerMapping 的动作在 AbstractHandlerMappinggetHandler 方法中:

1
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
2
    Object handler = getHandlerInternal(request);
3
    if (handler == null) {
4
        handler = getDefaultHandler();
5
    }
6
    // ......
7
}

这里面第一句就会调另一个 getHandlerInternal 方法,而这个方法在 AbstractHandlerMapping 中是自己定义的抽象方法:

1
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

这就是非常经典的模板方法模式的体现了:父类 / 抽象类把逻辑步骤都定义出来,具体的动作让子类实现

小结

SpringFramework 中体现的模板方法模式非常多,包括各种 Template ( JdbcTemplateHibernateTemplateRedisTemplate 等),以及核心源码内部设计的模板方法(如 AbstractApplicationContext 中的 refreshonRefreshAbstractHandlerMapping 中的 getHandlergetHandlerInternal 等),它设计的核心思想是父类 / 抽象类把逻辑步骤都定义出来,具体的动作让子类实现。

观察者模式

观察者模式,也被称为发布订阅模式,其实它还有一个叫法:监听器模式。那说到监听器,是不是就立马想起来 SpringFramework 中的 ApplicationListener 了?对了,就这么回事,其实都不用展开讲,小伙伴们也能答得出来了。不过,想回答的漂亮,还需要额外知道一点点东西。

观察者模式的核心

咱都知道,观察者模式的两大核心是:观察者、被观察主题。对应到 SpringFramework 中的概念分别是:事件、监听器

不过我个人比较喜欢把 SpringFramework 的事件驱动核心概念划分为 4 个:事件源、事件、广播器、监听器

  • 事件源:发布事件的对象
  • 事件:事件源发布的信息
  • 广播器:事件真正广播给监听器的对象【即 ApplicationContext
    • ApplicationContext 接口有实现 ApplicationEventPublisher 接口,具备事件广播器的发布事件的能力
  • 监听器:监听事件的对象

在 SpringFramework 中,事件源想要发布事件,需要注入事件广播器,通过事件广播器来广播事件

SpringFramework使用监听器

以下是一个最简单的监听器使用实例:

事件模型
1
public class ServiceEvent extends ApplicationEvent {
2
    
3
    public ServiceEvent(Object source) {
4
        super(source);
5
    }
6
}
事件源
1
@Service
2
public class EventService implements ApplicationContextAware {
3
    
4
    private ApplicationContext ctx;
5
    
6
    public void publish() {
7
        ctx.publishEvent(new ServiceEvent(this));
8
    }
9
    
10
    @Override
11
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
12
        this.ctx = ctx;
13
    }
14
}
监听器
1
@Component
2
public class MyListener implements ApplicationListener<ServiceEvent> {
3
    
4
    @Override
5
    public void onApplicationEvent(ServiceEvent event) {
6
        System.out.println("监听到ServiceEvent事件:" + event);
7
    }
8
}

小结

SpringFramework 中实现的观察者模式就是事件驱动机制,可以通过自定义监听器,监听不同的事件,完成不同的业务处理。

适配器模式

了解 SpringWebMvc 中 DispatcherServlet 的工作原理,应该对 HandlerAdapter 不陌生,它就是辅助执行 Controller 中具体方法的适配器。如果只答这一句当然够用,不过回答的再细致点,那自然是更好的。

DispatcherServlet委派HandlerAdapter

DispatcherServletdoDispatch 方法中,有一句代码是根据 HandlerMapping 中的 Handler 获取 HandlerAdapter

1
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

这个 Handler 就是目标 Controller 中的目标方法,在适配的过程中它会寻找现有的所有 HandlerAdapter 是否能支持执行这个 Handler ,根据 RequestMappingHandlerMapping 的命名,自然也能猜得出来,真正负责执行目标 Controller 中方法的是 RequestMappingHandlerAdapter

HandlerAdapter执行Handler

RequestMappingHandlerAdapter 中,执行目标 Controller 中方法的核心方法是 handle

1
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
2
        throws Exception {
3
    return handleInternal(request, response, (HandlerMethod) handler);
4
}

看到了 handleInternal ,是不是又想到了模板方法?没错,就是这个设计。

在底层,核心的执行 Handler 的逻辑其实就是反射

1
protected Object doInvoke(Object... args) throws Exception {
2
    ReflectionUtils.makeAccessible(getBridgedMethod());
3
    try {
4
        // method.invoke
5
        return getBridgedMethod().invoke(getBean(), args);
6
    } // catch ......
7
}

由此可知,HandlerAdapter 的执行其实也没有很神秘,最终还是玩的反射而已。

小结

SpringFramework 中的适配器模式,体现之一是 HandlerAdapter 辅助执行目标 Controller 中的方法,底层是借助反射执行。

装饰者模式

装饰者跟代理、适配器都有些相似,不过装饰者更强调的是在原有的基础上附加额外的动作 / 方法 / 特性,而代理模式控制内部对象访问的意思。

SpringFramework 中体现的装饰者模式,在核心源码中并没有体现,不过在缓存模块 spring-cache 中倒是有一个体现:TransactionAwareCacheDecorator

Bean的包装

了解过 Bean 的创建流程,应该知道最底层的创建动作发生在 AbstractAutowireCapableBeanFactorydoCreateBean 中,而这里面第一波动作就把 BeanWrapper 都创建好了:

1
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
2
        throws BeanCreationException {
3
    // Instantiate the bean.
4
    BeanWrapper instanceWrapper = null;
5
    // ......
6
    if (instanceWrapper == null) {
7
        instanceWrapper = createBeanInstance(beanName, mbd, args);
8
    }

注意看,它创建的动作是 createBeanInstance ,意为“创建 bean 实例”,那就可以理解为:bean 对象已经在这个动作下实例化好了。深入这个方法,最终可以在 createBeanInstance 方法的最后一句 return 中看到 bean 实例的创建,以及 BeanWrapper 的包装:

1
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
2
    // ......
3
    return instantiateBean(beanName, mbd);
4
}
5
6
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
7
    try {
8
        Object beanInstance;
9
        // 实例化对象......
10
        BeanWrapper bw = new BeanWrapperImpl(beanInstance);
11
        initBeanWrapper(bw);
12
        return bw;
13
    } // catch ......
14
}

看到下面的代码中,它把 bean 的实例对象包装为一个 BeanWrapper ,形成装饰者。

SpringCache的核心设计

SpringCache 主做的是应用业务层级的缓存,它与 JSR107 有一定关系但又不同(Spring 自己实现了一套缓存,就是这个 SpringCache )。

用一张简单的图解释 SpringCache 的核心,大概可以这样理解:

一个 CacheManager 中包含多个 Cache ,一个 Cache 可以简单理解为一个 Map<String, T>

Cache 接口的设计,也能看出来就是 Map 的思路:(核心方法)

1
@Nullable
2
ValueWrapper get(Object key);
3
4
void put(Object key, @Nullable Object value);

Cache的装饰者体现

TransactionAwareCacheDecorator 作为装饰者,那肯定要实现 Cache 接口,并且组合一个 Cache 的对象:

1
public class TransactionAwareCacheDecorator implements Cache {
2
3
	private final Cache targetCache;

这里面,真正体现装饰者的位置是在 put 方法中:

1
public void put(final Object key, @Nullable final Object value) {
2
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
3
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
4
            @Override
5
            public void afterCommit() {
6
                TransactionAwareCacheDecorator.this.targetCache.put(key, value);
7
            }
8
        });
9
    }
10
    else {
11
        this.targetCache.put(key, value);
12
    }
13
}

可以发现,它会在 put 之前,检查当前线程中是否存在已经开启的事务:如果存在事务,则会将 put 操作注册到事务结束后

小结

SpringFramework 中实现的装饰者模式,体现之一是在 SpringCache 缓存的存放逻辑中,如果执行缓存时的线程中存在事务,则缓存的保存会在事务结束后再执行。

外观模式

外观模式算是比较简单的模式,它强调的是统一入口,内部组合。一个经典的体现是在 IOC 解析 Bean 的定义信息时使用的 BeanDefinitionLoader

BeanDefinitionLoader 中,它组合了 4 种不同类型的解析器:

1
class BeanDefinitionLoader {
2
    // 注解式Bean定义解析器
3
	private final AnnotatedBeanDefinitionReader annotatedReader;
4
    // xml文件的Bean定义解析器
5
	private final XmlBeanDefinitionReader xmlReader;
6
    // Groovy的Bean定义解析器
7
	private BeanDefinitionReader groovyReader;
8
    // 类路径的Bean定义扫描器
9
	private final ClassPathBeanDefinitionScanner scanner;

尽管组合的解析器很多,但最终暴露出来的方法是同样的名:load

1
private int load(Class<?> source) { /* ... */ }
2
private int load(GroovyBeanDefinitionSource source) { /* ... */ }
3
private int load(Resource source) { /* ... */ }
4
private int load(Package source) { /* ... */ }
5
private int load(CharSequence source) { /* ... */ }

由此可以体现出很经典的外观模式。

委派模式

委派模式本不属于 GoF23 中的设计模式,不过既然咱在前面也反复提到过几次,这里咱还是摘出来说两句。

DispatcherServlet 的核心工作流程中,委派模式体现在 HandlerMappingHandlerAdapterViewResolver 接受 DispatcherServlet 的委派,如下面的整体流程图:

到这里,SpringFramework 中使用的设计模式就差不多整理完了,想要回答的尽可能全面、准确、有深度,小伙伴们要好好理解这些模式的设计、思想,以及 SpringFramework 中的源码实现,这样在被面试官问到才会更加游刃有余。

【都看到这里了,小伙伴们要不要关注点赞一下呀,有源码学习需要的可以看我小册哦,学习起来 ~ 奥利给】