使用JDKProxy实现MyBatis时出现StackOverflowError问题求助
为啥会突然爆StackOverflowError?
你碰到的这个栈溢出问题,本质是无限递归调用搞出来的——虽然你没手动调用proxy.toString(),但大概率是IDE调试时自动触发的:IDE展示代理对象信息时,会默认调用它的toString()方法,这就间接启动了代理逻辑。
具体到你的代码逻辑:
当代理对象触发toString()这类Object自带的方法时,会进入MapperProxy.invoke()。因为toString()不是TestMapper接口里定义的方法,所以mapperData会是null;如果之前的代码是调用method.invoke(proxy, args),那相当于再次调用代理对象的toString(),又会回到invoke()方法,无限循环下去,栈直接被撑爆。
而你改成return method.invoke(sqlSession, args)后,因为sqlSession是真实的MySqlSession实例(不是代理对象),调用它的toString()不会触发代理逻辑,递归直接终止,自然就没异常了。不过这么做其实有点“歪打正着”,因为代理对象的toString()应该返回自身的信息,而不是sqlSession的,不符合代理的语义。
正确的解决姿势
我们需要在invoke()方法里先做个判断:如果当前调用的是Object类的方法(比如toString()、hashCode()、equals()),直接走原生逻辑,别进入代理处理;只有Mapper接口定义的方法,才走SQL执行逻辑。
修改后的MapperProxy代码如下:
public class MapperProxy<T> implements InvocationHandler { private final MySqlSession sqlSession; // 构造函数注入sqlSession,原代码里应该漏了这个字段声明 public MapperProxy(MySqlSession sqlSession) { this.sqlSession = sqlSession; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 优先处理Object类的原生方法,避免递归 if (method.getDeclaringClass() == Object.class) { // 调用InvocationHandler实例自身的Object方法,不会触发代理逻辑 return method.invoke(this, args); } // 下面是原有的Mapper方法处理逻辑 int seperate = method.getDeclaringClass().getName().lastIndexOf("."); String mapper = method.getDeclaringClass().getName().substring(seperate + 1); MapperData mapperData = sqlSession.getConfiguration() .getMapperRegistory() .get(mapper + "." + method.getName()); if (mapperData != null) { System.out.println(String.format("SQL [%s],parameter [%s]", mapperData.getSql(), args[0])); Class<?> clazz = sqlSession.getClass(); Method realMethod = clazz.getMethod(method.getName(), MapperData.class, Object.class); return realMethod.invoke(sqlSession, mapperData, args); } // 非Object方法也非Mapper方法的情况,抛出异常提示 throw new UnsupportedOperationException(String.format("不支持调用方法:%s", method.getName())); } catch (InvocationTargetException ite) { throw ite.getCause(); } } }
核心改动点
- 新增Object方法判断分支:把Object类的方法单独拎出来处理,直接调用
this(也就是MapperProxy实例)的对应方法,完全绕开代理逻辑,从根源上避免递归。 - 补全sqlSession字段:原代码里
MapperProxy没有显式声明sqlSession成员变量,必须通过构造函数注入,否则编译会报错。
额外提醒
除了toString(),hashCode()和equals()这两个Object方法也可能触发相同的栈溢出问题,所以这个判断分支一定要覆盖所有Object类的方法,不能漏。
内容的提问来源于stack exchange,提问作者dc p

