MyBatis 的 Service(或 Mapper 接口)能够直接通过代理模式调用,而无需手动处理 AopContext.currentProxy(),原因在于 MyBatis 和 Spring AOP 的代理机制存在以下关键差异:
1. 代理模式的实现方式不同
• MyBatis 的代理:
MyBatis 使用 JDK 动态代理(基于接口)为 Mapper 接口生成代理对象。当调用 sqlSession.getMapper(UserMapper.class) 时,MyBatis 会创建一个实现了 InvocationHandler 的 MapperProxy 对象,拦截所有接口方法调用,并将其转换为对应的 SQL 执行。
• 关键点:MyBatis 的代理是显式生成的(通过 getMapper 方法),且代理逻辑仅处理 SQL 映射,不涉及 Spring 的 AOP 上下文。
• Spring AOP 的代理:
Spring 默认使用 JDK 动态代理(基于接口)或 CGLIB(基于类)创建代理对象。若需要通过 AopContext.currentProxy() 获取当前代理,必须满足:
- 配置
@EnableAspectJAutoProxy(exposeProxy=true)。 - 调用链在同一线程的 AOP 上下文中(例如,同类方法内部调用需通过代理触发)。
2. 代理对象的调用链路差异
• MyBatis 的调用流程:
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 显式获取代理对象
userMapper.getUserById(1); // 直接调用代理方法
• 代理对象由 MyBatis 直接管理,调用链路清晰,无需依赖线程上下文。
• Spring AOP 的调用问题:
在您的 Service 类中,commonMethod() 直接调用 method() 会绕过代理(因为 this.method() 是目标对象调用,而非代理对象调用)。此时需通过 AopContext.currentProxy() 或依赖注入代理对象来触发 AOP 逻辑。
3. 设计目的的差异
• MyBatis 代理:
专注于将接口方法映射到 SQL 执行,不涉及复杂的 AOP 增强(如事务、日志等)。代理行为是 MyBatis 框架的核心功能,而非可选特性。
• Spring AOP 代理:
用于通用增强逻辑(如事务、缓存)。需要显式配置(如 exposeProxy)以支持复杂场景(如同类方法调用)。
解决方案对比
| 场景 | MyBatis 的 Mapper 接口 | Spring 的 Service 类 |
|---|---|---|
| 代理生成方式 | 通过 sqlSession.getMapper() 显式生成 | 由 Spring 容器自动生成(需配置 @EnableAspectJAutoProxy) |
| 同类调用问题 | 无(方法调用始终通过代理) | 需通过 AopContext 或依赖注入代理 |
| 设计目标 | SQL 映射 | AOP 增强(事务、日志等) |
总结
MyBatis 的代理能直接使用,是因为其代理对象是显式获取且功能单一(仅处理 SQL 映射),而 Spring AOP 的代理需要额外配置以支持复杂场景(如同类方法调用)。若您的 Service 需要 AOP 增强,推荐通过依赖注入代理对象(@Autowired private ServiceI selfProxy)替代 AopContext。