当前位置: 首页 > news >正文

浙江省建设工程招投标网站涉密网络建设

浙江省建设工程招投标网站,涉密网络建设,wordpress中文社区,最近的国际新闻热点0. 相关分享 ASM字节码处理工具原理及实践#xff08;一#xff09; 上一篇讲了ASM的简介、导入#xff0c;以及字节码文件结构#xff0c;并给出了ASM通过ClassVisitor对class进行访问的基础实战。本篇将进入MethodVisitor#xff0c;尝试对方法进行访问、生成、转换。…0. 相关分享 ASM字节码处理工具原理及实践一 上一篇讲了ASM的简介、导入以及字节码文件结构并给出了ASM通过ClassVisitor对class进行访问的基础实战。本篇将进入MethodVisitor尝试对方法进行访问、生成、转换。方法的代码存储为字节码指令序列。在此之前我们需要先复习JVM栈结构才能更好地理解方法中字节码指令的逻辑。 1. JVM栈结构 一个JVM栈中包含了若干个栈帧表征着一个个方法的调用栈。一个栈帧中存储着 局部变量表Local Variables操作数栈Operand Stack动态链接Dynamic Linking指向运行时常量池的方法引用MethodRef)方法返回地址Return Address方法正常退出或异常退出的定义附加信息 每个线程都有自己各自的栈栈是线程私有的。栈帧的大小主要由局部变量表和操作数栈决定。操作数栈的深度、局部变量表的长度在编译器就已经确定并写入到字节码中。如果栈帧只展示局部变量表和操作数栈一个执行堆栈可能会为如下形式 1.1 局部变量表 局部变量表也称为 局部变量数组 或 本地变量表。最基本的存储单元是 Slot 变量槽参数值的存放总是在局部变量数组的 index 0 开始直到 数组长度-1 的索引结束。 局部变量表中32位以内的类型占用一个 slot 64位的类型long、double占用两个 slot。上图中的 L1、L2 等都忽略了slot的个数可能L2是double类型那么它应当占用 2 个 slot。局部变量表的长度按 slot 的个数计算。 1.2 操作数栈 独立的栈帧除了包含有局部变量表之外还包含一个后进先出的操作数栈Operand Stack。操作数栈在方法执行的执行过程中根据字节码指令往栈中写入数据或者提取数据即入栈push和出栈pop 某些字节码指令将值压入操作数栈其余的字节码指令将操作数栈取出栈使用它们之后再把执行结果压入栈。 与局部变量表类似的栈中可以是任意类型的Java数据类型 32位的类型占用一个栈单位深度64位的类型占用两个栈单位深度 2. 字节码指令 字节码指令由操作码 opcode 和操作数 arguments 表征 操作码 opcode 是一个无符号的字节值是代号由助记符标识。操作数 arguments 是定义精确指令行为的静态值。 根据字节码指令和操作数栈的关系字节码指令可以分为两类一类字节码指令设计用于将值从局部变量表转移到操作数栈中反之亦然另一类则只操作操作数栈它们从操作数栈弹出一些值根据这些值进行计算将结果重新压入操作数栈栈顶。 例如 ILOAD、LLOAD、FLOAD、DLOAD和ALOAD指令读取一个局部变量的值并将这个值压入到操作数栈中。由于这些指令操作的是局部变量表需要提供表/数组的索引 i 作为参数来表示读取哪一个局部变量。我们之前提到32位以内的类型都存入到一个 slot 中反过来提取的时候这里 ILOAD 可以用于加载 boolean、byte、char、short和int类型的局部变量。不仅如此我们也说了 64位 的局部变量将会占用两个插槽 slot 故LLOAD、DLOAD加载数据时候实际上加载了 i 和 i1 两个插槽的内容。ALOAD 用于加载其他类型比引用类型、数组引用类型等。 我们可以观察到 xLOAD和 xSTORE指令都是由x表征类型的者用来确保不进行非法的转换。 除了上述 xLOAD 和 xSTORE 的指令之外其他字节码指令只在操作数栈上工作。这里给出字节码指令的汇总 注意 a 和 b 表示 int、float、long、double类型o 和 p 表示 对象引用类型v 表示单位为1的类型w 表示long、double这样单位为2的类型i 和 j 和 n 表示 int 类型 2.1 Local variables 局部变量 指令栈(原先)栈指令执行后ILOAD,LLOAD,FLOAD,DLOAD var……, aALOAD var……, oISTORE,LSTORE,FSTORE,DSTORE var…, a…ASTORE var…, o…IINC var incr…… 示例 【局部变量压栈指令】将一个局部变量加载到操作数栈xload 、xload_n其中x为i、l、f、d、a n从0到3 aload_0 // 将局部变量表中0号局部变量的值压入到操作数栈 aload 5 // 将局部变量表中5号局部变量的值压入操作数栈【出栈装入局部变量表指令】将一个数值从操作数栈存储到局部变量表xstore、xstore_n其中x为i、l、f、d、an为0到3xastore其中x为i、l、f、d、a、b、c、s 2.2 Stack 操作数栈 指令栈原先栈指令执行后POP…, v…POP2…, v1, v2……, w…DUP…, v…, v , vDUP2…, v1, v2…, v1, v2, v1, v2…, w…, w, wSWAP…, v1, v2…, v2, v1DUP_X1…, v1, v2…, v2, v1, v2DUP_X2…, v1, v2, v3…, v3, v1, v2, v3…, w, v…, v, w, vDUP2_X1…, v1, v2, v3…, v2, v3, v1, v2, v3…, v, w…, w, v, wDUP2_X2…, v1, v2, v3, v4…, v3, v4, v1, v2, v3, v4…, w, v1, v2…, v1, v2, w, v1, v2…, v1, v2, w…, w, v1, v2, w…, w1, w2…, w2, w1, w2 2.3 Constants 常量操作 指令栈原先栈指令执行后ICONST_n (−1 ≤ n ≤ 5…… , nLCONST_n (0 ≤ n ≤ 1)…… , nLFCONST_n (0 ≤ n ≤ 2)…… , nFDCONST_n (0 ≤ n ≤ 1)…… , nDBIPUSH b, −128 ≤ b 127…… , bSIPUSH s, −32768 ≤ s 32767…… , sLDC cst (int, float, long, double, String or Type)…… , cstACONST_NULL…… , null 【常量入栈指令】将一个常量加载到操作数栈bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_i、lconst_l、fconst_f、dconst_d 示例 ldc指令可以接受一个8位的参数指向常量池中int、float或者String的索引并将指定的内容压入操作数栈 例如ldc #9 , 常量池表#9为“hello”将这个字符串索引压入栈中 ldc_w 把常量池中的项压入栈w表示使用宽索引款缩影支持索引范围大于ldc接收两个8位的参数 ldc2_w 把常量池中 long 或者 double 类型的项压入栈 aconst_null 把null压入操作数栈 fconst_0 把浮点数0压入栈2.4 Arithmetic and logic 计算和逻辑运算 指令栈原先栈指令执行后IADD,LADD,FADD,DADD…, a, b…, abISUB,LSUB,FSUB,DSUB…, a, b…, a- bIMUL, LMUL, FMUL, DMUL… , a , b… , a * bIDIV, LDIV, FDIV, DDIV… , a , b… , a / bIREM, LREM, FREM, DREM… , a , b… , a % bINEG, LNEG, FNEG, DNEG (negtive)… , a… , -aISHL, LSHL (left)… , a , n… , a nISHR, LSHR (right)… , a , n… , a nIUSHR, LUSHR… , a , n… , a nIAND, LAND… , a , b… , a bIOR, LOR… , a , b… , a | bIXOR, LXOR… , a , b… , a ^ bLCMP… , a , b… , a b ? 0 : (a b ? -1 : 1)FCMPL, FCMPG… , a , b… , a b ? 0 : (a b ? -1 : 1)DCMPL, DCMPG… , a , b… , a b ? 0 : (a b ? -1 : 1) 2.4 Cast类型转换 指令栈原先栈指令执行后I2B… , i… , (byte) iI2C… , i… , (char) iI2S… , i… , (short) iL2I, F2I, D2I… , a… , (int) aI2L, F2L, D2L… , a… , (long) aI2F, L2F, D2F… , a… , (float) aI2D, L2D, F2D… , a… , (double) aCHECKCAST class… , o… , (class) o 从byte、char、short类型到int类型的宽化类型转换实际上是不存在的虚拟机对这种类型转换并没有做实质性的转化处理只是通过操作数栈交换了两个数据。 窄化数据转换可能发生精度丢失可能丢失掉几个最低有效位上的值转换后的浮点数值根据IEEE754最接近含入模式所得到的正确整数值。窄化类型转换可能会发生上限溢出、下限溢出和精度丢失等情况。 2.5 Objects对象、Field字段、Method方法 c: class类, f: field字段名, m:method方法名, t: description描述符 指令栈原先栈执行后NEW class……, new classGETFIELD c f t…, o… , o.fPUTFIELD c f t… , o , v…GETSTATIC c f t…… , c.fPUTSTATIC c f t… , v…INVOKEVIRTUAL c m t… , o , v1 , … , vn… , o.m(v1, … vn)INVOKESPECIAL c m t… , o , v1 , … , vn… , o.m(v1, … vn)INVOKESTATIC c m t… , v1 , … , vn… , c.m(v1, … vn)INVOKEINTERFACE c m t… , o , v1 , … , vn… , o.m(v1, … vn)INVOKEDYNAMIC m t bsm… , o , v1 , … , vn… , o.m(v1, … vn)INSTANCEOF class… , o… , o instanceof classMONITORENTER… , o…MONITOREXIT… , o… 2.6 Arrays集合 指令栈原先栈指令执行后NEWARRAY type (for any primitive type)… , n… , new type[n]ANEWARRAY class… , n… , new class[n]MULTIANEWARRAY […[t n… , i1 , … , in… , new t[i1]…[in]…BALOAD, CALOAD, SALOAD… , o , i… , o[i]IALOAD, LALOAD, FALOAD, DALOAD… , o , i… , o[i]AALOAD… , o , i… , o[i]BASTORE, CASTORE, SASTORE… , o , i , j…IASTORE, LASTORE, FASTORE, DASTORE… , o , i , a…AASTORE… , o , i , p…ARRAYLENGTH… , o… , o.length 示例 public class Student {public String[] infos;public String getInfo(int index){return infos[index];} }其中 getInfo(int index) 的字节码指令为: 0 aload_0 // 先将 this 压入栈 1 getfield #2 asmcore/base/Student.infos //拿到class下的infos变量这是个 [Ljava/lang/String 类型的 4 iload_1 //将局部变量表 1号 变量 index 压入栈 5 aaload //..., o, i - ..., o[i] 6 areturn //将 o[i] 返回出去由于 getInfo 是非静态方法所以局部变量表的0号局部变量为this引用 2.7 Jumps 跳转指令 指令栈原先说明IFEQ… , ijump if i 0IFNE… , ijump if i ! 0IFLT… , ijump if i 0IFGE… , ijump if i 0IFGT… , ijump if i 0IFLE… , ijump if i 0IF_ICMPEQ… , i , jjump if i jIF_ICMPNE… , i , jjump if i ! jIF_ICMPLT… , i , jjump if i jIF_ICMPGE… , i , jjump if i jIF_ICMPGT… , i , jjump if i jIF_ICMPLE… , i , jjump if i jIF_ACMPEQ… , o , pjump if o pIF_ACMPNE… , o , pjump if o ! pIFNULL… , ojump if o nullIFNONNULL… , ojump if o ! nullGOTO…jump alwaysTABLESWITCH… , ijump alwaysLOOKUPSWITCH… , ijump always 示例 public class Student {public String[] infos;public String getInfo(int index){if (index 0 ){return nothing;}else{return infos[index];}} }编译后代码优化为 public class Student {public String[] infos;public Student() {}public String getInfo(int index) {return index 0 ? nothing : this.infos[index];} }其中 getInfo(int index) 字节码指令为 0 iload_1 //将1号局部变量index压入栈1 ifge 7 (6) //如果栈顶元素大于0跳转到7行4 ldc #2 nothing //从常量池中将 nothing 压入栈6 areturn //将栈顶 “nothing” 返回出去7 aload_0 //如果栈顶元素大于0来到这里将0号局部变量this压入栈8 getfield #3 asmcore/base/Student.infos //拿到其field 11 iload_1 //将1号局部变量index压入栈 12 aaload //...,o,i - ..., o[i] 13 areturn //将栈顶 o[i] 返回出去在 ASM 中字节码可以表示为 public getInfo(I)Ljava/lang/String; //方法L0LINENUMBER 7 L0 //记录行号ILOAD 1 //获取1号int类型局部变量 index 压入栈IFGE L1 //如果 index 0 跳转到 L1L2LINENUMBER 8 L2 //记录行号LDC nothing //将常量 nothing 压入栈ARETURN //栈顶返回引用类型对象L1LINENUMBER 10 L1 //记录行号FRAME SAME //帧ALOAD 0 //获取0号引用类型局部变量 this 压入栈GETFIELD asmcore/base/Student.infos : [Ljava/lang/String; //拿到 this 的String[]类型的 infos 字段ILOAD 1 //获取1号int类型局部变量 index 压入栈AALOAD //...,o,i - ..., o[i]ARETURN //栈顶返回引用类型对象L3LOCALVARIABLE this Lasmcore/base/Student; L0 L3 0 //局部变量表中变量this索引为0LOCALVARIABLE index I L0 L3 1 //局部变量表中index变量索引为1MAXSTACK 2 //操作数栈深度MAXLOCALS 2 //局部变量表长度2.8 Return 返回指令 指令栈说明IRETURN, LRETURN, FRETURN, DRETURN… , a返回数据类型ARETURN… , o返回引用类型RETURN…返回类型为voidATHROW…, o抛出异常结束执行 3. MethodVisitor 与 ClassVisitor类似MethodVisitor也有访问回调顺序 visitAnnotationDefalt 最多一次 ( visitAnnotation | visitParameterAnnotation | visitAttribute ) 零或多次 ( visitCode (visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn | visitLocalVariable | visitLineNumber) 零或多次 visitMaxs ) 最多一次 visitEnd 固定一次 MethodVisitor 的相关方法为: abstract class MethodVisitor { // public accessors ommitedMethodVisitor(int api);MethodVisitor(int api, MethodVisitor mv);AnnotationVisitor visitAnnotationDefault();AnnotationVisitor visitAnnotation(String desc, boolean visible);AnnotationVisitor visitParameterAnnotation(int parameter,String desc, boolean visible);void visitAttribute(Attribute attr);void visitCode();void visitFrame(int type, int nLocal, Object[] local, int nStack,Object[] stack);void visitInsn(int opcode);void visitIntInsn(int opcode, int operand);void visitVarInsn(int opcode, int var);void visitTypeInsn(int opcode, String desc);void visitFieldInsn(int opc, String owner, String name, String desc);void visitMethodInsn(int opc, String owner, String name, String desc);void visitInvokeDynamicInsn(String name, String desc, Handle bsm,Object... bsmArgs);void visitJumpInsn(int opcode, Label label);void visitLabel(Label label);void visitLdcInsn(Object cst);void visitIincInsn(int var, int increment);void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels);void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels);void visitMultiANewArrayInsn(String desc, int dims);void visitTryCatchBlock(Label start, Label end, Label handler,String type);void visitLocalVariable(String name, String desc, String signature,Label start, Label end, int index);void visitLineNumber(int line, Label start);void visitMaxs(int maxStack, int maxLocals);void visitEnd(); }我们发现annotations 和 attributes 必须要首先访问然后再访问方法的方法体。按照上述的访问顺序进行访问。visitCode 和 visitMaxs 表征着当前来到方法体的开始、结束位置。和 ClassVisitor 类似 visitEnd 在最后被调用表征着访问事件的结束。 我们并不能直接访问到方法而是需要通过一些 MethodVisitor 相关的API 来访问 首先通过 ClassReader 的 accept 方法来开启访问事件将事件传递给 ClassVisitor当访问到方法时ClassVisitor会被调用 visitMethod 方法这个方法返回了一个 MethodVisitor实例接下去进入这个 MethodVisitor进行方法的访问事件的回调MethodVisitor 也把它接收到的所有方法转发给另一个 MethodVisitor 实例所以它也可以被看做是一个事件过滤器。 最后通常还是会来到事件消费者 ClassWriter当它接收到 MethodVisitor 传来的方法访问细节时可能会面临计算帧、计算局部变量表、操作数栈大小的问题。 ASM 为我们提供了几种选择 new Classwriter(0):不做任何自动化计算程序员必须自己计算帧、局部变量表和操作数栈的大小new ClassWriter(ClassWriter.COMPUTE_MAXS):自动计算局部变量表和操作数栈的大小你仍然必须调用 visitMaxs但它的参数将不被使用。new ClassWriter(ClassWriter.COMPUTE_FRAMES):所有东西都被自动计算你无需调用 visitFrame但你仍然必须调用 visitMaxs参数将忽略不被使用 自动计算有好处就是方便了程序员的开发但是它会带来性能损耗官方给出使用 COMPUTE_MAXS 会让性能降低 10% 使用 COMPUTE_FRAMES 会让性能降低 20% 。 如果我们要自行计算帧我们要使用 visitFrame(F_NEW, nLocals, locals, nStack, stack)其中 nLocals 和 nStack 是局部变量表和操作数栈的大小 locals 和 stack 是相应的集合。自动计算帧的时候可能会加载父类到JVM并通过反射的手段做一些事情如果你正在生成的几个类相互之间有关联可能关联的类此时还不存在会出现自动计算错误。官方提示可以通过重写 getCommonSuperClass方法来解决这个问题。 3.1 生成一个 Method 方法 - ClassWriterMethodVisitor 假设我们当前有一个类 public class Bean{public int f; }我要加一个给f设置值的方法 public class Bean{public int f;//添加一个方法public void checkAndSet(int f){if(f 0){this.f f;}else{throw new IllegalArgumentException();}} }我们可以在把访问事件分发给 ClassWriter 的时候模拟分发一个原本不存在的MethodVisitor的访问事件从而实现让 ClassWriter 添加一个方法的效果首先根据 ASMPlugin 查看字节码形式或者 jclasslib查看也行 使用ASMPlugin查看 public checkAndSet(I)VL0LINENUMBER 8 L0ILOAD 1IFLT L1L2LINENUMBER 9 L2ALOAD 0ILOAD 1PUTFIELD asmcore/base/Bean.f : IGOTO L3L1LINENUMBER 11 L1FRAME SAMENEW java/lang/IllegalArgumentExceptionDUPINVOKESPECIAL java/lang/IllegalArgumentException.init ()VATHROWL3LINENUMBER 13 L3FRAME SAMERETURNL4LOCALVARIABLE this Lasmcore/base/Bean; L0 L4 0LOCALVARIABLE f I L0 L4 1MAXSTACK 2MAXLOCALS 2使用 jclasslib 查看 0 iload_11 iflt 12 (11)4 aload_05 iload_16 putfield #2 asmcore/base/Bean.f9 goto 20 (11) 12 new #3 java/lang/IllegalArgumentException 15 dup 16 invokespecial #4 java/lang/IllegalArgumentException.init 19 athrow 20 return 根据 ASMPlugin 的结果我们来尝试构建MethodVisitor大概的代码结构分析字节码其逻辑大概是判断传入参数 f 是否非负如果是继续执行字节码如果不是跳转到另一个代码块跳转到另一个label/跳转到另一个代码段起始位置。需要注意 visitCode为方法代码的开始标志visitEnd为方法代码的结束标志。 mv.visitCode();//模拟访问代码开始 mv.visitVarInsn(ILOAD, 1);//变量操作将传入参数f压入操作数栈 Label label new Label(); mv.visitJumpInsn(IFLT,label);//跳转标志跳转判断的数字为操作数栈顶元素如果符合条件跳入else{}代码块的label标识 //如果上述判断成功继续执行下面字节码指令 mv.visitVarInsn(ALOAD, 0);//把this压入栈 mv.visitVarInsn(ILOAD, 1);//把传入参数f压入操作数栈局部变量表中并没有把f剔除所以可以多次使用例如压入操作数栈 mv.visitFieldInsn(PUTFIELD,asmcore/base/Bean,f,I);//访问field字段Bean类的f字段描述符为I意为int类型数据 Label end new Label(); mv.visitJumpInsn(GOTO, end);//if(){}的代码块执行完成进入returnreturn这块代码由end标识 //来到else{}代码块的label部分 mv.visitLabel(label);//打上label标签 mv.visitFrame(F_SAME, 0, null, 0, null);//自行计算帧 mv.visitTypeInsn(NEW,java/lang/IllegalArgumentException);//类相关new一个对象 mv.visitInsn(DUP);//在操作数栈中复制一份栈顶元素 mv.visitMethodInsn(INVOKESPECIAL,java/lang/IllegalArgumentException,init,()V);//取出栈顶元素调用这个类的构造函数得到的对象放入操作数栈顶 mv.visitInsn(ATHROW);//无参指令ATHROW将栈顶对象作为异常对象抛出 //来到return代码块的label部分 mv.visitLabel(end); mv.visitFrame(F_SAME, 0, null, 0, null);//自行计算帧 mv.visitInsn(RETURN);//直接返回 mv.visitMaxs(2,2);//局部变量表、操作数栈的深度计算 mv.visitEnd();//方法的代码段结束 我们来实践一下 ClassReader读取 Bean 类将事件传递给 ClassVisitor其中这个ClassVisitor的visitEnd()中模拟转发这个方法访问事件的转发交给ClassWriter去消费/记录 首先我们写一个 AddMethodAdapter 用来添加模拟转发方法事件且将模拟转发方法访问事件设计在ClassVisitor的 visitEnd 调用返回之前完成。 public class AddMethodAdapter extends ClassVisitor {public AddMethodAdapter(ClassVisitor downstream) {super(ASM4,downstream);}Overridepublic void visitEnd() {//模拟添加一个方法MethodVisitor mv super.visitMethod(0x1,checkAndSet,(I)V,null,new String[]{});mv.visitCode();//模拟访问代码开始mv.visitVarInsn(ILOAD, 1);//变量操作将传入参数f压入操作数栈Label label new Label();mv.visitJumpInsn(IFLT,label);//跳转标志跳转判断的数字为操作数栈顶元素如果符合条件跳入else{}代码块的label标识 //如果上述判断成功继续执行下面字节码指令mv.visitVarInsn(ALOAD, 0);//把this压入栈mv.visitVarInsn(ILOAD, 1);//把传入参数f压入操作数栈局部变量表中并没有把f剔除所以可以多次使用例如压入操作数栈mv.visitFieldInsn(PUTFIELD,asmcore/base/Bean,f,I);//访问field字段Bean类的f字段描述符为I意为int类型数据Label end new Label();mv.visitJumpInsn(GOTO, end);//if(){}的代码块执行完成进入returnreturn这块代码由end标识 //来到else{}代码块的label部分mv.visitLabel(label);//打上label标签mv.visitFrame(F_SAME, 0, null, 0, null);//自行计算帧mv.visitTypeInsn(NEW,java/lang/IllegalArgumentException);//类相关new一个对象mv.visitInsn(DUP);//在操作数栈中复制一份栈顶元素mv.visitMethodInsn(INVOKESPECIAL,java/lang/IllegalArgumentException,init,()V,false);//取出栈顶元素调用这个类的构造函数得到的对象放入操作数栈顶mv.visitInsn(ATHROW);//无参指令ATHROW将栈顶对象作为异常对象抛出 //来到return代码块的label部分mv.visitLabel(end);mv.visitFrame(F_SAME, 0, null, 0, null);//自行计算帧mv.visitInsn(RETURN);//直接返回mv.visitMaxs(2,2);//局部变量表、操作数栈的深度计算mv.visitEnd();//方法的代码段结束super.visitEnd();} }接下去就可以按我们的老套路开始利用这个代码增强的性质进行链式转发 ClassWriter cw new ClassWriter(0); AddMethodAdapter addMethodAdapter new AddMethodAdapter(cw); try {ClassReader cr new ClassReader(asmcore.base.Bean);cr.accept(addMethodAdapter,0);byte[] b cw.toByteArray();//将byte[]写入文件save(b,Bean); } catch (IOException e) {e.printStackTrace(); }最后我们查看生成的字节码文件的反编译结果 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) //package asmcore.base;public class Bean {public int f;public Bean() {}public void checkAndSet(int var1) {if (var1 0) {this.f var1;} else {throw new IllegalArgumentException();}} } 符合我们的预期。 3.2 转变方法 Transforming Methods 除了增加、删除一个方法我们可能还要对原有方法进行修改例如在原有代码的基础上进行代码增强。我们尝试在方法的开头和结束位置加上一个执行时间的记录。假设我们有一个工具类其中代码增强部分为该方法的耗时计算 public class Util {//耗时操作时间记录public void doSomething() throws Exception {//----增加的代码begin----long begin System.currentTimeMillis();//----增加的代码end----Thread.sleep(100);//----增加的代码begin----System.out.println(System.currentTimeMillis() - begin);//----增加的代码end----} }我们来看一下这个字节码是什么样的先来看一下没有增加代码的时候的情况 public doSomething()V throws java/lang/Exception L0LINENUMBER 8 L0LDC 100INVOKESTATIC java/lang/Thread.sleep (J)VL1LINENUMBER 10 L1RETURNL2LOCALVARIABLE this Lasmcore/base/Util; L0 L2 0MAXSTACK 2MAXLOCALS 1然后是增加代码之后的情况在其中标注出了新增加的内容 public doSomething()V throws java/lang/Exception L0//新代码LINENUMBER 7 L0//新增加了方法调用INVOKESTATIC java/lang/System.currentTimeMillis ()J//将返回值存入var1(也就是 begin 这个本地变量)LSTORE 1L1//原有代码LINENUMBER 8 L1LDC 100INVOKESTATIC java/lang/Thread.sleep (J)VL2//新代码LINENUMBER 9 L2//获取PrintStream对象GETSTATIC java/lang/System.out : Ljava/io/PrintStream;//调用System.currentMillisINVOKESTATIC java/lang/System.currentTimeMillis ()J//将begin的值存入操作数栈LLOAD 1//操作数栈减法LSUB//调用打印传入参数就一个就是操作数栈栈顶元素INVOKEVIRTUAL java/io/PrintStream.println (J)VL3//原有代码LINENUMBER 10 L3RETURNL4//局部变量表信息LOCALVARIABLE this Lasmcore/base/Util; L0 L4 0LOCALVARIABLE begin J L1 L4 1MAXSTACK 5MAXLOCALS 3由于这里引入了新的局部变量所以我们需要处理局部变量表这部分我们直接使用 LocalVariablesSorter 这个封装好的 MethodVisitor 的实现类帮我们处理局部变量。ClassWriter 使用 ClassWriter.COMPUTE_MAXS 来自动计算局部变量表。其他部分尽量使用原生API保持与上文描述一致 public class AddTimerAdapter extends ClassVisitor {public AddTimerAdapter(ClassVisitor downstream) {super(ASM4,downstream);}//如果是 doSomething 方法就对这个方法进行代码增强Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {if (name.equals(doSomething)){return new TimerMethodVisitor(ASM4,access,descriptor,cv.visitMethod(access, name, descriptor, signature, exceptions));}return cv.visitMethod(access, name, descriptor, signature, exceptions);}public static class TimerMethodVisitor extends LocalVariablesSorter {int beginIndex;protected TimerMethodVisitor(int api, int access, String descriptor, MethodVisitor methodVisitor) {super(api, access, descriptor, methodVisitor);}Overridepublic void visitCode() {//在原来MethodVisitor的基础上在代码开始的地方插入内容mv.visitCode();//调用System.currentMillis方法mv.visitMethodInsn(INVOKESTATIC,java/lang/System,currentMillis,()J,false);//由于这里无法处理局部变量表、操作数栈的下标所以需要让Writer自己去做//借助 AdviceAdapter 帮我们封装好新增局部变量的方法进行新增局部变量beginIndex newLocal(Type.LONG_TYPE);//index为本地变量下标//存入操作数栈mv.visitVarInsn(LSTORE,beginIndex);}//在方法退出之前计算时间Overridepublic void visitInsn(int opcode) {//需要注意的是owner给的是类descriptor是描述符//如果是return或者是throw exception就提前打印时间if ((opcode IRETURN opcode RETURN ) || opcode ATHROW){//获取printStream对象存入操作数栈mv.visitFieldInsn(GETSTATIC,java/lang/System,out,Ljava/io/PrintStream;);//调用System.currentMillis方法mv.visitMethodInsn(INVOKESTATIC,java/lang/System,currentMillis,()J,false);//将begin的值存入操作数栈mv.visitVarInsn(LLOAD,beginIndex);//操作数栈减法mv.visitInsn(LSUB);//调用打印mv.visitMethodInsn(INVOKEVIRTUAL,java/io/PrintStream,println,(J)V,false);}super.visitInsn(opcode);}Overridepublic void visitMaxs(int maxStack, int maxLocals) {//其实这里没有用了因为ClassWriter设置了 COMPUTE_MAXSsuper.visitMaxs(maxStack2, maxLocals);}} }我们来调用一下查看结果 ClassWriter cw new ClassWriter(ClassWriter.COMPUTE_MAXS); AddTimerAdapter addTimerAdapter new AddTimerAdapter(cw); try {ClassReader cr new ClassReader(asmcore.base.Util);cr.accept(addTimerAdapter,0);byte[] b cw.toByteArray();save(b,Util); } catch (IOException e) {e.printStackTrace(); }运行后得到的字节码为 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) //package asmcore.base;public class Util {public Util() {}public void doSomething() throws Exception {long var1 System.currentMillis();Thread.sleep(100L);System.out.println(System.currentMillis() - var1);} }符合预期。
http://www.yingshimen.cn/news/1118/

