Spring中使用的设计模式,你都能说全吗?[下]
这是 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 | |
6 | |
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 | |
18 | public String getSql() { |
19 | return sql; |
20 | } |
21 | } |
22 | |
23 | return execute(new QueryStatementCallback()); |
24 | } |
注意看方法参数列表的最后一个:ResultSetExtractor
,它是一个函数式接口,而且它只有一个方法,就是根据结果集返回泛型类型的数据:
1 |
|
2 | public interface ResultSetExtractor<T> { |
3 | |
4 | T extractData(ResultSet rs) throws SQLException, DataAccessException; |
5 | } |
是不是这个设计跟我上面演示的思路基本一致了?这就是 JdbcTemplate
中体现的模板方法模式。
除了这些 xxxTemplate
中有体现,其实在 SpringFramework 中的最底层 IOC 容器模型,BeanFactory
和 ApplicationContext
,以及 SpringWebMvc 中的 HandlerMapping
、HandlerAdapter
等等,都有很多模板方法模式的体现。下面咱也举两个例子来体会。
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
的动作在 AbstractHandlerMapping
的 getHandler
方法中:
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 ( JdbcTemplate
、HibernateTemplate
、RedisTemplate
等),以及核心源码内部设计的模板方法(如 AbstractApplicationContext
中的 refresh
套 onRefresh
、AbstractHandlerMapping
中的 getHandler
套 getHandlerInternal
等),它设计的核心思想是父类 / 抽象类把逻辑步骤都定义出来,具体的动作让子类实现。
观察者模式
观察者模式,也被称为发布订阅模式,其实它还有一个叫法:监听器模式。那说到监听器,是不是就立马想起来 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 |
|
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 | |
11 | public void setApplicationContext(ApplicationContext ctx) throws BeansException { |
12 | this.ctx = ctx; |
13 | } |
14 | } |
监听器
1 |
|
2 | public class MyListener implements ApplicationListener<ServiceEvent> { |
3 | |
4 | |
5 | public void onApplicationEvent(ServiceEvent event) { |
6 | System.out.println("监听到ServiceEvent事件:" + event); |
7 | } |
8 | } |
小结
SpringFramework 中实现的观察者模式就是事件驱动机制,可以通过自定义监听器,监听不同的事件,完成不同的业务处理。
适配器模式
了解 SpringWebMvc 中 DispatcherServlet
的工作原理,应该对 HandlerAdapter
不陌生,它就是辅助执行 Controller 中具体方法的适配器。如果只答这一句当然够用,不过回答的再细致点,那自然是更好的。
DispatcherServlet委派HandlerAdapter
在 DispatcherServlet
的 doDispatch
方法中,有一句代码是根据 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 的创建流程,应该知道最底层的创建动作发生在 AbstractAutowireCapableBeanFactory
的 doCreateBean
中,而这里面第一波动作就把 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 |
|
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 | |
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
的核心工作流程中,委派模式体现在 HandlerMapping
、HandlerAdapter
、ViewResolver
接受 DispatcherServlet
的委派,如下面的整体流程图:
到这里,SpringFramework 中使用的设计模式就差不多整理完了,想要回答的尽可能全面、准确、有深度,小伙伴们要好好理解这些模式的设计、思想,以及 SpringFramework 中的源码实现,这样在被面试官问到才会更加游刃有余。
【都看到这里了,小伙伴们要不要关注点赞一下呀,有源码学习需要的可以看我小册哦,学习起来 ~ 奥利给】
原文作者: LinkedBear
原文链接: http://yoursite.com/2020/06/13/Spring中使用的设计模式,你都能说全吗?[下]/
版权声明: 转载请注明出处(必须保留作者署名及链接)