MyBatis 与 Spring 中的设计模式

MyBatis 与 Spring 中的设计模式精讲:从源码到实践

设计模式 是软件工程的基石,各大主流框架(MyBatis、Spring)在架构设计中大量运用了经典模式。理解这些模式如何落地,不仅能提升源码阅读能力,更能帮助你在实际项目中写出更优雅、可扩展的代码。本文将从建造者、工厂、单例、代理、模板方法五个核心模式入手,深入剖析它们在 MyBatis 和 Spring 中的具体应用。


一、为什么框架钟爱设计模式?

一个好的框架需要满足:高内聚低耦合、可扩展、可复用、易维护。设计模式恰好提供了经过实践验证的解决方案:

  • 建造者模式 ⇒ 解构复杂对象的创建过程
  • 工厂模式 ⇒ 解耦产品创建与使用
  • 单例模式 ⇒ 控制资源访问,减少内存开销
  • 代理模式 ⇒ 无侵入式增强功能(AOP)
  • 模板方法模式 ⇒ 抽取复用代码,允许子类定制

MyBatis 和 Spring 都深度运用了上述模式,下面逐一拆解。


二、MyBatis 中的设计模式

MyBatis 是一个优秀的持久层框架,其核心对象 SqlSessionExecutorStatementHandler 等的创建与协作都充满了设计模式的智慧。

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 方法内部根据参数创建 ExecutorSimpleExecutor / ReuseExecutor / BatchExecutor),再包装成 DefaultSqlSession 返回。工厂模式解耦了 SqlSession 的创建逻辑与使用逻辑,当需要扩展新的 SqlSession 类型时,只需修改工厂类或新增工厂实现。

2.3 单例模式(Singleton Pattern)

定义:确保一个类只有一个实例,并自行实例化向整个系统提供该实例。

应用场景 :MyBatis 中的 ErrorContextLogFactory 使用了单例模式。

  • 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(...); // 子类必须实现
}

三种子类(SimpleExecutorReuseExecutorBatchExecutor)分别实现了 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 自身很多核心组件(如 BeanFactoryApplicationContext)在应用中通常也只有一个实例,但它们的实现类并非使用单例模式,而是由容器保证唯一性。


四、小结与对比

设计模式 MyBatis 应用示例 Spring 应用示例
建造者模式 SqlSessionFactoryBuilder BeanDefinitionBuilder(解析配置构建 BeanDefinition)
简单工厂模式 SqlSessionFactory 创建 SqlSession BeanFactory 根据名称创建 Bean
工厂方法模式 较少(可自定义 DataSourceFactory FactoryBean 接口,ProxyFactoryBean
单例模式 ErrorContext(线程级)、LogFactory Bean 默认 singleton 作用域,SingletonBeanRegistry
代理模式 MapperProxy 实现 Mapper 接口 AOP 动态代理(JDK/CGLIB)
模板方法模式 BaseExecutor JdbcTemplateHibernateTemplate

五、面试应答黄金模板

面试问题 精炼答案(可直接背诵)
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 用到了哪种设计模式?它的作用是什么?欢迎留言讨论。


相关推荐
l软件定制开发工作室2 小时前
Spring开发系列教程(35)——使用Actuator
java·后端·spring
DavidSoCool4 小时前
Spring AI Alibaba ReactAgent 调用Tool 实现多轮对话
java·人工智能·spring·多轮对话·reactagent
挨踢ren5 小时前
单例模式:C++实现与多线程安全
c++·设计模式
ximu_polaris6 小时前
设计模式(C++)-行为型模式-访问者模式
c++·设计模式·访问者模式
范什么特西6 小时前
第一个Mybatis
java·开发语言·mybatis
超梦dasgg6 小时前
智慧充电系统计费定价服务Java 实现
java·开发语言·spring·微服务
码云数智-园园7 小时前
Spring循环依赖:三级缓存到底解决了什么,没解决什么?
java·后端·spring
workflower7 小时前
农业信息化
大数据·人工智能·设计模式·机器人·软件工程
过期动态8 小时前
【RabbitMQ基础篇】RabbitMQ从入门到实战
java·jvm·数据库·分布式·spring·rabbitmq·intellij-idea