MyBatis 代理生成不依赖 opens 指令,而是基于 JDK 动态代理;其核心前提为接口可被类加载器加载、方法可被反射读取、未被模块系统隐藏;仅当启用 JPMS 且 Mapper 包未导出时才需在 module-info.java 中用 opens 显式开放。
mybatis 本身不提供 opens 指令,也**不依赖 java 9+ 的模块系统(jpms)进行接口代理生成**。所谓“通过 opens 指令支持 mybatis 代理类生成”,属于概念混淆——opens 是 module-info.java 中用于开放包给反射访问的关键字,而 mybatis 的 mapper 接口代理完全基于 jdk 动态代理(java.lang.reflect.proxy),其运行前提仅需:接口可被类加载器加载、方法签名可被反射读取、无模块封装阻断反射。
MyBatis 代理生成真正依赖的条件
不是 opens,而是以下三点:
-
接口必须在 classpath 下可见:MyBatis 启动时通过
MapperRegistry扫描并注册接口,要求接口类能被当前ClassLoader正常加载(如放在src/main/java或 JAR 的BOOT-INF/classes中); -
接口不能被模块系统隐藏:若项目启用了 JPMS(即存在
module-info.java),且 Mapper 接口所在包未用opens或exports声明,则Proxy.newProxyInstance()在尝试获取接口方法元信息时会抛InaccessibleObjectException; -
代理过程无需实现类:MyBatis 不需要你写
UserMapperImpl,它通过MapperProxy+MapperMethod将接口调用直接转为SqlSession.selectOne("namespace.id", args)。
什么时候才需要在 module-info.java 中加 opens?
仅当你的项目明确采用模块化(JPMS),且 Mapper 接口所在的包被模块封装、又未导出时,动态代理底层的反射操作才会失败。此时需显式开放对应包:
// module-info.java 示例
module com.example.app {
opens com.example.mapper to mybatis;
requires mybatis;
requires java.sql;
}
注意:to mybatis 表示只对 mybatis 模块开放反射权限,而非全局开放;若使用 Spring Boot + MyBatis,实际应写 to org.mybatis.spring 或具体使用的 MyBatis 模块名(取决于其模块声明)。
实战中更常见的问题与解法
多数“代理失败”并非模块限制,而是配置或路径问题:
-
Mapper 接口未被扫描到:检查
<mappers></mappers>配置是否包含该接口,或@MapperScan是否覆盖对应包路径; -
接口方法无对应 SQL 映射:确保 XML 中
namespace与接口全限定名一致,且<select id="xxx"></select>的id与方法名匹配; -
类加载器隔离:Web 容器(如 Tomcat)或 OSGi 环境中,不同 ClassLoader 可能导致接口加载不一致,此时需统一委托策略,而非依赖
opens; -
Spring 环境下误用 new Proxy:不要手动调用
Proxy.newProxyInstance()创建 Mapper,应始终通过SqlSession.getMapper()或 Spring 的@Mapper注入。
一句话总结
MyBatis 的代理类生成逻辑由 MapperProxyFactory 触发,本质是标准 JDK 动态代理,它不解析也不执行 opens 指令;只有当你主动启用 Java 模块系统、且严格封装了 Mapper 包时,opens 才是绕过反射限制的必要补充,而非代理机制本身的组成部分。

















