mybatis 的 getmapper() 返回的是 jdk 动态代理对象(proxy 类型),而非接口实现类;该代理由 mapperproxy(invocationhandler 实现)拦截所有方法调用,通过「接口全限定名+方法名」查找预注册的 mappedstatement,再交由 executor 执行 sql。

MyBatis 的 getMapper() 真的返回了接口实现类吗
不是。它返回的是一个 JDK 动态代理对象,类型是 Proxy,而非你写的 UserMapper 实现类(你甚至根本没写)。这个代理对象实现了 UserMapper 接口,但所有方法调用都会被重定向到 InvocationHandler 的 invoke() 方法里——这才是 SQL 执行的真正入口。
常见错误现象:在 IDE 里点进 userMapper.getUserById(1),发现跳不到任何实现,只看到一堆生成的字节码类名(如 $Proxy23),误以为“断点进不去”就是框架黑盒。其实不是黑盒,是代理层把调用拦截了。
关键点:
-
sqlSession.getMapper(UserMapper.class)底层调用Proxy.newProxyInstance(),传入自定义的MapperProxy(实现了InvocationHandler) - 每次方法调用,
MapperProxy.invoke()会根据Method对象查出对应的MappedStatement(即封装好的 SQL、参数映射、结果映射等) - 不依赖接口是否有实现类,只依赖接口方法签名是否能匹配到已注册的 SQL 映射(XML 或注解)
为什么接口方法名和参数能对应上 XML 中的 <select></select> 标签
不是靠名字硬匹配,而是靠「全限定接口名 + 方法名」拼成的唯一 key(例如 com.example.UserMapper.getUserById)去 Configuration 的 mappedStatements Map 中查找。XML 解析阶段,MyBatis 就已把每个 SQL 标签注册为一个 MappedStatement,key 就是这个组合字符串。
容易踩的坑:
- XML 文件里的
namespace必须和接口全类名完全一致,大小写敏感;写错一个字母,getMapper()不报错,但调用时抛BindingException: Invalid bound statement - 接口方法参数如果是多个,必须用
@Param显式命名,否则 XML 中无法用#{param1}安全引用(尤其在动态 SQL 中易出错) - 方法返回值类型不影响匹配,但影响结果映射:返回
User和List<user></user>用的是同一段 SQL,只是ResultHandler处理逻辑不同
MapperProxy.invoke() 之后发生了什么
代理拦截后,核心流程是:解析参数 → 获取 MappedStatement → 交由 Executor 执行 → 结果用 TypeHandler 和 ResultSetHandler 映射回 Java 对象。整个过程不经过你写的任何实现代码。
性能与兼容性注意点:
- 代理对象创建是一次性的(
getMapper()调用时),但每次方法调用都会新建MapperMethod实例做元数据缓存,避免重复反射解析 - Spring 集成时,
MapperFactoryBean把代理对象包装成 Spring Bean,所以@Autowired UserMapper注入的仍是代理,不是原始接口 - 如果用了 MyBatis-Plus 或 tk.mybatis 的通用 Mapper,它们在
MapperProxy之上又加了一层逻辑,比如根据方法名selectByPrimaryKey自动生成 SQL,此时MappedStatement可能是运行时动态构建的,而非 XML 解析所得
不写 XML、只用注解时,SQL 是怎么绑定到方法上的
注解(如 @Select("SELECT * FROM user WHERE id = #{id}"))在接口加载阶段就被 MapperAnnotationBuilder 解析,并立即构造成 MappedStatement 注册进 Configuration。和 XML 流程完全一致,只是源头不同。
限制与风险:
- 注解 SQL 无法复用,复杂动态 SQL(如多条件
<where></where>、<foreach></foreach>)只能写在 XML 里;强行用@SelectProvider会把逻辑推到 Java 类中,可读性下降 - IDE 对注解内 SQL 的语法检查弱于 XML(比如字段名拼错不会高亮),上线前务必验证
- 使用
@Options(useGeneratedKeys = true)等配置时,必须确保数据库驱动支持,否则代理执行到Executor层才报错,堆栈离原始调用点较远
代理机制本身很薄,真正的复杂度藏在 MappedStatement 的构建、ParameterHandler 的参数处理、以及各种 Handler 的协作里——这些才是调试时最常卡住的地方。











