Spring中使用的设计模式,你都能说全吗?[下]
Posted javatiange
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring中使用的设计模式,你都能说全吗?[下]相关的知识,希望对你有一定的参考价值。
这是 Spring 面试题系列的第二篇 【Spring 中的设计模式】 下半部分,
本篇只回答一个问题:
Spring 中使用了哪些设计模式?分别都是如何实现的?
回顾一下上一篇的概述,从整体上讲,SpringFramework 中使用了 11 种设计模式:
- 单例模式+原型模式
- 工厂模式
- 代理模式
- 策略模式
- 模板方法模式
- 观察者模式
- 适配器模式
- 装饰者模式
- 外观模式
- 委派模式(不属于GoF23)
这一篇咱把下面的 6 个设计模式也详细解析一下。
喜欢本文的小伙伴不要忘记三连呀 ~ ~ ~ 三克油!!!
模板方法模式
模板方法模式,在 SpringFramework 中使用的那是相当的多啊!即便是初学 SpringFramework ,在学习到 jdbc 部分,也应该接触过一个模板方法模式的体现,就是 JdbcTemplate
的回调机制:(贴两个比较常见的经典方法)
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
public List<Map<String, Object>> queryForList(String sql) throws DataAccessException {
return query(sql, getColumnMapRowMapper());
}
在调用时,只需要一句话就够了:
List<Map<String, Object>> datas = jdbcTemplate.queryForList("select * from user");
小伙伴们可能会一脸懵,这么个方法就体现模板方法模式了吗?我也看不出来啊?莫慌,下面咱先回顾下原生 jdbc 的操作步骤,慢慢自然就明白了。
原生jdbc使用步骤
原生的 jdbc 操作需要以下这么多步骤:
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
// 编写SQL获取预处理对象
PreparedStatement statement = connection.prepareStatement("select * from user");
// 执行SQL获取结果集
ResultSet resultSet = statement.executeQuery();
// 封装结果数据
List<Map<String, Object>> datas = new ArrayList<>();
while (resultSet.next()) {
Map<String, Object> data = new HashMap<>();
data.put("id", resultSet.getString("id"));
data.put("name", resultSet.getString("name"));
datas.add(data);
}
// 关闭连接
resultSet.close();
statement.close();
connection.close();
// 返回/输出数据
System.out.println(datas);
但是,这里面的操作有很多是重复的,所以这一段代码可以抽取为几个方法:
// 获取连接
public Connection getConnection() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
}
// 封装数据
public List<Map<String, Object>> encapsulate(ResultSet resultSet) throws SQLException {
List<Map<String, Object>> datas = new ArrayList<>();
while (resultSet.next()) {
Map<String, Object> data = new HashMap<>();
data.put("id", resultSet.getString("id"));
data.put("name", resultSet.getString("name"));
datas.add(data);
}
return datas;
}
// 关闭连接
public void close(Connection connection, PreparedStatement statement, ResultSet resultSet) throws SQLException {
resultSet.close();
statement.close();
connection.close();
}
如此下来,主干代码就只剩下这些了:
public void query() throws Exception {
Connection connection = getConnection();
// 编写SQL获取预处理对象
PreparedStatement statement = connection.prepareStatement("select * from user");
// 执行SQL获取结果集
ResultSet resultSet = statement.executeQuery();
// 封装结果数据
List<Map<String, Object>> datas = encapsulate(resultSet);
close(connection, statement, resultSet);
// 返回/输出数据
System.out.println(datas);
}
那最后稍微调整一下,就可以成为一个通用的方法:
public List<Map<String, Object>> query(String sql, List<Object> params) throws Exception {
Connection connection = getConnection();
// 编写SQL获取预处理对象
PreparedStatement statement = connection.prepareStatement(sql);
// 填充占位符参数
for (int i = 0; i < params.size(); i++) {
statement.setObject(i + 1, params.get(i));
}
// 执行SQL获取结果集
ResultSet resultSet = statement.executeQuery();
// 封装结果数据
List<Map<String, Object>> datas = encapsulate(resultSet);
close(connection, statement, resultSet);
// 返回数据
return datas;
}
目前这个方法已经可以通用了,不过只能返回 Map ,如果想返回模型类对象怎么办呢?这样咱就可以扩展一个自定义结果集封装的方法,让调用者自己决定如何封装。再次改动的代码可以像如下优化:
public <T> List<T> query(String sql, List<Object> params, Function<ResultSet, List<T>> encapsulate) throws Exception {
Connection connection = getConnection();
// 编写SQL获取预处理对象
PreparedStatement statement = connection.prepareStatement(sql);
// 填充占位符参数
for (int i = 0; i < params.size(); i++) {
statement.setObject(i + 1, params.get(i));
}
// 执行SQL获取结果集
ResultSet resultSet = statement.executeQuery();
// 封装结果数据(借助Function接口实现结果集封装)
List<T> datas = encapsulate.apply(resultSet);
close(connection, statement, resultSet);
// 返回数据
return datas;
}
看最后一个参数,我让方法的调用者传一个 Function<ResultSet, List<T>>
类型的自定义处理逻辑,这样返回的数据究竟是什么类型我也就不用管了,完全由调用者自己决定。
如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
到这里,模板方法模式就得以体现了,整个查询的动作我只需要让方法调用者传 SQL 、SQL 中可能出现的参数,以及自定义的结果集封装逻辑,其余的动作都不需要方法调用者关注。
可是,这跟 JdbcTemplate
又有什么关系呢???
JdbcTemplate中体现的模板方法
我上面不是摘出来一个 JdbcTemplate
的方法嘛:
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
咱点进去看 query
方法的实现:
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
// assert logger ......
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
注意看方法参数列表的最后一个:ResultSetExtractor
,它是一个函数式接口,而且它只有一个方法,就是根据结果集返回泛型类型的数据:
@FunctionalInterface
public interface ResultSetExtractor<T> {
@Nullable
T extractData(ResultSet rs) throws SQLException, DataAccessException;
}
是不是这个设计跟我上面演示的思路基本一致了?这就是 JdbcTemplate
中体现的模板方法模式。
除了这些 xxxTemplate
中有体现,其实在 SpringFramework 中的最底层 IOC 容器模型,BeanFactory
和 ApplicationContext
,以及 SpringWebMvc 中的 HandlerMapping
、HandlerAdapter
等等,都有很多模板方法模式的体现。下面咱也举两个例子来体会。
ApplicationContext中体现的模板方法
说到 ApplicationContext
,最经典的方法莫过于 AbstractApplicationContext
中的 refresh
:(只截取很少的一部分)
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// ......
try {
// ......
// Initialize other special beans in specific context subclasses.
onRefresh();
// ......
} // catch finally ......
}
}
在这个方法中有一个动作是 onRefresh
方法,点进去发现它是一个空方法:
/**
* Template method which can be overridden to add context-specific refresh work.
* Called on initialization of special beans, before instantiation of singletons.
* <p>This implementation is empty.
* @throws BeansException in case of errors
* @see #refresh()
*/
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
注意看文档注释的前两个单词:Template method !!!文档注释已经告诉咱这是一个模板方法了!而且方法体中也说了,**它默认不做任何事情,留给子类扩展使用。**由此咱就可以了解 ApplicationContext
中最经典的模板方法模式设计之一了。
HandlerMapping中体现的模板方法
在 DispatcherServlet
接收到客户端的请求,要进行实际处理时,需要先根据 uri 寻找能匹配的 HandlerMapping
,这一步它会委托 HandlerMapping
帮忙找(这里面涉及到委派了,下面会讲到),而这个寻找 HandlerMapping
的动作在 AbstractHandlerMapping
的 getHandler
方法中:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
// ......
}
这里面第一句就会调另一个 getHandlerInternal
方法,而这个方法在 AbstractHandlerMapping
中是自己定义的抽象方法:
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使用监听器
以下是一个最简单的监听器使用实例:
事件模型
public class ServiceEvent extends ApplicationEvent {
public ServiceEvent(Object source) {
super(source);
}
}
事件源
@Service
public class EventService implements ApplicationContextAware {
private ApplicationContext ctx;
public void publish() {
ctx.publishEvent(new ServiceEvent(this));
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
}
监听器
@Component
public class MyListener implements ApplicationListener<ServiceEvent> {
@Override
public void onApplicationEvent(ServiceEvent event) {
System.out.println("监听到ServiceEvent事件:" + event);
}
}
小结
SpringFramework 中实现的观察者模式就是事件驱动机制,可以通过自定义监听器,监听不同的事件,完成不同的业务处理。
适配器模式
了解 SpringWebMvc 中 DispatcherServlet
的工作原理,应该对 HandlerAdapter
不陌生,它就是辅助执行 Controller 中具体方法的适配器。如果只答这一句当然够用,不过回答的再细致点,那自然是更好的。
DispatcherServlet委派HandlerAdapter
在 DispatcherServlet
的 doDispatch
方法中,有一句代码是根据 HandlerMapping
中的 Handler
获取 HandlerAdapter
:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
这个 Handler 就是目标 Controller 中的目标方法,在适配的过程中它会寻找现有的所有 HandlerAdapter
是否能支持执行这个 Handler ,根据 RequestMappingHandlerMapping
的命名,自然也能猜得出来,真正负责执行目标 Controller 中方法的是 RequestMappingHandlerAdapter
。
HandlerAdapter执行Handler
在 RequestMappingHandlerAdapter
中,执行目标 Controller 中方法的核心方法是 handle
:
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
看到了 handleInternal
,是不是又想到了模板方法?没错,就是这个设计。
在底层,核心的执行 Handler 的逻辑其实就是反射:
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
// method.invoke
return getBridgedMethod().invoke(getBean(), args);
} // catch ......
}
由此可知,HandlerAdapter
的执行其实也没有很神秘,最终还是玩的反射而已。
小结
SpringFramework 中的适配器模式,体现之一是 HandlerAdapter
辅助执行目标 Controller 中的方法,底层是借助反射执行。
装饰者模式
装饰者跟代理、适配器都有些相似,不过装饰者更强调的是在原有的基础上附加额外的动作 / 方法 / 特性,而代理模式有控制内部对象访问的意思。
SpringFramework 中体现的装饰者模式,在核心源码中并没有体现,不过在缓存模块 spring-cache 中倒是有一个体现:TransactionAwareCacheDecorator
。
Bean的包装
了解过 Bean 的创建流程,应该知道最底层的创建动作发生在 AbstractAutowireCapableBeanFactory
的 doCreateBean
中,而这里面第一波动作就把 BeanWrapper
都创建好了:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
// ......
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
注意看,它创建的动作是 createBeanInstance
,意为“创建 bean 实例”,那就可以理解为:bean 对象已经在这个动作下实例化好了。深入这个方法,最终可以在 createBeanInstance
方法的最后一句 return 中看到 bean 实例的创建,以及 BeanWrapper
的包装:
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// ......
return instantiateBean(beanName, mbd);
}
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
try {
Object beanInstance;
// 实例化对象......
BeanWrapper bw = new BeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
return bw;
} // catch ......
}
看到下面的代码中,它把 bean 的实例对象包装为一个 BeanWrapper
,形成装饰者。
SpringCache的核心设计
SpringCache 主做的是应用业务层级的缓存,它与 JSR107 有一定关系但又不同(Spring 自己实现了一套缓存,就是这个 SpringCache )。
用一张简单的图解释 SpringCache 的核心,大概可以这样理解:
一个 CacheManager
中包含多个 Cache
,一个 Cache
可以简单理解为一个 Map<String, T> 。
从 Cache
接口的设计,也能看出来就是 Map
的思路:(核心方法)
@Nullable
ValueWrapper get(Object key);
void put(Object key, @Nullable Object value);
Cache的装饰者体现
TransactionAwareCacheDecorator
作为装饰者,那肯定要实现 Cache 接口,并且组合一个 Cache 的对象:
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
这里面,真正体现装饰者的位置是在 put
方法中:
public void put(final Object key, @Nullable final Object value) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
}
else {
this.targetCache.put(key, value);
}
}
可以发现,它会在 put 之前,检查当前线程中是否存在已经开启的事务:如果存在事务,则会将 put 操作注册到事务结束后。
小结
SpringFramework 中实现的装饰者模式,体现之一是在 SpringCache 缓存的存放逻辑中,如果执行缓存时的线程中存在事务,则缓存的保存会在事务结束后再执行。
外观模式
外观模式算是比较简单的模式,它强调的是统一入口,内部组合。一个经典的体现是在 IOC 解析 Bean 的定义信息时使用的 BeanDefinitionLoader
。
在 BeanDefinitionLoader
中,它组合了 4 种不同类型的解析器:
class BeanDefinitionLoader {
// 注解式Bean定义解析器
private final AnnotatedBeanDefinitionReader annotatedReader;
// xml文件的Bean定义解析器
private final XmlBeanDefinitionReader xmlReader;
// Groovy的Bean定义解析器
private BeanDefinitionReader groovyReader;
// 类路径的Bean定义扫描器
private final ClassPathBeanDefinitionScanner scanner;
尽管组合的解析器很多,但最终暴露出来的方法是同样的名:load
。
private int load(Class<?> source) { /* ... */ }
private int load(GroovyBeanDefinitionSource source) { /* ... */ }
private int load(Resource source) { /* ... */ }
private int load(Package source) { /* ... */ }
private int load(CharSequence source) { /* ... */ }
由此可以体现出很经典的外观模式。
委派模式
委派模式本不属于 GoF23 中的设计模式,不过既然咱在前面也反复提到过几次,这里咱还是摘出来说两句。
在 DispatcherServlet
的核心工作流程中,委派模式体现在 HandlerMapping
、HandlerAdapter
、ViewResolver
接受 DispatcherServlet
的委派,如下面的整体流程图:
到这里,SpringFramework 中使用的设计模式就差不多整理完了,想要回答的尽可能全面、准确、有深度,小伙伴们要好好理解这些模式的设计、思想,以及 SpringFramework 中的源码实现,这样在被面试官问到才会更加游刃有余。
最后
秃头哥给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。
码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!
以上是关于Spring中使用的设计模式,你都能说全吗?[下]的主要内容,如果未能解决你的问题,请参考以下文章
关于Spring 的IoC和AOP的面试题,快看看你都能答上来哪些