精华内容
参与话题
问答
  • bcel_BCEL的字节码工程

    2020-06-29 06:03:58
    在本系列的最后三篇文章中,我向您展示了如何使用Javassist框架进行类工作。... 当您确实想控制程序执行的每个步骤时,低级方法使BCEL非常有用,但是对于两种情况都可以使用的情况,使用BCEL的工作也比使用Javassist...

    在本系列的最后三篇文章中,我向您展示了如何使用Javassist框架进行类工作。 这次,我将使用Apache字节码工程库(BCEL)来介绍一种非常不同的字节码操作方法。 BCEL在实际的JVM指令级别上运行,这与Javassist支持的源代码接口不同。 当您确实想控制程序执行的每个步骤时,低级方法使BCEL非常有用,但是对于两种情况都可以使用的情况,使用BCEL的工作也比使用Javassist复杂得多。

    我将首先介绍基本的BCEL体系结构,然后将本文的大部分时间用于用BCEL重建我的第一个Javassist类工作示例。 我将快速浏览BCEL软件包中包含的一些工具以及开发人员在BCEL之上构建的一些应用程序。

    BCEL班级访问

    BCEL为您提供与Javassist相同的基本功能,以检查,编辑和创建Java二进制类。 与BCEL的明显区别是,所有内容都旨在在JVM汇编程序语言级别上工作,而不是Javassist提供的源代码接口。 幕后有一些更深层次的差异,包括在BCEL中使用两个单独的组件层次结构-一个用于检查现有代码,另一个用于创建新代码。 我将假设您熟悉本系列以前的文章中的Javassist(请参阅侧栏不要错过本系列的其余部分 )。 因此,在您开始使用BCEL时,我将集中讨论可能会使您感到困惑的差异。

    与Javassist一样,BCEL的类检查方面基本上可以复制通过Reflection API直接在Java平台中提供的功能。 在类工作工具包中,此重复是必要的,因为通常在修改完类之后 ,才希望加载正在使用的类。

    BCEL在org.apache.bcel包中提供了一些基本的常量定义,但是除了这些定义之外,所有与检查相关的代码都在org.apache.bcel.classfile包中。 该包中的起点是JavaClass类。 在使用常规Java反射时,此类在使用BCEL访问类信息中的作用与java.lang.Class相同。 JavaClass定义了获取类的字段和方法信息的方法,以及有关超类和接口的结构信息。 java.lang.Class不同, JavaClass还提供对类的内部信息的访问,包括常量池和属性,以及完整的二进制类表示形式(字节流)。

    JavaClass实例通常是通过解析实际的二进制类来创建的。 BCEL提供了org.apache.bcel.Repository类来为您处理解析。 默认情况下,BCEL解析并缓存在JVM类路径中找到的类的表示,从org.apache.bcel.util.Repository实例获取实际的二进制类表示(请注意程序包名称中的差异)。 org.apache.bcel.util.Repository实际上是二进制类表示源的接口。 您可以替换其他路径来查找类文件,或使用其他访问类信息的方式来代替使用类路径的默认源。

    换班

    除了对类组件的反射式访问之外, org.apache.bcel.classfile.JavaClass还提供了用于更改类的方法。 您可以使用这些方法将任何类组件设置为新值。 但是,它们通常没有很多直接用途,因为包中的其他类不支持以任何合理的方式构造组件的新版本。 而是在org.apache.bcel.generic包中提供了一整套单独的类,它们提供了由org.apache.bcel.classfile类表示的相同组件的可编辑版本。

    就像org.apache.bcel.classfile.JavaClass是使用BCEL检查现有类的起点一样, org.apache.bcel.generic.ClassGen是创建新类的起点。 它还可以用于修改现有的类-为了处理这种情况,有一个构造函数采用JavaClass实例,并使用它来初始化ClassGen类信息。 完成类修改后,您可以通过调用返回JavaClass的方法来从ClassGen实例中获得可用的类表示形式,该方法又可以转换为二进制类表示形式。

    听起来令人困惑? 我觉得是这样的。 实际上,在这两个程序包之间来回切换是使用BCEL时最尴尬的方面之一。 重复的类结构往往会给您带来麻烦,因此,如果您对BCEL进行了大量工作,则可能值得编写可隐藏其中一些差异的包装器类。 在本文中,我将主要使用org.apache.bcel.generic包类,并避免使用包装器,但这是您需要自己做的事情。

    除了ClassGen之外, org.apache.bcel.generic包还定义了一些类来管理各种类组件的构造。 这些结构类包括ConstantPoolGen处理常量池, FieldGenMethodGen的领域和方法, InstructionList用的JVM指令序列工作。 最后, org.apache.bcel.generic包还定义了表示每种JVM指令类型的类。 您可以直接创建这些类的实例,或者在某些情况下,使用org.apache.bcel.generic.InstructionFactory帮助器类。 使用InstructionFactory的优点是,它可以为您处理许多指令构建的簿记细节(包括根据指令需要将项目添加到常量池中)。 在下一节中,您将看到如何使所有这些类一起玩。

    BCEL的课堂工作

    对于应用BCEL的示例,我将使用与第4部分中的Javassist示例相同的任务-测量执行方法所花费的时间。 我什至将使用与Javassist相同的方法:我将创建原始方法的副本,以使用修改后的名称进行计时,然后将原始方法的主体替换为将计时计算包裹在调用周围的代码。重命名的方法。

    选择豚鼠

    清单1给出了一个示例方法,我将用于演示: StringBuilder类的buildString方法。 正如我在说4部分 ,这种方法构造了一个String它一再追加一个字符的字符串的结尾创建一个更长的字符串-正好做任何Java性能专家会告诉你不要做任何要求的长度。 因为字符串是不可变的,所以这种方法意味着每次通过循环都会构造一个新的字符串,并从旧字符串中复制数据,并在最后添加一个字符。 最终结果是,此方法用于创建更长的字符串时,将产生越来越多的开销。

    清单1.要计时的方法
    public class StringBuilder
    {
        private String buildString(int length) {
            String result = "";
            for (int i = 0; i < length; i++) {
                result += (char)(i%26 + 'a');
            }
            return result;
        }
        
        public static void main(String[] argv) {
            StringBuilder inst = new StringBuilder();
            for (int i = 0; i < argv.length; i++) {
                String result = inst.buildString(Integer.parseInt(argv[i]));
                System.out.println("Constructed string of length " +
                    result.length());
            }
        }
    }

    清单2显示了与我将使用BCEL进行的类工作更改等效的源代码。 在这里,包装方法只保存当前时间,然后调用重命名的原始方法并打印时间报告,然后再将调用结果返回到原始方法。

    清单2.添加到原始方法的时间
    public class StringBuilder
    {
        private String buildString$impl(int length) {
            String result = "";
            for (int i = 0; i < length; i++) {
                result += (char)(i%26 + 'a');
            }
            return result;
        }
        
        private String buildString(int length) {
            long start = System.currentTimeMillis();
            String result = buildString$impl(length);
            System.out.println("Call to buildString$impl took " +
                (System.currentTimeMillis()-start) + " ms.");
            return result;
        }
        
        public static void main(String[] argv) {
            StringBuilder inst = new StringBuilder();
            for (int i = 0; i < argv.length; i++) {
                String result = inst.buildString(Integer.parseInt(argv[i]));
                System.out.println("Constructed string of length " +
                    result.length());
            }
        }
    }

    编码转换

    实现代码以添加方法计时使用了BCEL类访问部分中概述的BCEL API。 在JVM指令级别上工作使代码比在第4部分中的Javassist示例更长,因此在这里,我将一次逐步介绍它,然后再提供完整的实现。 在最后的代码中,所有这些部分将组成一个方法,该方法带有一对参数: cgen ,是org.apache.bcel.generic.ClassGen类的实例,该类已使用要修改的类的现有信息进行了初始化; method ,这是我要计时的方法的org.apache.bcel.classfile.Method实例。

    清单3包含了transform方法的第一段代码。 从注释中可以看到,第一部分只是初始化我将要使用的基本BCEL组件,其中包括使用要计时的方法的信息初始化新的org.apache.bcel.generic.MethodGen实例。 我为此MethodGen设置了一个空指令列表,稍后将使用实际的计时代码进行填写。 在第二部分中,我从原始方法创建了第二个org.apache.bcel.generic.MethodGen实例,然后从类中删除了原始方法。 在第二个MethodGen实例上,我只是将名称更改为使用“ $ impl”后缀,然后调用getMethod()将可修改的方法信息转换为org.apache.bcel.classfile.Method实例的固定形式。 然后,我使用addMethod()调用将重命名的方法添加到该类中。

    清单3.添加拦截方法
    // set up the construction tools
    InstructionFactory ifact = new InstructionFactory(cgen);
    InstructionList ilist = new InstructionList();
    ConstantPoolGen pgen = cgen.getConstantPool();
    String cname = cgen.getClassName();
    MethodGen wrapgen = new MethodGen(method, cname, pgen);
    wrapgen.setInstructionList(ilist);
        
    // rename a copy of the original method
    MethodGen methgen = new MethodGen(method, cname, pgen);
    cgen.removeMethod(method);
    String iname = methgen.getName() + "$impl";
    methgen.setName(iname);
    cgen.addMethod(methgen.getMethod());

    清单4给出了transform方法的下一段代码。 这里的第一部分计算堆栈上方法调用参数所占用的空间。 这部分是必需的,因为在调用包装方法之前将开始时间存储在堆栈帧中,我需要知道什么偏移量可以用于局部变量(请注意,我可以使用BCEL的局部变量处理来获得相同的效果,但是对于本文我更喜欢一种显式方法)。 该代码的第二部分生成对java.lang.System.currentTimeMillis()的调用,以获取开始时间,并将其保存到堆栈帧中计算出的局部变量offset中。

    您可能想知道为什么我在参数大小计算开始时检查该方法是否是静态的,然后将堆栈帧插槽初始化为零(如果不是,则初始化为零)。 此方法与Java语言如何处理方法调用有关。 对于非静态方法,每个调用中的第一个(隐藏)参数都是目标对象的this引用,在计算堆栈帧上完整的参数集大小时需要考虑这一点。

    清单4.设置打包的调用
    // compute the size of the calling parameters
    Type[] types = methgen.getArgumentTypes();
    int slot = methgen.isStatic() ? 0 : 1;
    for (int i = 0; i < types.length; i++) {
        slot += types[i].getSize();
    }
        
    // save time prior to invocation
    ilist.append(ifact.createInvoke("java.lang.System",
        "currentTimeMillis", Type.LONG, Type.NO_ARGS, 
        Constants.INVOKESTATIC));
    ilist.append(InstructionFactory.createStore(Type.LONG, slot));

    清单5显示了生成对包装方法的调用并保存结果(如果有)的代码。 本文的第一部分再次检查该方法是否是静态的。 如果该方法不是静态的,则生成代码以this对象引用加载到堆栈,并将方法调用类型设置为virtual (而不是static )。 然后, for循环生成将所有调用参数值复制到堆栈的代码, createInvoke()方法生成对包装方法的实际调用,最后的if语句将结果值保存到堆栈帧中的另一个局部变量位置(if结果类型不是void )。

    清单5.调用包装的方法
    // call the wrapped method
    int offset = 0;
    short invoke = Constants.INVOKESTATIC;
    if (!methgen.isStatic()) {
        ilist.append(InstructionFactory.createLoad(Type.OBJECT, 0));
        offset = 1;
        invoke = Constants.INVOKEVIRTUAL;
    }
    for (int i = 0; i < types.length; i++) {
        Type type = types[i];
        ilist.append(InstructionFactory.createLoad(type, offset));
        offset += type.getSize();
    }
    Type result = methgen.getReturnType();
    ilist.append(ifact.createInvoke(cname, 
        iname, result, types, invoke));
        
    // store result for return later
    if (result != Type.VOID) {
       ilist.append(InstructionFactory.createStore(result, slot+2));
    }

    现在进入总结。 清单6生成了用于实际计算自开始时间起经过的毫秒数的代码,并将其打印为格式正确的消息。 这部分看起来很复杂,但是大多数操作实际上只是编写输出消息的各个部分。 它确实说明了我在较早的代码中没有使用的几种操作类型,包括字段访问(对java.lang.System.out访问)和一些不同的指令类型。 如果您将JVM视为基于堆栈的处理器,那么其中的大多数内容应该很容易理解,因此在此不再赘述。

    清单6.使用的计算和打印时间
    // print time required for method call
    ilist.append(ifact.createFieldAccess("java.lang.System", "out", 
        new ObjectType("java.io.PrintStream"), Constants.GETSTATIC));
    ilist.append(InstructionConstants.DUP);
    ilist.append(InstructionConstants.DUP);
    String text = "Call to method " + methgen.getName() + " took ";
    ilist.append(new PUSH(pgen, text));
    ilist.append(ifact.createInvoke("java.io.PrintStream", "print",
        Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
    ilist.append(ifact.createInvoke("java.lang.System", 
        "currentTimeMillis", Type.LONG, Type.NO_ARGS, 
        Constants.INVOKESTATIC));
    ilist.append(InstructionFactory.createLoad(Type.LONG, slot));
    ilist.append(InstructionConstants.LSUB);
    ilist.append(ifact.createInvoke("java.io.PrintStream", "print", 
        Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL));
    ilist.append(new PUSH(pgen, " ms."));
    ilist.append(ifact.createInvoke("java.io.PrintStream", "println", 
        Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));

    在生成了时序消息代码之后,清单7剩下的就是包装方法代码的完成,并从包装方法调用中返回保存的结果值(如果有的话),然后完成构造的包装方法的确定。 最后一部分涉及几个步骤。 调用stripAttributes(true)只是告诉BCEL不要为构造的方法生成调试信息,而setMaxStack()setMaxLocals()调用将为该方法计算并设置堆栈使用情况信息。 完成之后,我实际上可以生成该方法的最终版本并将其添加到类中。

    清单7.完成包装器
    // return result from wrapped method call
    if (result != Type.VOID) {
        ilist.append(InstructionFactory.createLoad(result, slot+2));
    }
    ilist.append(InstructionFactory.createReturn(result));
        
    // finalize the constructed method
    wrapgen.stripAttributes(true);
    wrapgen.setMaxStack();
    wrapgen.setMaxLocals();
    cgen.addMethod(wrapgen.getMethod());
    ilist.dispose();

    完整代码

    清单8显示了完整的代码(略微重新格式化以适合宽度),包括一个main()方法,该方法采用类文件的名称以及要转换的方法:

    清单8.完整的转换代码
    public class BCELTiming
    {
        private static void addWrapper(ClassGen cgen, Method method) {
            
            // set up the construction tools
            InstructionFactory ifact = new InstructionFactory(cgen);
            InstructionList ilist = new InstructionList();
            ConstantPoolGen pgen = cgen.getConstantPool();
            String cname = cgen.getClassName();
            MethodGen wrapgen = new MethodGen(method, cname, pgen);
            wrapgen.setInstructionList(ilist);
            
            // rename a copy of the original method
            MethodGen methgen = new MethodGen(method, cname, pgen);
            cgen.removeMethod(method);
            String iname = methgen.getName() + "$impl";
            methgen.setName(iname);
            cgen.addMethod(methgen.getMethod());
            Type result = methgen.getReturnType();
            
            // compute the size of the calling parameters
            Type[] types = methgen.getArgumentTypes();
            int slot = methgen.isStatic() ? 0 : 1;
            for (int i = 0; i < types.length; i++) {
                slot += types[i].getSize();
            }
            
            // save time prior to invocation
            ilist.append(ifact.createInvoke("java.lang.System",
                "currentTimeMillis", Type.LONG, Type.NO_ARGS, 
                Constants.INVOKESTATIC));
            ilist.append(InstructionFactory.
                createStore(Type.LONG, slot));
            
            // call the wrapped method
            int offset = 0;
            short invoke = Constants.INVOKESTATIC;
            if (!methgen.isStatic()) {
                ilist.append(InstructionFactory.
                    createLoad(Type.OBJECT, 0));
                offset = 1;
                invoke = Constants.INVOKEVIRTUAL;
            }
            for (int i = 0; i < types.length; i++) {
                Type type = types[i];
                ilist.append(InstructionFactory.
                    createLoad(type, offset));
                offset += type.getSize();
            }
            ilist.append(ifact.createInvoke(cname, 
                iname, result, types, invoke));
            
            // store result for return later
            if (result != Type.VOID) {
                ilist.append(InstructionFactory.
                    createStore(result, slot+2));
            }
            
            // print time required for method call
            ilist.append(ifact.createFieldAccess("java.lang.System",
                "out",  new ObjectType("java.io.PrintStream"),
                Constants.GETSTATIC));
            ilist.append(InstructionConstants.DUP);
            ilist.append(InstructionConstants.DUP);
            String text = "Call to method " + methgen.getName() +
                " took ";
            ilist.append(new PUSH(pgen, text));
            ilist.append(ifact.createInvoke("java.io.PrintStream",
                "print", Type.VOID, new Type[] { Type.STRING },
                Constants.INVOKEVIRTUAL));
            ilist.append(ifact.createInvoke("java.lang.System", 
                "currentTimeMillis", Type.LONG, Type.NO_ARGS, 
                Constants.INVOKESTATIC));
            ilist.append(InstructionFactory.
                createLoad(Type.LONG, slot));
            ilist.append(InstructionConstants.LSUB);
            ilist.append(ifact.createInvoke("java.io.PrintStream",
                "print", Type.VOID, new Type[] { Type.LONG },
                Constants.INVOKEVIRTUAL));
            ilist.append(new PUSH(pgen, " ms."));
            ilist.append(ifact.createInvoke("java.io.PrintStream",
                "println", Type.VOID, new Type[] { Type.STRING },
                Constants.INVOKEVIRTUAL));
                
            // return result from wrapped method call
            if (result != Type.VOID) {
                ilist.append(InstructionFactory.
                    createLoad(result, slot+2));
            }
            ilist.append(InstructionFactory.createReturn(result));
            
            // finalize the constructed method
            wrapgen.stripAttributes(true);
            wrapgen.setMaxStack();
            wrapgen.setMaxLocals();
            cgen.addMethod(wrapgen.getMethod());
            ilist.dispose();
        }
        
        public static void main(String[] argv) {
            if (argv.length == 2 && argv[0].endsWith(".class")) {
                try {
                
                    JavaClass jclas = new ClassParser(argv[0]).parse();
                    ClassGen cgen = new ClassGen(jclas);
                    Method[] methods = jclas.getMethods();
                    int index;
                    for (index = 0; index < methods.length; index++) {
                        if (methods[index].getName().equals(argv[1])) {
                            break;
                        }
                    }
                    if (index < methods.length) {
                        addWrapper(cgen, methods[index]);
                        FileOutputStream fos =
                            new FileOutputStream(argv[0]);
                        cgen.getJavaClass().dump(fos);
                        fos.close();
                    } else {
                        System.err.println("Method " + argv[1] + 
                            " not found in " + argv[0]);
                    }
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                }
                
            } else {
                System.out.println
                    ("Usage: BCELTiming class-file method-name");
            }
        }
    }

    旋转一下

    清单9所显示的第一运行结果StringBuilder在未修改形式的程序,然后运行该BCELTiming程序以添加定时信息,最后运行StringBuilder程序它被修改后。 您可以看到StringBuilder在被修改后如何开始报告执行时间,以及由于效率低下的字符串构造代码,其时间如何比构造的字符串的长度快得多。

    清单9.运行程序
    [dennis]$ java StringBuilder 1000 2000 4000 8000 16000
    Constructed string of length 1000
    Constructed string of length 2000
    Constructed string of length 4000
    Constructed string of length 8000
    Constructed string of length 16000
    
    [dennis]$ java -cp bcel.jar:. BCELTiming StringBuilder.class buildString
    
    [dennis]$ java StringBuilder 1000 2000 4000 8000 16000
    Call to method buildString$impl took 20 ms.
    Constructed string of length 1000
    Call to method buildString$impl took 79 ms.
    Constructed string of length 2000
    Call to method buildString$impl took 250 ms.
    Constructed string of length 4000
    Call to method buildString$impl took 879 ms.
    Constructed string of length 8000
    Call to method buildString$impl took 3875 ms.
    Constructed string of length 16000

    总结BCEL

    BCEL不仅仅是我在本文中显示的基本的课堂工作支持,还有更多。 它还包括一个完整的验证程序实现,以确保根据JVM规范,二进制类是有效的(请参阅org.apache.bcel.verifier.VerifierFactory ),这是一个反汇编程序,可生成框架清晰并链接在一起的JVM级别的二进制视图类,甚至是BCEL程序生成器,它都会输出BCEL程序的源代码以构建您提供的类。 (Javadocs中未包含org.apache.bcel.util.BCELifier类,因此请查看源代码以供使用。此功能很吸引人,但输出可能太神秘了,无法供大多数开发人员使用)。

    在我自己使用BCEL的过程中,我发现HTML反汇编程序特别有用。 要进行尝试,只需执行BCEL JAR中的org.apache.bcel.util.Class2HTML类,并以您要反汇编的类文件的路径作为命令行参数即可。 它将在当前目录中生成HTML文件。 例如,在这里,我将分解用于时序示例的StringBuilder类:

    [dennis]$ java -cp bcel.jar org.apache.bcel.util.Class2HTML StringBuilder.class
    Processing StringBuilder.class...Done.

    图1是反汇编程序生成的帧输出的屏幕截图。 在此快照中,右上方的大框显示了添加到StringBuilder类中的定时包装器方法的反汇编。 完整HTML输出包含在下载文件中-如果您想实时观看,只需在浏览器窗口中打开StringBuilder.html文件即可。”

    图1.拆卸StringBuilder
    拆卸StringBuilder

    当前,BCEL可能是Java类工作中使用最广泛的框架。 它列出了网站上使用BCEL的许多其他项目,包括Xalan XSLT编译器,Java编程语言的AspectJ扩展以及几种JDO实现。 许多其他未列出的项目也正在使用BCEL,包括我自己的JiBX XML数据绑定项目。 但是,此后BCEL列出的几个项目已切换到其他库,因此不要将列表的长度作为BCEL受欢迎程度的绝对指南。

    BCEL的最大优势在于其对商业友好的Apache许可以及广泛的JVM指令级支持。 这些功能加上其稳定性和使用寿命,使其成为班级应用程序的非常受欢迎的选择。 但是,BCEL似乎并没有为速度或易用性而精心设计。 Javassist为大多数目的提供了一个友好得多的API,至少在我的简单测试中,它具有同等(甚至更好)的速度。 如果您的项目可以使用Mozilla公共许可证(MPL)或GNU较小通用公共许可证(LGPL)来使用软件,则Javassist可能是一个更好的选择(可以使用以下两种许可证之一)。

    下一个

    既然我已经向您介绍了Javassist和BCEL,那么本系列的下一篇文章将深入探讨比到目前为止所见更为有用的类工作应用程序。 回到第2部分 ,我演示了对方法的反射调用比直接调用要慢得多。 在第8部分中,我将展示如何在运行时使用Javassist和BCEL来用动态生成的代码替换反射调用,从而显着提高性能。 下个月再检查一下Java编程动态,以了解详细信息。


    翻译自: https://www.ibm.com/developerworks/java/library/j-dyn0414/index.html

    展开全文
  • apache bcel动态编译


    1:下载源码

    http://public.dhe.ibm.com/software/dw/library/j-dyn7.zip

    bcel jar

    http://download.csdn.net/detail/qq_16590151/9701906

    2:编译代码

    项目路径:/opt/test/thread

    cd /opt/test/thread

    编译java文件

    javac /opt/test/thread/src/com/test/wm/StringBuilder.java

    运行java文件

    java com.test.wm.StringBuilder 100 200 300
    Constructed string of length 100
    Constructed string of length 200
    Constructed string of length 300

    bcel动态编译class文件

    java -cp /opt/test/bcel.jar:. com.test.wm.BCELTiming /opt/test/thread/src/com/test/wm/StringBuilder.class buildString

    运行动态编译后的class文件

    java -XX:-UseSplitVerifier com.test.wm.StringBuilder 100 200 300
    Call to method buildString$impl took 1 ms.
    Constructed string of length 100
    Call to method buildString$impl took 0 ms.
    Constructed string of length 200
    Call to method buildString$impl took 2 ms.
    Constructed string of length 300

    使用场景

    1:cat埋点(企业各应用统一日志输出、可动态编译spring底层调用、jdbc底层查询)

    2:统一日志输出

    3:上文中的耗时打印

    展开全文
  • 借助BCEL

    2015-05-15 17:59:00
    BCEL是一个java字节码引擎库,有了他我们可以方便的表示出class文件的数据,另外BCEL还支持动态的创建和改变java class文件。这里我仅是初略的了解BCEL解释class文件的功能,为我后面学习FindBugs工具做准备,需要...

    BCEL是一个java字节码引擎库,有了他我们可以方便的表示出class文件的数据,另外BCEL还支持动态的创建和改变java class文件。这里我仅是初略的了解BCEL解释class文件的功能,为我后面学习FindBugs工具做准备,需要深入学习BCEL的创建、改变class文件的功能,可以查看官方文档。

    5151

    这是org.apache.bcel.classfile包下的类,可以看到这些类展示了class文件所有的数据结构,JavaClass是一个java class文件的抽象,他可以通过ClassParser来得到。另外可用通过JavaClass实例来得到Constant、Field、Method等所用class文件数据:

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String classPath = "K:\\....\\HeapSort.class";
        try {
            JavaClass jClass = new ClassParser(classPath).parse();
            //Class2HTML class2html = new Class2HTML(jClass, "K:\\....\\");
            Method[] methods = jClass.getMethods();
            Method me = null;
            for(Method method : methods){
                System.out.println(method.toString());
                if(method.getName().equals("sort")) me = method;
            }
            
            System.out.println(me.toString());
            Code code = me.getCode();
            System.out.println(code.toString());
            Attribute[] attribute = me.getAttributes();
            System.out.println(attribute[0].toString());
            
            ConstantPool pool = jClass.getConstantPool();
            Constant[] constants = pool.getConstantPool();
            System.out.println("constant 数量:"+constants.length);
        } catch (ClassFormatException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    这里就不展示输出结果了,在org.apache.bcel.util包中有些工具类,如使用Class2HTML类可以把class文件的一些数据展示到HTML文件中:

    QQ图片20150515172301

    上面展示的有所有的常量,class的属性,和方法中的code属性。这里提一下,上图中可以看到该class文件中共包括了89个常量,但在上述代码输出中我们得到常量个数为90。正好与了解Java Class文件结构中提到的constant_pool_count这个值等于常量池中常量数量加1相符。

    转载于:https://www.cnblogs.com/pzhblog/p/4506539.html

    展开全文
  • BCEL简明教程

    2015-12-04 18:29:18
    注意:本文章主要依据BCEL官方手册进行阐述,大部分内容都是从该手册直接翻译过来的,并做了一定的简化,同时还参考了《深入理解Java虚拟机》(周志明著)。笔者在下面阐述的时候也会给出相应的章节,如果读者有不...

    注意:本文章主要依据BCEL官方手册进行阐述,大部分内容都是从该手册直接翻译过来的,并做了一定的简化,同时还参考了《深入理解Java虚拟机》(周志明著)。笔者在下面阐述的时候也会给出相应的章节,如果读者有不清楚的地方请参考BCEL官方手册或是BCEL API,以及《深入理解Java虚拟机》。

    BCEL是什么?

    相信搜索到这篇文章的读者应该知道BCEL是啥,不过还是简要提一下吧:BCEL(Byte Code Engineering Library)原本是Apache Jakarta的一个子项目,目前已成为Apache Commons的一个子项目,主要用于分析、创建、操纵Java  class文件。
    为了说明如何使用BCEL,需要简要介绍一下JVM结构和class文件的结构。

    JVM结构

    JVM
    JVM是Java提供平台无关性的基础,它是一台抽象的机器,主要任务是装载class文件并且执行其中的字节码。Java虚拟机的体系结构如上图所示。这里仅给出JVM中比较重要的部分的说明,详细介绍请参考《深入理解Java虚拟机》(周志明著)和《Java Virtual Machiine Specification》(参见Oracle网站)。

    • 类装载子系统:每个Java虚拟机都有一个类装载子系统,它根据给定的全限定名来装入类或接口。
    • 运行时数据区:用于组织需要内存来存储的东西,如,字节码,程序创建的对象,传递给方法的参数,返回值,局部变量,运算的中间结果,……
    • Java堆:存放运行时创建的对象实例,以及对象间的引用关系
    • Java栈:其实更准确的称呼应该是“Java Virtual Machine Stack”,每当一个方法被执行的时候,都会创建一个Java栈帧(stack frame),栈帧中保存着该方法的操作数栈、局部变量表(local variable table)、动态链接、方法出口等信息,栈帧创建后就会放到Java栈上,执行完毕后抛弃该栈帧。我们平时所说的方法内的局部变量保存在“栈”上其实就是指Java栈。
    • 方法区:保存被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。注意方法区中包含有一个运行区常量池,class文件中的常量池(将在后面讲解)中的内容将在类加载后存放到这个运行时常量池中。
    • 程序计数器(PC计数器):PC寄存器的值总是指示下一条将被执行的指令
    • 本地方法栈:用于本地方法的执行,本地方法参考《Core Java》中有关JNI的阐述。

    Java class文件格式

    注:要完整的说明class文件的结构是个非常费劲的事情,JVM指令系统更是如此。限于篇幅这里不再详细阐述,仅给出简要的说明,详细说明请参考《深入理解Java虚拟机》和《Java Virtual Machiine Specification》
    class文件的格式大体如下图所示
    classfile
    class文件的header部分包含有一个“魔数”(magic number)0xCAFEBABE(其实是”cafe baby”,此乃当年Java开发团队的幽默),然后是版本号,再之后是常量池,访问权限标志位,当前类(注意,这里的类是指类和接口,是比较泛泛的概念,下文同)所实现的接口列表,当前类所包含的域、方法,最后是当前类的属性(可以包含用户自定义的数据)。
    由于需要在运行时动态解析的类、域、方法的符号引用实际都是以字符串常量的形式保存在常量池中,所以常量池实际上占了class文件的很大比重,一般是60%,而byte code一般只占12%。
    常量池中一般包含有如下类型的常量:References to methods, fields and classes, strings, integers, floats, longs, and doubles.(以上参考 2.1 java class file format)
    注意,class文件中,非抽象方法包含有一个属性“Code”,包含有栈帧所需的最大深度、局部变量的个数、byte code指令的数组。当在方法执行过程中发生异常时,JVM会查找异常表(table of exception handlers),该表标记了handler,也就是哪些异常处理代码负责处理具体哪个部分代码所抛出的异常,如果没有合适的handler,异常将会传递给该方法(callee)的调用者(caller)。handler的信息保存在“Code”属性中。(2.3 Method Code)

    实例分析

    (这一部分参考2.6 Code Example)
    首先结合上述的内容,分析以下Java代码编译后所得到的class文件。(将就着看吧,wordpress里一行行调代码缩进实在太麻烦,就没怎么调整~~)

         import java.io.*;
    public class Factorial {
           private static BufferedReader in = new BufferedReader(new
                                     InputStreamReader(System.in));
    public static final int fac(int n) {
             return (n == 0)? 1 : n * fac(n - 1);
           }
    public static final int readInt() {
             int n = 4711;
             try {
             System.out.print(&quot;Please enter a number&gt; &quot;);
             n = Integer.parseInt(in.readLine());
             } catch(IOException e1) { System.err.println(e1); }
             catch(NumberFormatException e2) { System.err.println(e2); }
             return n;
           }
    public static void main(String[] argv) {
             int n = readInt();
             System.out.println(&quot;Factorial of &quot; + n + &quot; is &quot; + fac(n));
           }
         }
    

    fac()方法对应的字节码如下:
     

    0:  iload_0
    1:  ifne            #8
    4:  iconst_1
    5:  goto            #16
    8:  iload_0
    9:  iload_0
    10: iconst_1
    11: isub
    12: invokestatic    Factorial.fac (I)I (12)
    15: imul
    16: ireturn
    LocalVariable(start_pc = 0, length = 16, index = 0:int n)
    

    每条指令的功能这里就不细说,需要注意的是,JVM是一种基于栈的虚拟机,任何操作都需要先将操作数放入栈帧中的操作数栈,然后调用相关方法,操作的结果也会自动的压入操作数栈。在本例中,fac()方法采用了递归的方式计算n!,第12行体现了这一点,在JVM执行到底12行时,会创建一个新的栈帧,以执行新的fac()方法调用。此外,ireturn会将结果返回给当前方法(callee)的调用者(caller),压入到caller栈帧中操作数栈的栈顶。
    readInt()方法的字节码如下:
     

        0:  sipush        4711
     3:  istore_0
     4:  getstatic     java.lang.System.out Ljava/io/PrintStream;
     7:  ldc           &quot;Please enter a number&gt; &quot;
     9:  invokevirtual java.io.PrintStream.print (Ljava/lang/String;)V
     12: getstatic     Factorial.in Ljava/io/BufferedReader;
     15: invokevirtual java.io.BufferedReader.readLine ()Ljava/lang/String;
     18: invokestatic  java.lang.Integer.parseInt (Ljava/lang/String;)I
     21: istore_0
     22: goto          #44
     25: astore_1
     26: getstatic     java.lang.System.err Ljava/io/PrintStream;
     29: aload_1
     30: invokevirtual java.io.PrintStream.println (Ljava/lang/Object;)V
     33: goto          #44
     36: astore_1
     37: getstatic     java.lang.System.err Ljava/io/PrintStream;
     40: aload_1
     41: invokevirtual java.io.PrintStream.println (Ljava/lang/Object;)V
     44: iload_0
     45: ireturn
    Exception handler(s) =
     From    To      Handler Type
     4       22      25      java.io.IOException(6)
     4       22      36      NumberFormatException(10)
    

    这个例子需要说明的是,平时我们调用System.out.println()打印输出时,虽然只是传入要打印的内容,但在字节码层次上,首先需要将System类的静态变量out压入到栈顶,然后将需要打印的内容(这里是字符串“”Please enter a number>”)压入栈,接着调用println()方法。之所以需要将out压入栈,是因为实例方法调用的时候,都会将该实力对象的引用隐式地作为第一个参数(Instance methods always implicitly take an instance reference as their first argument)。
    另外,Java代码中的异常处理代码,try语句不会产生任何实际代码,只是规定了exception handler在哪些代码发生异常时会被调用。

    BCEL API

    BCEL API主要分为以下3个部分(3. The BCEL API):

    • bcel.classfile.*:主要用于查看class文件的结构(尤其是在没有源代码的情况下),一般不用作byte code的修改。
    • bcel.generic.*:动态产生或是修改class文件,可以插入代码、从class文件中剔除无用代码、实现一个Java编译器的代码生成器后端。
    • 其他:代码示例,使用工具

    bcel.classfile.*的类设计如下图:
    javaclass
    通过下面的代码,即可访问一个class文件的JavaClass对象,然后通过这个对象的get/set方法,即可访问这个class文件的各个部分:

    JavaClass clazz = Repository.lookupClass(&quot;java.lang.String&quot;);
    

    比如说,下面的printCode()就能输出String类的所有方法:
     

    System.out.println(clazz);
     printCode(clazz.getMethods());
    ...
    public static void printCode(Method[] methods) {
    for(int i=0; i &lt; methods.length; i++) {
    System.out.println(methods[i]);
    Code code = methods[i].getCode();
    if(code != null) // Non-abstract method
    System.out.println(code);
    }
    }
    

    3.3 ClassGen
    bcel.generic.*的类设计如下图:
    classgen
    要创建一个类,需要使用ClassGen,而要给这个类添加其他部分,则需要使用上图中给出的其他类。在给出具体实例之前,还需要先确定如何表示类型——域需要指明其类型,方法则需要给出参数及其返回值的类型。
    3.3.1 Types
    使用BCEL的Type类用法如下:

    Type   return_type = Type.VOID;
    Type[] arg_types   = new Type[] { new ArrayType(Type.STRING, 1) }
    

    3.3.2 Generic fields and methods
    域需要使用FieldGen来创建,并且需要指定其访问权限。方法则需要添加可能需要的异常、local variables、exception handler。由于包含有byte code的地址的引用,这两者被称之为instruction targeter,这些targeter包含有updateTarget()方法,用于更新目标地址,这个地方是采用Observer模式实现的。一般方法(不是抽象方法)会指向一个instruction list(包含instruction对象),对byte code地址的引用由instruction对象处理,当instruction list更新时,instruction targeter也会被更新。
    每个方法的maximum stack size和maximum number of local variables可以手动或是采用setMaxStack()和setMaxLocals()方法自动设置。
    3.3.3 Instructions
    instruction包含有 opcode(有时叫做tag),字节长度,在byte code中的偏移量。有些指令是不可变的(比如operators),InstructionConstants用于提供预定义的常量供用户使用(flyweight模式)。
    指令分类:按照type hierarchy of instruction classes(附录有)。也可以按照所实现的接口分类。
    重要的指令:branch instructions,比如goto,使得这些指令也可以看做instruction targeter。
    所有指令都可以通过accept(Visitor v)方法来访问(visitor模式)。(3.3.3 Instructions)
    3.3.4 Instruction lists
    instruction list:对instructions的引用不是由直接指向instruction的指针,而是指向instruction handle的指针。这使得添加、插入、删除byte code很方便,同时允许重用不可变的指令对象(flyweight object)。由于使用符号引用,具体的bytecode偏移量的计算直到finalization才计算即可,即用户停止操作bytecode的时候。下文将instruction handle和instruction视为同一概念。instruction handle通过addAttribute()方法可以包含有用户自定义数据。
    append操作
    instructionlist的append方法会返回一个instruction handle,用作branch instruction的目标地址。

    InstructionList il = new InstructionList();
    ...
    GOTO g = new GOTO(null);
    il.append(g);
    ...
    // Use immutable fly-weight object
    InstructionHandle ih = il.append(InstructionConstants.ACONST_NULL);
    g.setTarget(ih);
    

    insert操作
    指令可以插入到现有list的任意位置,需要插入到给定instruction handle的前边,insert方法会返回一个instruction handle用于可能的异常处理的目标地址。

    InstructionHandle start = il.insert(insertion_point,
    InstructionConstants.NOP);
    ...
    mg.addExceptionHandler(start, end, handler, &quot;java.io.IOException&quot;);
    

    delete操作
    需要给出指定范围,该范围内的指令将被删除并释放资源(dispose)。但instruction targeter仍引用着将要被删除的指令时,delete方法可能抛出TargetLostException。用户需要自己处理这些异常。参见附录的窥孔优化的例子。

    try {
    il.delete(first, last);
    } catch(TargetLostException e) {
    InstructionHandle[] targets = e.getTargets();
    for(int i=0; i &lt; targets.length; i++) {
    InstructionTargeter[] targeters = targets[i].getTargeters();
    for(int j=0; j &lt; targeters.length; j++)
    targeters[j].updateTarget(targets[i], new_target);
    }
    };
    

    finalize操作
    当instruction list已经操作完毕、打算生成纯粹的字节码时,所有符号引用被映射为真实的byte code offset,这一操作由getByteCode()完成(默认由Method.getMethod())。生成字节码后用户应该调用dispose()一遍使得这些instrution handle能别重用。

    InstructionList il = new InstructionList();
    ClassGen  cg = new ClassGen("HelloWorld", "java.lang.Object",
    "<generated>", ACC_PUBLIC | ACC_SUPER,
    null);
    MethodGen mg = new MethodGen(ACC_STATIC | ACC_PUBLIC,
    Type.VOID, new Type[] {
    new ArrayType(Type.STRING, 1)
    }, new String[] { "argv" },
    "main", "HelloWorld", il, cp);
    ...
    cg.addMethod(mg.getMethod());
    il.dispose(); // Reuse instruction handles of list
    

    3.3.6 Instruction Factories
    为了简化某些指令的创建,用户可以使用InstructionFactory类创建指令(提供了很多有用的方法用于创建指令),也可以使用compound instruction:当产生byte code,某些“模式”出现的比较频繁,比如算术或是比较运算,可以用一个compound instruction(一个只有单一的一个getInstructionList()方法的接口),这可以用于任何位置,尤其是添加操作。
    例子:将操作数压入栈,用PUSH可以自动产生合适的指令

    InstructionFactory f  = new InstructionFactory(class_gen);
    InstructionList    il = new InstructionList();
    ...
    il.append(new PUSH(cp, "Hello, world"));
    il.append(new PUSH(cp, 4711));
    ...
    il.append(f.createPrintln("Hello World"));
    ...
    il.append(f.createReturn(type));
    

    regular expressions
    可以用正则表达式来搜索特定模式的代码。——org.apache.bcel.util.InstructionFinder的search()方法,找到后会返回一个迭代器,其他的约束条件可以通过code constraint对象来表达。

    CodeConstraint constraint = new CodeConstraint() {
    public boolean checkCode(InstructionHandle[] match) {
    IfInstruction if1 = (IfInstruction)match[0].getInstruction();
    GOTO          g   = (GOTO)match[2].getInstruction();
    return (if1.getTarget() == match[3]) &&
    (g.getTarget() == match[4]);
    }
    };
    InstructionFinder f    = new InstructionFinder(il);
    String            pat = "IfInstruction ICONST_0 GOTO ICONST_1 NOP(IFEQ|IFNE)";
    for(Iterator e = f.search(pat, constraint); e.hasNext(); ) {
    InstructionHandle[] match = (InstructionHandle[])e.next();;
    ...
    match[0].setTarget(match[5].getTarget()); // Update target
    ...
    try {
    il.delete(match[1], match[5]);
    } catch(TargetLostException ex) { ... }
    }
    

    例子:优化boolean表达式

    用BCEL生成HelloWorld

    最后,举个简单的例子,说明具体如何使用BCEL直接生成class文件。比如说,我们现在要使用BCEL生成一个HelloWorld.class文件,实现如下代码的功能:

    try {
     // BCEL Appendix A example
     // ClassGen(String class_name, String super_class_name, String
     // file_name, int access_flags, String[] interfaces)
     ClassGen cg = new ClassGen("HelloWorld", "java.lang.Object",
     "<generated>", ACC_PUBLIC | ACC_SUPER, null);
     ConstantPoolGen cp = cg.getConstantPool();
     InstructionList il = new InstructionList();
    // create main method
     // MethodGen(int access_flags, Type return_type, Type[] arg_types,
     // String[] arg_names, String method_name, String class_name,
     // InstructionList il, ConstantPoolGen cp)
     MethodGen mg = new MethodGen(ACC_STATIC | ACC_PUBLIC, Type.VOID,
     new Type[] { new ArrayType(Type.STRING, 1) },
     new String[] { "argv" }, "main", "HelloWorld", il, cp);
    InstructionFactory factory = new InstructionFactory(cg);
    // define some often used types
     ObjectType i_stream = new ObjectType("java.io.InputStream");
     ObjectType p_stream = new ObjectType("java.io.PrintStream");
     //%%
     //BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
     //%%
     // create variables in and name
     il.append(factory.createNew("java.io.BufferedReader"));
     il.append(InstructionConstants.DUP);
     il.append(factory.createNew("java.io.InputStreamReader"));
     il.append(InstructionConstants.DUP);
     //init input stream
     il.append(factory.createFieldAccess("java.lang.System", "in",
     i_stream, Constants.GETSTATIC));
     il.append(factory.createInvoke("java.io.InputStreamReader",
     "<init>", Type.VOID, new Type[] { i_stream },
     Constants.INVOKESPECIAL));
     il.append(factory.createInvoke("java.io.BufferedReader", "<init>",
     Type.VOID, new Type[] { new ObjectType("java.io.Reader") },
     Constants.INVOKESPECIAL));
     //add in into the local variable pool and get the index automatically
     LocalVariableGen lg = mg.addLocalVariable("in", new ObjectType(
     "java.io.BufferedReader"), null, null);
     int in = lg.getIndex();//index of "in" var
     lg.setStart(il.append(new ASTORE(in)));//store the reference into local variable
     //首先创建对象,并初始化,操作结果在JVM的“堆”里,还需要在本地变量表中创建引用,因此在本地变量表中添加一个“in”比那辆,
     //然后根据索引值调用“astore”指令,即可将对象引用赋值给本地变量
     /*
     0: new #8; //class java/io/BufferedReader
     3: dup
     4: new #10; //class java/io/InputStreamReader
     7: dup
     8: getstatic #16; //Field java/lang/System.in:Ljava/io/InputStream;
     11: invokespecial #20; //Method java/io/InputStreamReader."<init>":(Ljava/io/InputStream;)V
     14: invokespecial #23; //Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
     17: astore_1
     * */
    //%%
     //String name = null;
     //%%
     // create local variable name and init it to null
     lg = mg.addLocalVariable("name", Type.STRING, null, null);
     int name = lg.getIndex();
     il.append(InstructionConstants.ACONST_NULL);//add "null" to the stack top
     lg.setStart(il.append(new ASTORE(name)));//"store" the value of "null" into "name" var
    //%%
     //System.out.print("Please enter your name> ")
     //%%
     // create try_catch block
     InstructionHandle try_start = il.append(factory.createFieldAccess(
     "java.lang.System", "out", p_stream, Constants.GETSTATIC));
     //从常量池中取出“please .....”,压入栈顶:这里感觉有问题,这个字符串常量应该先压入常量池才可以(最好是在这之前加一句,
     //加一句添加常量池操作其实并不影响实际运行的效率)
     il.append(new PUSH(cp, "Please enter your name> "));
     il.append(factory.createInvoke("java.io.PrintStream", "print",
     Type.VOID, new Type[] { Type.STRING },
     Constants.INVOKEVIRTUAL));
     //%%
     //name = in.readLine();
     //%%
     //将本地变量“in”推送至栈顶
     il.append(new ALOAD(in));
     il.append(factory.createInvoke("java.io.BufferedReader",
     "readLine", Type.STRING, Type.NO_ARGS,
     Constants.INVOKEVIRTUAL));//调用readLine()方法
     il.append(new ASTORE(name));//接收的结果在栈顶,需要保存,因此加上保存到“name”slot的指令
    //%%
     // } catch(IOException e) { return; }
     //%%
     GOTO g = new GOTO(null);
     InstructionHandle try_end = il.append(g);
     //add return:如果出异常,才会走到这条“return”指令,并返回到caller中
     InstructionHandle handler = il.append(InstructionConstants.RETURN);
     // add exception handler which returns from the method
     mg.addExceptionHandler(try_start, try_end, handler, null);
    //%%
     //没有异常,继续执行:System.out.println("Hello, " + name);
     //%%
     // "normal" code continues, set the branch target of the GOTO
     InstructionHandle ih = il.append(factory.createFieldAccess(
     "java.lang.System", "out", p_stream, Constants.GETSTATIC));
     g.setTarget(ih);
    // print "Hello":创建一个StringBuffer对象,通过调用StringBuffer的append操作,实现
     //string1 + string2的操作,并且操作结果调用toString方法
     il.append(factory.createNew(Type.STRINGBUFFER));
     il.append(InstructionConstants.DUP);
     il.append(new PUSH(cp, "Hello, "));
     il.append(factory.createInvoke("java.lang.StringBuffer", "<init>",
     Type.VOID, new Type[] { Type.STRING },
     Constants.INVOKESPECIAL));
     il.append(new ALOAD(name));
     il.append(factory.createInvoke("java.lang.StringBuffer", "append",
     Type.STRINGBUFFER, new Type[] { Type.STRING },
     Constants.INVOKEVIRTUAL));
     //
     il.append(factory.createInvoke("java.lang.StringBuffer",
     "toString", Type.STRING, Type.NO_ARGS,
     Constants.INVOKEVIRTUAL));
     il.append(factory.createInvoke("java.io.PrintStream", "println",
     Type.VOID, new Type[] { Type.STRING },
     Constants.INVOKEVIRTUAL));
     il.append(InstructionConstants.RETURN);
    // finalization
     mg.setMaxStack();
     cg.addMethod(mg.getMethod());
     il.dispose();
     cg.addEmptyConstructor(ACC_PUBLIC);
    // dump the class
    cg.getJavaClass().dump("HelloWorld.class");
     System.out.println("dump successly");
     } catch (java.io.IOException e) {
     System.err.println(e);
     } catch (Exception e1) {
     e1.printStackTrace();
     }
    
    展开全文
  • BCEL & Javassist 的介绍

    千次阅读 2007-06-28 17:40:00
    BCEL 介绍:Byte Code Engineering Library (BCEL),这是Apache Software Foundation 的Jakarta 项目的一部分。BCEL是 Java classworking 最广泛使用的一种框架,它可以让您深入 JVM 汇编语言进行类操作的细节。BCEL...
  • 最近看了Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码,网址是...bcel的类库却是不够友好,api文档也是走马观花的点一下,很多函数没有说明。理解基本靠猜,还好有个示范代码。下面对自己理解...
  • 1 异常描述在从 SVN 正常检出项目后,部署完 Maven 及配置完 Tomcat 之后,按常规方法启动... org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 15 此异常,为:C
  • 在springboot配置外部tomcat时候,控制台报错: 严重: Unable to process Jar entry [module-info.class] from Jar [jar:file:/C:/javaWebHbuilder/idea_project/spring-boot-04-web-jsp/target/spring-boot-04-web-...
  • org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool。 此异常,为:ClassFormatException,类格式异常 最后换成Tomcat8,启动项目就好了,这应该是to
  • Tomcat启动时输出以下错误:24-May-2015 14:47:07.441 SEVERE [localhost-startStop-1] org.apache.catalina.startup.ContextConfig.processAnnotationsJar Unable to process Jar entry ...
  • 遇到这个错误基板上就是JDK版本和Tomcat冲突的问题了!!!别问我是怎么知道的,我猜的! 查查本地JDK版本 换JDK或者换Tomcat,反正我就是这么调好的!!!哎,没本事千万不要乱搞开发环境! 一遇到紧急情况急死你...
  • 异常摘要org.aspectj.apache.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 18 at org.aspectj.apache.bcel.classfile.Constant.readConstant(Constant.java:133) at org.aspectj.a.....
  • org.aspectj.apache.bcel.classfile.ClassFormatException Invalid byte tag in constant pool 原因是AOP 包的版本太低,不支持JDK 8新特性,比如lambda 表达式写在Bean 里面了 需要换下版本 AspectJ Maven 地址 ....
  • 出现这个问题,好多人说是web.xml 中servlet配置出错,由于我使用的是 @WebServlet 注解方式,所以从自己创建的servlet配置查找问题, 查找发现是urlPatterns中配置路径缺少 / 导致的,添加后重新编译运行正常...
  • 严重: Unable to process Jar entry [module-info.class] from Jar [jar:file:/D:/tools/Maven/conf/repo/org/projectlombok/lombok/1.18.2/lombok-1.18.2.jar!...org.apache.tomcat.util.bcel.classf...
  • Maven项目运行环境:Tomcat7与struts2-core2.5.16及以上版本+JDK1.7+apache-maven-3.6.1 ...project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:sc...
  • 今天将项目打包,放到tomcat中允许时,...Unable to process Jar entry [COM/ibm/db2os390/sqlj/custom/DB2SQLJCustomizer.class] from Jar [jar:file:/D:/tomcat/tomcat7/apache-tomcat-7.0.30-windows-x64/apache...
  • 一个Springboot项目,更新后报错缺包。接下来,就是疯狂的找这个jar包,最后还是找不到。 在逛帖子时发现一个方向--maven,于是回头看一下JDK,结果发现是JDK11但我记得之前设置为JDK8的,不知道为什么会这样!...
  • 1.老项目基础上重构开发新项目,要求jdk1.7升级到1.8 老项目使用的是jdk1.7+jetty9.2,可以启动 升级后用的是jdk1.8+jetty9.4,启动报错 第一次遇到这个错误,感觉上是jdk升级...org.aspectj.apache.bcel.classfile....
  • 项目之前启动都没这个问题,突然启动时就有这个错误了: 看报错信息有提示从jackson-annotations报出来的。 细想,自己最近也没有动这...1.提高tomcat的版本到tomcat9.x 2.找到包含jdk1.9语法的jar包,降低它的版本...
  • org.aspectj.apache.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 18 原来是 aspectjweaver的版本低了,需要升级到新点的版本,我升级到1.9.0后,果然再启动就好了 ...
  • org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 19 at org.apache.tomcat.util.bcel.classfile.Constant.readConstant(Constant.java:97) at
  • 又是一上午就解决这个破问题,小弟我原本使用tomcat7版本在eclipse上,现在改成myEclipse了 配置我原本的tomcat后只要带上项目的调试就报找不到文件,就是db2jcc.jar或者db2jcc4.jar(具体看你报错的项目路径里写的...
  • pom文件中aspectjweaverjar包,版本较低,升级下便可。
  • 错误内容: Dump file: ajcore.20170410.145149.602....Dump reason: org.aspectj.apache.bcel.classfile.ClassFormatException Dump on exception: true Dump at exit condition: abort ---- Exception Inform
  • ant-apache-bcel.jar.zip

    2019-07-17 14:34:47
    标签:ant-apache-bcel.jar.zip,ant,apache,bcel,jar.zip包下载,依赖包
  • 这次我将用一种很不同的方法操纵字节码——使用 Apache Byte Code Engineering Library (BCEL)。与 Javassist 所支持的源代码接口不同,BCEL 在实际的 JVM 指令层次上进行操作。在希望对程序执行的每一步进行控制时...

空空如也

1 2 3 4 5 ... 20
收藏数 3,404
精华内容 1,361
关键字:

BCEL