MyBatis通过@MapperScan触发MapperScannerRegistrar注册MapperFactoryBean的BeanDefinition,再由FactoryBean.getObject()调用MyBatis的MapperProxyFactory创建JDK动态代理对象,最终注入Spring容器供@Autowired使用。

MyBatis 的 Mapper 接口没有实现类,却能在 Spring 中直接 @Autowired 注入使用,核心在于:Spring 容器里注册的不是接口本身,而是由 MyBatis 创建的动态代理对象——这个代理对象被包装成一个符合 Spring 规范的 Bean,并通过 FactoryBean 机制完成实例化和管理。
Mapper 接口如何变成 Spring Bean?
Spring 本身不识别纯接口,也不会为接口创建实例。MyBatis 借助 Spring 的扩展机制,在容器启动早期主动注册 Bean 定义(BeanDefinition),把每个 Mapper 接口“映射”为一个 MapperFactoryBean 类型的 Bean。这个类实现了 FactoryBean<T> 接口,Spring 在需要该 Bean 实例时,会调用它的 getObject() 方法来获取真正的代理对象。
-
@MapperScan注解触发MapperScannerRegistrar执行,后者实现ImportBeanDefinitionRegistrar - 扫描指定包下的所有 Mapper 接口,为每个接口注册一个
BeanDefinition,其beanClass设为MapperFactoryBean.class - 同时设置该
BeanDefinition的构造参数(如mapperInterface)和依赖(如sqlSessionFactory)
代理对象是怎么生成并返回的?
MapperFactoryBean.getObject() 是关键入口。它不自己写逻辑,而是委托给 MyBatis 内部的 Configuration 对象:
- 先从 Spring 容器中获取已配置好的
SqlSessionFactory - 调用
sqlSession.getMapper(mapperInterface),这步实际走到Configuration.getMapper() -
Configuration持有MapperRegistry,后者维护着所有 Mapper 接口对应的MapperProxyFactory -
MapperProxyFactory.newInstance(sqlSession)创建MapperProxy实例,再用 JDK 动态代理(Proxy.newProxyInstance)绑定到接口上
为什么注入的是代理,调用却像真实对象?
最终注入 Service 层的,是 MapperProxy 实例——它实现了 InvocationHandler。每次调用 Mapper 方法(如 userMapper.selectById(1)),都会被 MapperProxy.invoke() 拦截:
立即学习“Java免费学习笔记(深入)”;
- 根据方法签名查出对应
MappedStatement(含 SQL、参数映射、结果映射等元信息) - 通过
SqlSession执行 SQL,交由 JDBC 处理 - 将结果按配置自动封装为 Java 对象并返回
Spring 和 MyBatis 协作的关键点
整个过程依赖两个层面的配合:
-
注册时机:利用
BeanDefinitionRegistryPostProcessor或@Import机制,在 Bean 实例化前完成 Mapper Bean 定义的批量注入 -
实例化方式:不靠构造函数或反射实例化接口,而是通过
FactoryBean+ 动态代理组合,让 Spring “以为”它在管理普通 Bean,实则背后是 MyBatis 的执行引擎 -
生命周期对齐:Mapper 代理对象共享
SqlSessionFactory和SqlSession的生命周期,确保事务、连接等上下文一致


















