在日常开发中,我们使用 MyBatis Mapper 时只需要定义一个接口并编写 SQL 映射,就能直接调用数据库操作方法。这种看似简单的背后,实际上是一套精妙的 Spring 整合机制在发挥作用。本文将深入剖析 MyBatis Mapper 如何从"无实现"的接口转变为功能完整的 Spring Bean。
FactoryBean
Spring 的标准 Bean 实例化流程是通过反射调用类的构造函数完成的。但对于某些复杂场景(如 MyBatis Mapper),Spring 提供了 FactoryBean
接口来支持定制化的 Bean 创建逻辑。
FactoryBean
的核心价值在于:
将复杂的对象构建过程封装起来
提供比 XML/注解配置更灵活的实例化方式
支持动态决定返回对象的类型
自 Spring 3.0 起支持泛型,增强了类型安全性
MyBatis 正是通过 MapperFactoryBean
实现了这一机制:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
我们把debug断点打在getObject方法,发现操作跟我们刚学Mybatis写的demo一模一样。通过传入接口实例返回经过包装的代理类。
Mapper 接口的注册与发现
我们暂时别跟着断点往下走,我们可能想知道spring是如何加载这些Mapper接口和MapperFactoryBean 。起点就在MapperScan注解。看MapperScan源码,这个注解为 Import 了MapperScannerRegistrar类。
@MapperScan(basePackages = "包名")
MapperScannerRegistrar类的registerBeanDefinitions方法利用ClassPathMapperScanner对象读取解析了指定包名下所有的接口,并且通过构建BeanDefinitions对象,让每个MapperFactoryBean拥有不同的mapperInterface class属性。
整个流程的起点是 @MapperScan
注解:
@MapperScan
通过@Import
引入了MapperScannerRegistrar
MapperScannerRegistrar
使用ClassPathMapperScanner
扫描指定包下的接口为每个 Mapper 接口创建对应的
BeanDefinition
将这些
BeanDefinition
注册到 Spring 容器
关键点在于,注册的不是接口本身的 BeanDefinition
,而是以该接口作为 mapperInterface
属性的 MapperFactoryBean
的定义。
BeanDefinitions收集的途径非常多,比如还有BeanDefinitionRegistryPostProcessors动态注入BeanDefinition。对于spring ioc初始化的过程 比较模糊的同学,可以看看下面的系列化文章补补基础,写的非常好:
Spring Bean 实例化的关键阶段
在 Spring 容器的 refresh()
过程中,finishBeanFactoryInitialization()
方法会触发非延迟加载的单例 Bean 的初始化:
调用
getBean()
开始加载流程根据
BeanDefinition
创建MapperFactoryBean
实例通过
getObjectForBeanInstance()
判断是否需要调用FactoryBean
的getObject()
对于
FactoryBean
类型,最终调用getObjectFromFactoryBean()
protected Object getObjectForBeanInstance(Object beanInstance, String name, ...) {
// 情况1:如果是普通 Bean(非FactoryBean),直接返回
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
// 情况2:如果是 FactoryBean,且名称以 & 开头(表示要获取FactoryBean本身),直接返回
if (BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
// 情况3:如果是 FactoryBean 且需要获取其产品(getObject的结果)
return getObjectFromFactoryBean((FactoryBean<?>) beanInstance, name);
}
继续走读源码,我们会看到终于调用了FactoryBean的getObject方法,而我们也开始介绍下一章。
动态代理:无实现接口的魔法
当调用链最终到达 MapperFactoryBean.getObject()
时,真正的魔法开始了:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
深入这个调用,我们会发现 MyBatis 使用了 JDK 动态代理:
MapperProxyFactory
创建代理实例Proxy.newProxyInstance()
生成接口的代理实现MapperProxy
作为InvocationHandler
处理所有方法调用
public class MapperProxy<T> implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 处理 Object 方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 获取缓存的 MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
每个 Mapper 方法调用都会被 MapperProxy
拦截,然后:
解析对应的 SQL 语句
处理参数绑定
执行数据库操作
处理返回结果
Proxy做了什么事可以看我之前的博文
从代理模式再出发!Proxy.newProxyInstance的秘密
总结:从定义到实现的完整链条
定义阶段:开发者编写 Mapper 接口和 SQL 映射
注册阶段:
@MapperScan
扫描接口并注册MapperFactoryBean
定义实例化阶段:Spring 创建
MapperFactoryBean
并调用其getObject()
代理阶段:MyBatis 通过动态代理生成接口实现
执行阶段:方法调用被代理拦截,转换为真正的数据库操作
这种设计完美结合了 Spring 的扩展性和 MyBatis 的灵活性,使得开发者可以专注于业务逻辑,而无需关心繁琐的数据库访问实现细节。
修订
- 2019.10.23 补充了spring ioc方面的知识,更有利于理解spring与Mybatis的结合过程。