mybatis 的 getmapper() 返回的是 jdk 动态代理对象而非真实实现类,它实现接口但所有方法调用均被 mapperproxy.invoke() 拦截;因 jdk 代理仅支持接口,故必须使用接口而非抽象类或普通类。

MyBatis 的 getMapper() 真的返回了一个“实现类”吗?
不,它返回的是一个 JDK 动态代理对象——没有生成 .class 文件,也没有编译期的实现类。这个代理对象实现了你声明的 UserMapper 接口,但所有方法调用都会被拦截到 MapperProxy.invoke() 中统一处理。
常见错误现象:有人在调试时试图在 IDE 里点进 userMapper.queryAll() 查看“实现源码”,结果跳转失败或进入空方法——因为根本不存在真实实现,只有代理逻辑。
-
getMapper(Class<t> type)</t>最终委托给MapperRegistry,从knownMappers(HashMap)中查出对应的MapperProxyFactory - 工厂调用
newInstance(sqlSession),内部执行Proxy.newProxyInstance(..., new MapperProxy(...)) - 代理对象本身不持有 SQL,SQL 解析、参数绑定、结果映射全部发生在
invoke()被触发时
为什么必须用接口,不能用抽象类或普通类?
JDK 动态代理只支持对 接口 生成代理,这是 Java 语言层面的硬性限制。MyBatis 没有、也不能绕过这点。
使用场景:如果你误写了一个抽象类 AbstractUserMapper 并尝试 sqlSession.getMapper(AbstractUserMapper.class),会直接抛出 BindingException,提示 “Type is not known to the MapperRegistry”。
- MyBatis 启动时只扫描并注册
@Mapper接口或<mapper class="..."></mapper>中的接口类 - 抽象类/普通类不会被放入
knownMappers,getMapper()查不到就报错 - 即使你手动把抽象类塞进
knownMappers,JDK 代理创建也会在Proxy.newProxyInstance()阶段失败
MapperProxy 和 MapperMethod 分工怎么理解?
MapperProxy 是“门卫”,只做拦截和分发;MapperMethod 是“业务员”,真正读 XML 或注解、解析 SQL、决定走 selectOne 还是 selectList。
容易踩的坑:以为缓存或日志逻辑该写在 MapperProxy 里——其实不该。它的 invoke() 方法极轻量,重逻辑(比如参数校验、慢 SQL 记录)应放在自定义插件或 Service 层。
-
MapperProxy构造时就持有SqlSession和methodCache(ConcurrentMap) - 首次调用某方法时,才根据
Method对象构建MapperMethod并缓存;后续复用,避免重复解析 XML/注解 -
MapperMethod.execute()内部根据command.getType()(INSERT/SELECT 等)调用对应SqlSession方法,参数转换也在这里完成
不整合 Spring 时,getMapper() 的调用链你能跟到哪一层?
能清晰看到三层委托:你的代码 → DefaultSqlSession.getMapper() → Configuration.getMapper() → MapperRegistry.getMapper()。再往下就是 MapperProxyFactory.newInstance() 触发 JDK 代理创建。
性能影响:每次 getMapper() 都不新建代理对象,而是复用已创建的 MapperProxyFactory;但每个 SqlSession 实例都会拿到独立的代理对象(所以不同 session 的 mapper 不共享一级缓存)。
- 代理对象生命周期 =
SqlSession生命周期;session 关闭后,其持有的 mapper 代理不可再用 - 不要缓存
SqlSession或它的 mapper 代理到静态变量或单例中,否则引发线程安全问题或连接泄漏 - Spring 整合后,这些细节被隐藏,但底层仍是这套委托链——只是
SqlSession由SqlSessionTemplate管理,代理对象由MapperFactoryBean创建
最常被忽略的一点:动态代理不是魔法,它依赖完整的元信息加载。如果 XML 文件没被正确 <mapper resource="..."></mapper> 引入,或注解 SQL 写错位置(比如放在 default 方法里),MapperMethod 构建就会失败,报错却可能出现在第一次调用时,而不是启动阶段。










