MyBatis 与 Spring 中的设计模式精讲:从源码到实践
设计模式 是软件工程的基石,各大主流框架(MyBatis、Spring)在架构设计中大量运用了经典模式。理解这些模式如何落地,不仅能提升源码阅读能力,更能帮助你在实际项目中写出更优雅、可扩展的代码。本文将从建造者、工厂、单例、代理、模板方法五个核心模式入手,深入剖析它们在 MyBatis 和 Spring 中的具体应用。
一、为什么框架钟爱设计模式?
一个好的框架需要满足:高内聚低耦合、可扩展、可复用、易维护。设计模式恰好提供了经过实践验证的解决方案:
- 建造者模式 ⇒ 解构复杂对象的创建过程
- 工厂模式 ⇒ 解耦产品创建与使用
- 单例模式 ⇒ 控制资源访问,减少内存开销
- 代理模式 ⇒ 无侵入式增强功能(AOP)
- 模板方法模式 ⇒ 抽取复用代码,允许子类定制
MyBatis 和 Spring 都深度运用了上述模式,下面逐一拆解。
二、MyBatis 中的设计模式
MyBatis 是一个优秀的持久层框架,其核心对象 SqlSession、Executor、StatementHandler 等的创建与协作都充满了设计模式的智慧。
2.1 建造者模式(Builder Pattern)
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
应用场景 :MyBatis 初始化过程中,需要解析 XML 配置文件、映射文件,构建一个庞大的 Configuration 对象。建造者模式将解析细节封装在各个 Builder 类中,使客户端(SqlSessionFactoryBuilder)无需关心内部构造逻辑。
核心类与流程
使用
构建
SqlSessionFactoryBuilder
+build(Reader reader) : SqlSessionFactory
+build(Configuration config) : SqlSessionFactory
XMLConfigBuilder
-Configuration configuration
+parse() : Configuration
Configuration
// 包含大量配置项
代码示例:
java
// 建造者模式入口
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactoryBuilder 内部会创建 XMLConfigBuilder 解析配置文件,最终返回 Configuration 对象,再将该对象传递给 DefaultSqlSessionFactory 的构造器。构建过程与表示彻底分离,如果未来需要从其他来源(如注解、API)构建配置,只需增加新的 Builder 实现类即可。
2.2 工厂模式(Factory Pattern)
MyBatis 中主要使用了 简单工厂模式(静态工厂模式),同时也隐含了工厂方法模式。
定义:简单工厂模式专门定义一个类负责创建其他类的实例,客户端根据参数获得不同产品。
应用 :SqlSessionFactory 就是工厂角色,它负责生产 SqlSession(产品)。
java
// 简单工厂:openSession 方法根据参数返回不同类型的 SqlSession
public interface SqlSessionFactory {
SqlSession openSession(); // 默认
SqlSession openSession(boolean autoCommit); // 指定是否自动提交
SqlSession openSession(ExecutorType execType);// 指定执行器类型
}
从类图看,DefaultSqlSessionFactory 实现了 SqlSessionFactory,其 openSession 方法内部根据参数创建 Executor(SimpleExecutor / ReuseExecutor / BatchExecutor),再包装成 DefaultSqlSession 返回。工厂模式解耦了 SqlSession 的创建逻辑与使用逻辑,当需要扩展新的 SqlSession 类型时,只需修改工厂类或新增工厂实现。
2.3 单例模式(Singleton Pattern)
定义:确保一个类只有一个实例,并自行实例化向整个系统提供该实例。
应用场景 :MyBatis 中的 ErrorContext 和 LogFactory 使用了单例模式。
- ErrorContext :记录当前线程的执行错误上下文。每个线程独立持有实例,通过
ThreadLocal实现线程级单例。 - LogFactory:负责创建日志适配器(Log 接口的实现),只需一个全局工厂实例来管理日志实现类的选择。
java
public class ErrorContext {
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();
private ErrorContext() {} // 私有构造器
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
}
这种设计确保了同一线程内始终使用同一个 ErrorContext 对象,避免了上下文传递的混乱。
2.4 代理模式(Proxy Pattern)
定义:为其他对象提供一种代理以控制对这个对象的访问。MyBatis 使用 JDK 动态代理实现了 Mapper 接口的无实现类调用。
工作流程:
DefaultSqlSession Proxy MapperProxyFactory Client DefaultSqlSession Proxy MapperProxyFactory Client getMapper(OrderMapper.class) 创建代理实例 返回代理对象 调用 orderMapper.selectById(1) 执行 SQL (selectOne) 返回结果 返回结果
关键代码 :DefaultSqlSession.getMapper() 方法最终调用 MapperRegistry.getMapper(),使用 MapperProxyFactory 为接口生成代理对象。
java
// MapperProxy 实现 InvocationHandler
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 若不是 Object 方法,则通过 SqlSession 执行 SQL
return mapperMethod.execute(sqlSession, args);
}
}
通过代理模式,MyBatis 为开发者生成了接口实现,无需编写 DAO 实现类,大大简化了开发。
2.5 模板方法模式(Template Method Pattern)
定义 :定义一个操作中的算法骨架,将一些步骤延迟到子类中,使子类可以不改变算法结构即可重新定义某些步骤。它基于继承实现代码复用。
应用 :MyBatis 中的 BaseExecutor 就是模板方法模式的典型代表。
BaseExecutor
+query() : : 模板方法
#doQuery() : : 抽象方法
#doUpdate() : : 抽象方法
SimpleExecutor
#doQuery() : : 实现
ReuseExecutor
#doQuery() : : 实现
BatchExecutor
#doQuery() : : 实现
代码骨架:
java
public abstract class BaseExecutor implements Executor {
@Override
public <E> List<E> query(...) {
// 模板方法:定义查询流程骨架
if (closed) throw new ExecutorException("...");
// 1. 从缓存获取
List<E> list = getFromLocalCache(...);
if (list != null) return list;
// 2. 真正执行查询(钩子方法)
list = doQuery(...);
// 3. 存入缓存
localCache.put(...);
return list;
}
protected abstract <E> List<E> doQuery(...); // 子类必须实现
}
三种子类(SimpleExecutor、ReuseExecutor、BatchExecutor)分别实现了 doQuery 方法,提供了不同的 Statement 处理策略。模板方法使算法的公共部分放在父类,子类只需关注差异化细节。
三、Spring 中的设计模式
Spring 框架体量庞大,设计模式无处不在。这里重点介绍工厂模式、代理模式、单例模式,并补充说明工厂方法模式的应用。
3.1 工厂模式(Factory Pattern)
Spring 的 IoC 容器本质就是一个巨大的工厂,负责创建和管理 Bean。主要体现为简单工厂模式 和工厂方法模式。
3.1.1 简单工厂模式
定义:由一个工厂类根据传入的参数动态决定创建哪一个产品类的实例。
应用 :BeanFactory 是 Spring 中最基本的工厂接口。调用 getBean(String name) 时,容器会根据传入的 id 或 name 返回对应的 Bean 实例。
java
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
Object userService = factory.getBean("userService");
getBean 方法内部根据配置元数据(XML、注解)判断 Bean 的类型、作用域、构造参数等,决定创建 SingletonBean 还是 PrototypeBean。简单工厂模式将对象的创建与使用分离,降低了耦合。
3.1.2 工厂方法模式
定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。
应用 :Spring 中的 FactoryBean 接口就是工厂方法模式的应用。开发人员可以实现 FactoryBean 来自定义 Bean 的创建逻辑。
java
public interface FactoryBean<T> {
T getObject() throws Exception; // 工厂方法
Class<?> getObjectType();
boolean isSingleton();
}
例如 ProxyFactoryBean 用于创建 AOP 代理对象,SqlSessionFactoryBean 用于在 MyBatis-Spring 整合时创建 SqlSessionFactory。
<<interface>>
FactoryBean<T>
+getObject() : T
ProxyFactoryBean
+getObject() : Object
SqlSessionFactoryBean
+getObject() : SqlSessionFactory
3.2 代理模式(Proxy Pattern)
定义:为其他对象提供一种代理以控制对这个对象的访问。
应用 :Spring AOP 底层依赖于动态代理(JDK 动态代理或 CGLIB)。当为目标 Bean 配置切面(如事务、日志、缓存)时,Spring 会生成一个代理对象,在调用目标方法前后执行增强逻辑。
前置增强
后置增强
客户端调用
代理对象
目标对象方法
返回结果
两种代理方式对比:
| 代理方式 | 适用场景 | 实现原理 |
|---|---|---|
| JDK 动态代理 | 目标类实现了至少一个接口 | 利用反射生成接口的代理类 |
| CGLIB 动态代理 | 目标类没有实现接口或需要代理类 | 通过字节码技术生成目标类的子类 |
Spring AOP 会自动选择:如果目标对象实现了接口则使用 JDK 代理,否则使用 CGLIB(也可强制使用 CGLIB)。
3.3 单例模式(Singleton Pattern)
定义:确保一个类只有一个实例,并提供一个全局访问点。
应用 :Spring 中的 Bean 默认作用域就是 singleton (单例)。容器内部通过注册表 (DefaultSingletonBeanRegistry)缓存单例 Bean,确保每个 Bean ID 只对应一个对象。
java
public class DefaultSingletonBeanRegistry {
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
protected Object getSingleton(String beanName) {
return this.singletonObjects.get(beanName);
}
}
- 好处:减少对象创建开销,避免资源冲突。
- 对比 :若需要非单例,可设置
@Scope("prototype"),此时每次getBean都会创建新实例。
此外,Spring 自身很多核心组件(如 BeanFactory、ApplicationContext)在应用中通常也只有一个实例,但它们的实现类并非使用单例模式,而是由容器保证唯一性。
四、小结与对比
| 设计模式 | MyBatis 应用示例 | Spring 应用示例 |
|---|---|---|
| 建造者模式 | SqlSessionFactoryBuilder |
BeanDefinitionBuilder(解析配置构建 BeanDefinition) |
| 简单工厂模式 | SqlSessionFactory 创建 SqlSession |
BeanFactory 根据名称创建 Bean |
| 工厂方法模式 | 较少(可自定义 DataSourceFactory) |
FactoryBean 接口,ProxyFactoryBean |
| 单例模式 | ErrorContext(线程级)、LogFactory |
Bean 默认 singleton 作用域,SingletonBeanRegistry |
| 代理模式 | MapperProxy 实现 Mapper 接口 |
AOP 动态代理(JDK/CGLIB) |
| 模板方法模式 | BaseExecutor |
JdbcTemplate、HibernateTemplate |
五、面试应答黄金模板
| 面试问题 | 精炼答案(可直接背诵) |
|---|---|
| MyBatis 用了哪些设计模式? | 主要有建造者模式(SqlSessionFactoryBuilder构建Configuration)、工厂模式(SqlSessionFactory生产SqlSession)、单例模式(ErrorContext)、代理模式(MapperProxy实现接口)、模板方法模式(BaseExecutor定义SQL执行骨架)。 |
| Spring 中的工厂模式和代理模式如何体现? | 工厂模式:BeanFactory 是简单工厂的体现,根据Bean名称生产实例;FactoryBean 是工厂方法模式,留给用户自定义创建复杂Bean。代理模式:AOP 使用 JDK 动态代理或 CGLIB 为目标对象创建代理,实现无侵入增强。 |
| 为什么 MyBatis 的 Mapper 只需要接口就能执行 SQL? | 利用了 JDK 动态代理。MapperProxyFactory 为 Mapper 接口生成代理对象,代理内部通过 SqlSession 调用对应 SQL 语句,屏蔽了底层 JDBC 细节。 |
| 单例模式在 Spring 中有什么优缺点? | 优点:减少内存占用,避免频繁创建销毁对象;缺点:状态共享可能引发线程安全问题,需避免在单例 Bean 中定义可变成员变量。 |
思考题 :Spring 的
BeanPostProcessor用到了哪种设计模式?它的作用是什么?欢迎留言讨论。