相关文章:

  • 网站可以做匿名聊天吗西安企业名录大全
  • 客户网站开发全流程网站建设的步骤图
  • 怎样建设档案馆网站电子商务网站的设计要素
  • 定制公司网站docker创建wordpress
  • 个人网站主页模板江苏省建设工程招标网官网
  • 西宁高端网站制作佛山专业做企业网站
  • 电商网站开发文献综述科技布是什么面料
  • 玩具网站模板wordpress浏览量显示
  • 网络设计网站建设类网站模板全球云邮登陆网站
  • 手工做火枪的网站sem是什么意思呢
  • wordpress转换为html5长沙网站优化seo
  • 学做php网站有哪些优秀的交互设计作品集
  • 杭州网站设计我选柚v米科技北京住房与城乡建设部网站
  • 响应式网站设计制作手工大全
  • 网站seo重庆厦门seo大佬
  • 网站建设公司发展规划硬件开发工程师简历
  • 工业信息化部网站备案查询上海怎么制作网站
  • 电子商务网站推广计划免费做动态图片的网站
  • 网站技术的解决方案前端开发培训机构济南七里河
  • 深圳坪山网站制作公司广州排名seo公司
  • 网络公司给销售公司做网站长沙3合1网站建设价格
  • 保山做网站建设网页设计作业html代码大全
  • 企业网站策划怎么样烟台网站制作培训
  • 龙岗网站建设开发设计公司做一个页面多少钱
  • 浙江网站推广公司网站建设的会计核算
  • 企业高端网站建设美工绍兴企业做网站
  • 杭州网站设计步骤怎样进入国外网站
  • wordpress改站点地址奇人网站
  • dw怎么做网站跳转临安网站建设
  • .net 创建网站项目钱网站制作