记一次cglib报错:MethodTooLargeException原创
金蝶云社区-QB不可能那么萌
QB不可能那么萌
21人赞赏了该文章 3,105次浏览 未经作者许可,禁止转载编辑于2021年11月30日 14:43:58
summary-icon摘要由AI智能服务提供

文本描述了在使用3.3版本的cglib包时,因领料出库单系统启用DEP功能导致的`MethodTooLargeException`错误。错误原因是cglib动态代理生成的`getIndex`方法过大,超过了Java方法体指令的最大限制(65535条)。通过分析发现,3.3版本cglib相比之前的2.2版本多代理了数百个protected方法,尤其是同一包下的protected方法,导致生成的代理类方法体过于庞大。通过查看源码和git修改记录,确认了这一变更。最终通过降级cglib包到3.1版本解决问题,但未来仍可能因方法过多再次触发此错误。

最近在升级3.3版本cglib包后领料出库单启用DEP报错。

net.sf.cglib.asm.$MethodTooLargeException: Method too large

DEP使用的是第三方开源包cglib实现方法前置脚本和后置脚本的执行,这里报错有个比较明显的提示

Method too large。

直译过来就是方法太大了,是哪个方法太大,也在异常下面给出了:getIndex。

我们都知道java文件会编译成class文件,方法体会编译成具体的指令。在java中规定了指令的最大条数,这个数字是用4个字节去存储所以,最多是65535条。

难道这个getIndex方法编译之后的指令已经超过了这个限制?

去代码中找这个方法,果然找不到,因为如果代码中有这么大的方法,编译器会直接报错的,不会到运行时才报错,很明显这个方法是cglib动态代理类中自动生成的。

查看cglib相关文档,在虚拟机参数中设置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY参数,可以将动态代理类输出到指定的路径。

在代码中或者在虚拟机参数中按如下设置。

代码:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:/cglibgenClass");

虚拟机参数:

EAS客户端在eas/client/bin/client.vmoptions文件中制定虚拟机参数。

当然就算设置了这个参数,也生成不了代理类,因为在生成的时候就会报错。


所以我们只能自己写个测试类测试一下。

测试类如下:

被代理类,有两个方法

package com.ice.yu;
public class KLen {
    public void a1(){System.out.println("longMethodName");}
    protected void a2(){System.out.println("longMethodName");}
}

通过cglib代理一下

package com.ice.yu;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class MainClass {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:/cglibgenClass");
        KLenProxy proxy = new KLenProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(KLen.class);
        enhancer.setCallback(proxy);
        enhancer.setUseFactory(true);
        KLen len = (KLen)enhancer.create();
        System.out.println(len.getClass().getDeclaredMethods().length);
        try {
            len.a1();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

运行后可以看到我们指定的位置已经产生了代理类文件

通过反编译工具反编译查看(这里推荐使用luyten,jd-gui有时候反编译会出问题,反编译不出来)

确实这个代理类生成了getindex方法,并且通过switch case 的方法把我们的a1和a2方法都生成了对应的分支,理论上如果我们的类中方法很多的时候,确实有可能把这个方法撑爆。(这里我测试了下方法名很简单的情况下,大概2000多个方法会触发这个Method too large报错。)

但是我们的领料出库单好像没这么多方法呀!只能调试看看了。调试了下,发现cglib代理一个类的时候不仅会代理本身的public和protected方法,还会代理父类的方法。因为EAS单据的继承体系比较深,并且一个类中方法很多,加在一起确实会有几千个。

但是很奇怪的是,在升级3.3版本cglib包之前是不会报错的,之前用的是2.2版本,比较古老。有对比了下两个版本代理方法的数量,发现2.2版本代理的方法,会比3.3少600多个,对比两个版本代理方法的区别,发现3.3会比2.2多代理一些protected方法。

分析源码发现两个版本有一处判断的差异,导致了现在这个现象。


看下面的这个方法,这是筛选代理方法的过滤,有个默认参数protectedOK,此参数默认值为false,字面理解就是要不要代理protected方法。2.2版本如果判断方法为protected,那直接返回protectedOK,那protected就不会被代理

但是3.3中,如果判断方法为protected并且protectedOK为false时,还会判断方法所在类和代理类是否在相同包下,即和代理类相同包名下的类中,即使是protected方法也会被代理。

查看git上的修改记录

此处为3.2版本的修改,相同包下的protected方法可以被代理。


到此时问题已经很明显了,由于3.3版本会比之前版本多代理600多的protected方法,所以导致代理类的getIndex方法体太大,超过了java的限制。最终是通过降级第三方包为3.1版本去处理的,但是这里还是有隐患,如果我们后续再在类中添加很多的public方法,当超过一定数量时,通过cglib代理还是会产生报错。




赞 21