ASM核心API-方法
AMS4使用指南
实战java虚拟机
方法结构-示例
类文件如下:
public class Bean {private int f;public int getF() {return f;}public void setF(int f) {this.f = f;}
}
使用javap -verbose xx/xx/Bean
,查看字节码指令,观察方法区:
构造方法
如果程序没有显示的定义构造函、器,
编译器会自动生成一个默认的构造器Bean(){super();}
0: aload_0 #this
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
getF()方法
Xreturn,ireturn表示int, 如果是引用类型为areturn ,void则为return;
0: aload_0
1: getfield #2 // Field f:I
4: ireturn ###ireturn == return int ;
setF()方法
0: aload_0 #### this
1: iload_1 ### 入参 f;
2: putfield #2 // Field f:I
5: return ###return void
抛出异常
增加一个稍微复杂的方法,抛出异常:
public void checkAndSetF(int f) {if (f >= 0) {this.f = f;} else {throw new IllegalArgumentException();}}
字节码指令如下:
0: iload_1
1: iflt 12 ###如果栈顶值<=0,则跳转至label标记指定的指令,否则顺序执行
4: aload_0
5: iload_1
6: putfield #2 // Field f:I
9: goto 20 ####无条件跳转
###创建一个异常对象,并压入栈顶。
12: new #3 // class java/lang/IllegalArgumentException
15: dup ####栈顶值再入栈一次,此时栈顶前2位都是同一个值
###invokespecial 弹出栈顶元素,调用其构造函数,此时栈顶值仍然是异常对象
16: invokespecial #4 // Method java/lang/IllegalArgumentException."<init>":()V
19: athrow ###弹出剩下的异常的副本,
20: return
异常处理try-catch
增加try-catch方法:
public static void sleep(long d) {try {Thread.sleep(d);} catch (InterruptedException e) {e.printStackTrace();}
}
字节码指令如下:
0: lload_0
1: invokestatic #5 // Method java/lang/Thread.sleep:(J)V
4: goto 20 ####正常执行至末尾return
7: astore_2
8: aload_2
9: invokevirtual #7 // Method java/lang/InterruptedException.printStackTrace:()V
12: goto 20
15: astore_2
16: aload_2
17: invokevirtual #9 // Method java/lang/Exception.printStackTrace:()V
20: return
Exception table:
###from-to偏移区间 出现type的异常,跳转至target
from to target type0 4 7 Class java/lang/InterruptedException0 4 15 Class java/lang/Exception
不存在用于捕获异常的字节代码: 而是将一个方法的字节代码与一个异常处理器列表关联在一起,这个列表规定了在某方法中一给定部分抛出异常时必须执行的代码。
MethodVisitor
用于生成和变转已编译方法
的都是基于 MethodVisitor 抽象类的,它由 ClassVisitor.visitMethod()
方法返回。
public abstract class MethodVisitor {AnnotationVisitor visitAnnotationDefault();AnnotationVisitor visitAnnotation(String desc, boolean visible);AnnotationVisitor visitParameterAnnotation(int parameter,String desc, boolean visible);void visitAttribute(Attribute attr);//..................void visitCode(); //方法字节码开始
// void visitXXXInsn(int opcode);void visitLabel(Label label);void visitTryCatchBlock(Label start, Label end, Label handler,String type);void visitMaxs(int maxStack, int maxLocals); //设置局部变量,栈帧大小void visitEnd(); //方法字节码结束
}
使用MethodVisitor .visitXXXInsn()来填充函数,添加方法实现的字节码
- visitVarInsn(int opcode, int var)
:带有参数的字节码指令
- visitInsn(int opcode)
: 无参数的字节码指令
- visitLdcInsn(Object cst)
: LDC专用指令。LDC_W,LDC2_W已被废弃
- visitTypeInsn(int opcode, String type)
:带有引用类型参数的字节码指令
- visitMethodInsn(int opcode, String owner, String name,String desc)
:调用方法
- visitFieldInsn(int opcode, String owner, String name, String desc)
:操作变量
MethodVisitor 类的方法必须按以下顺序调用(在这个类的 Javadoc 中规定):
visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )*---对于非抽象方法,如果存在注释和属性的话,必须首先访问
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |visitLocalVariable | visitLineNumber )*
visitMaxs---visitCode与visitMaxs成对出现,出现?次。
)?
visitEnd
ClassWriter 选项
MethodVisitor.visitMaxs(int ,int)
为一个方法计算局部变量和栈帧大小并不是一件容易的事。好在在创建ClassWriter 时,通过设置选项,使ASM为我们完成这些计算。
- 在使用
new ClassWriter(0)
时,不会自动计算任何东西。必须自行计算帧、局部变量与操作数栈的大小。 - 在使用
new ClassWriter(ClassWriter.COMPUTE_MAXS)
时,将为你计算局部变量与操作数栈部分的大小。还是必须调用 visitMaxs,但可以使用任何参数:它们将被忽略并重新计算。使用这一选项时,仍然必须自行计算这些帧。 - 在使用
new ClassWriter(ClassWriter.COMPUTE_FRAMES)
时,一切都是自动计算。不再需要调用 visitFrame,但仍然必须调用 visitMaxs(参数将被忽略并重新计算)。
这些选项的使用很方便,但有一个代价: COMPUTE_MAXS 选项使 ClassWriter 的速度降低
10%
,而使用 COMPUTE_FRAMES 选项则使其降低一半。
生成方法
普通方法
- 普通方法的入参中第一个参数是
this
。也就是说它的args_size最小是1
,而静态方法是0 - 定义静态方法必须要
ACC_STATIC
- 调用普通方法
INVOKEVIRTUAL
,调用static方法:INVOKESTATIC
//普通方法 empty()
mv = cw.visitMethod(ACC_PRIVATE, "empty", "()V", null, null);//静态static emptyStatic()
mv = cw.visitMethod(ACC_PRIVATE|ACC_STATIC, "emptyStatic", "()V", null, null);//implements java.lang.AutoCloseable.close()
mv = cw.visitMethod(ACC_INTERFACE, "close", "()V", null, new String[]{"java/lang/Exception"});
运行结果:
private void empty() {
}private static void emptyStatic() {
}void close() throws Exception {
}
构造方法
调用构造函数时,需要调用父类的构造函数或者类内部其他构造函数。
如果没有明显指定调用父类自定义的构造方法,那么编译器会调用默认的父类构造方法super()。
但是如果要调用父类自定义的构造方法,要在子类的构造方法中明确指定。
无参构造方法
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD,0); //visitVarInsn获取变量 this
mv.visitMethodInsn(INVOKESPECIAL,"java/lang/Thread","<init>","()V");//自动添加:调用父类的构造函数
mv.visitInsn(RETURN);
mv.visitMaxs(1,1);
mv.visitEnd();
有参构造方法
可以使用super()
或者this():因为上面的无参的构造函数中调用了super()
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;I)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD,0); // this
mv.visitMethodInsn(INVOKESPECIAL,clazzPath,"<init>","()V"); //调用自身的无参的构造函数,
mv.visitVarInsn(ALOAD,0); // this
mv.visitVarInsn(ALOAD,1); //获取入参 name
mv.visitFieldInsn(PUTFIELD,clazzPath,"name","Ljava/lang/String;");
mv.visitVarInsn(ALOAD,0); //
mv.visitVarInsn(ILOAD,2); //获取入参 age
mv.visitFieldInsn(PUTFIELD,clazzPath,"age","I");
mv.visitInsn(RETURN);
mv.visitMaxs(2,3);
mv.visitEnd();
调用结果
public User() {//如果没有显式的调用父类构造方法,会默认调用父类无参的构造函数super()
}public User(String var1, int var2) {this(); //间接调用 无参构造函数 中的 super();this.name = var1;this.age = var2;
}
静态构造方法<clinit>
即静态语句块static {}
mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitLdcInsn(1234L);
mv.visitMethodInsn(INVOKESTATIC,"java/lang/Long","valueOf","(J)Ljava/lang/Long;"); //Long.valueOf(long);自动装箱
mv.visitFieldInsn(PUTSTATIC,clazzPath,"clazzId","Ljava/lang/Long;");
mv.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
mv.visitLdcInsn("static{} invoked...");
mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
mv.visitMaxs(2,0); //long 占用2单位slot
mv.visitEnd();
期望运行结果
static {clazzId = 1234L;System.out.println("static{} invoked...");
}
实际运行结果:
public static Long clazzId = Long.valueOf(1234L);
static {System.out.println("static{} invoked...");}
转换方法
方法可以像类一样进行转换,也就是使用一个方法适配器将它收到的方法调用转发出去,并进行一些修改:
- 改变参数可用于改变各具体指令;
- 不转发某一收到的调用将删除一条指令;
- 在接收到的调用之间插入调用,将增加新的指令。
例:删除方法中的 NOP指令(因为它们不做任何事情,所以删除它们没有任何问题)
a).RemoveNopAdapter继承MethodVisitor
public class RemoveNopAdapter extends MethodVisitor {public RemoveNopAdapter(MethodVisitor mv) {super(ASM5,mv);}@Overridepublic void visitInsn(int opcode) {if(opcode == NOP){return ;}super.visitInsn(opcode);}
}
b).RemoveNopClassAdapter继承ClassVisitor
public class RemoveNopClassAdapter extends ClassVisitor {public RemoveNopClassAdapter(ClassVisitor classVisitor) {super(ASM5, classVisitor);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);if (mv != null) {//调用MethodAdaptermv = new RemoveNopAdapter(mv);}return mv;}
}
此时,调用程序图如下:
例1:为类增加安全控制
public class Account {public void operation() {System.out.println("operation.....");}
}
目前Accout.operation()
没有任何控制手段,现希望给operation()增加一些安全校验,判断这个方法是否有权限执行这个方法,如果有,则执行该方法;如果没有,直接退出。
增加安全校验方法:
public class SecurityChecker {public static boolean checkSecurity() {System.out.println("SecurityChecker.checkSecurity.....");/*** 简单模拟“安全校验”* 当前系统时间,尾数如果是偶数,则结果是1,如果是奇数则结果是0*/if ((System.currentTimeMillis() & 0x1) == 0) {return false;} else {return true;}}
}
即将原有的Account转换为如下形式:
public class Account {public void operation() throws InterruptedException{if(SecurityChecker.checkSecurity()){System.out.println("operation.....");} }
}
方法适配器AddSecurityCheckMethodAdapter
class AddSecurityCheckMethodAdapter extends MethodVisitor {public AddSecurityCheckMethodAdapter(MethodVisitor mv) {super(Opcodes.ASM5, mv);}@Overridepublic void visitCode() {Label continueLabel = new Label();visitMethodInsn(INVOKESTATIC,"asm/security/SecurityChecker","checkSecurity","()Z");//IFNE i != 0 时跳转,即true的时候跳转;visitLabel-->continueLabel处,继续执行visitJumpInsn(IFNE,continueLabel); //如果checkSecurity=false,则不发生跳转,顺序执行,下一条指令直接返回visitInsn(RETURN);visitLabel(continueLabel);super.visitCode();}
}
类适配器TimeStatClassAdapter
class AddSecurityCheckClassAdapter extends ClassVisitor {public AddSecurityCheckClassAdapter(ClassVisitor cv) {super(Opcodes.ASM5, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);MethodVisitor wrappedMv = mv;//当遇到operation方法时,if(mv != null && name.equals("operation")){wrappedMv = new AddSecurityCheckMethodAdapter(mv);}return wrappedMv;}
}
例2:统计函数执行时间
统计Accout.operation()方法的执行时间:
public class Account {public void operation() throws InterruptedException{System.out.println("operation.....");Thread.sleep(new Random().nextInt(1000));}
}
我们需要统计每个方法花费的时间,需要加入如下统计功能:
public class TimeStat {static ThreadLocal<Long> t = new ThreadLocal<>();public static void start() {t.set(System.currentTimeMillis());}public static void end(){long end = System.currentTimeMillis();System.out.println(Thread.currentThread().getStackTrace()+" spend:" +(end - t.get()));}
}
即将原有的Account转换为如下形式:
public class Account {public void operation() throws InterruptedException{TimeStat.start();System.out.println("operation.....");Thread.sleep(new Random().nextInt(1000));TimeStat.end();}
}
为了了解可以如何在 ASM 中实现它,可以编译这两个类,并针对这两个版本比较TraceClassVisitor 的输出(或者是使用默认的 Textifier 后端,或者是使用 ASMifier后端)。
TraceClassVisitor tcv = new TraceClassVisitor(cw, new ASMifier(),new PrintWriter(System.out));
方法适配器TimeStatMethodAdapter
class TimeStatMethodAdapter extends MethodVisitor {public TimeStatMethodAdapter(MethodVisitor mv) {super(Opcodes.ASM5, mv);}@Overridepublic void visitCode() {//方法执行开始,增加TimeStat.start();visitMethodInsn(INVOKESTATIC, "asm/timer/TimeStat", "start", "()V");super.visitCode();}@Overridepublic void visitInsn(int opcode) {/* int IRETURN = 172; // visitInsnint LRETURN = 173; // -int FRETURN = 174; // -int DRETURN = 175; // -int ARETURN = 176; // -int RETURN = 177; // -*///方法返回前,增加TimeStat.end();if (opcode >= IRETURN && opcode <= RETURN) {visitMethodInsn(INVOKESTATIC, "asm/timer/TimeStat", "end", "()V");}super.visitInsn(opcode);}
}
类适配器TimeStatClassAdapter
class TimeStatClassAdapter extends ClassVisitor {public TimeStatClassAdapter(ClassVisitor cv) {super(Opcodes.ASM5, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);MethodVisitor wrappedMv = mv;//判断,如果是operation方法时,使用适配方法if (mv != null && name.equals("operation")) {wrappedMv = new TimeStatMethodAdapter(mv);}return wrappedMv;}
}
工具
- Type:
t.getOpcode(IMUL),若 t 等于 Type.FLOAT_TYPE,则返回 FMUL
- TraceClassVisitor它打印它所访问类的文本表示,
包括类的方法的文本表示
,可以使用TraceMethodVisitor
代替TraceClassVisitor
public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) {MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);if (debug && mv != null && ...) { // 如果必须跟踪此方法Printer p = new Textifier(ASM5) {@Override public void visitMethodEnd() {print(aPrintWriter); // 在其被访问后输出它}};mv = new TraceMethodVisitor(mv, p);}return new MyMethodAdapter(mv);
}
- CheckClassAdapter来检验ClassVisitor 方法的调用顺序是否适当,参数是否有效,与之类似的,
CheckMethodAdapter
来检查一个方法,而不是检查整个类。 - AnalyzerAdapter:这个方法适配器根据 visitFrame 中访问的帧,计算每条指令之前的栈映射帧。
LocalVariablesSorter
这个方法适配器将一个方法中使用的局部变量按照它们在这个方法中的出现顺序重新进行编号。在向一个方法中插入新的局部变量
时,这个适配器很有用。没有这个适配器,就需要在所有已有局部变量之后
添加新的局部变量,但遗憾的是,在 visitMaxs 中,要直到方法的末尾处才能知道这些局部变量的编号。
假设需要一个这样局部变量来记录方法的执行时间:
public class Account {public static long timer;public void operation() throws Exception {long t = System.currentTimeMillis();System.out.println("operation.....");Thread.sleep(100);timer += System.currentTimeMillis() - t;}
}
那么MethodAdapter
class TimeStatMethodAdapter2 extends MethodVisitor {private LocalVariablesSorter lvs;private AnalyzerAdapter aa;private String owner;private int time;private int maxStack;public TimeStatMethodAdapter2(String owner,MethodVisitor mv,LocalVariablesSorter lvs , AnalyzerAdapter aa) {super(ASM5, mv);this.owner = owner;this.lvs = lvs;//由于java是单继承,所以这里setthis.aa = aa;}@Override public void visitCode() {mv.visitCode();mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");time = lvs.newLocal(Type.LONG_TYPE); //newLocal()mv.visitVarInsn(LSTORE, time);maxStack = 4;}@Override public void visitInsn(int opcode) {if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");mv.visitVarInsn(LLOAD, time);mv.visitInsn(LSUB); //submv.visitFieldInsn(GETSTATIC, owner, "timer", "J");mv.visitInsn(LADD);mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");maxStack = Math.max(aa.stack.size() + 4, maxStack);}mv.visitInsn(opcode);}@Overridepublic void visitMaxs(int maxStack, int maxLocals) {mv.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals);}
}
AdviceAdapter
这个方法适配器是一个抽象类, 可用于在一个方法的开头以及恰在任意 RETURN 或 ATHROW指令之前插入代码。
public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {protected void onMethodEnter(){}protected void onMethodExit(int opcode) {};
}
ASM核心API-方法相关推荐
- 【核心API开发】Spark入门教程[3]
本教程源于2016年3月出版书籍<Spark原理.机制及应用> ,在此以知识共享为初衷公开部分内容,如有兴趣,请支持正版书籍. Spark综合了前人分布式数据处理架构和语言的优缺点,使用简 ...
- hibernate教程--常用配置和核心API详解
一.Hibernate的常用的配置及核心API. 1.1 Hibernate的常见配置: 1.1.1.核心配置: 核心配置有两种方式进行配置: 1)属性文件的配置: * hibernate.prop ...
- hibernate教程--常用配置和核心API
一.Hibernate的常用的配置及核心API. 1.1Hibernate的常见配置: 1.1.1.核心配置: 核心配置有两种方式进行配置: 1)属性文件的配置: * hibernate.proper ...
- hibernate框架学习第二天:核心API、工具类、事务、查询、方言、主键生成策略等...
核心API Configuration 描述的是一个封装所有配置信息的对象 1.加载hibernate.properties(非主流,早期) Configuration conf = new Conf ...
- EXT核心API详解(二)-Array/Date/Function/Number/String
EXT核心API详解(二)-Array/Date/Function/Number/String Array类 indexOf( Object o ) Number object是否在数组中,找不到返 ...
- 核心API最佳实践——JDK日志分级
核心API最佳实践--JDK日志分级 时间:2005-10-29 08:00 来源:网管之家bitsCN.com 字体:[大 中 小] 日志(Log)是什么?字典对其的解释是"对某种机器工作 ...
- JavaEE基础(02):Servlet核心API用法详解
本文源码:GitHub·点这里 || GitEE·点这里 一.核心API简介 1.Servlet执行流程 Servlet是JavaWeb的三大组件之一(Servlet.Filter.Listener) ...
- Spring Security系列教程11--Spring Security核心API讲解
前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...
- Spring Security系列教程-Spring Security核心API讲解
前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...
- Activiti6.0 (三)核心API
核心API RepositoryService: 负责对流程定义文件的管理,操作一些静态文件(流程xml.流程图片),获取部署对象和资源对象 RunTimeService: 对流程进行控制,可用于启动 ...
最新文章
- modelsim 自动化 独立仿真vivado ip核工程
- 数学建模之运筹学问题
- RTX Server SDK跨服务器
- iOS学习之iOS沙盒(sandbox)机制和文件操作复习
- 开关变压器绕制教程_教程:如何将变压器权重和令牌化器从AllenNLP上传到HuggingFace
- 【iOS - 周总结】开发中遇到的小知识点(2018.12.10-2018.12.15)
- CVPR2020 | 谷歌提出多目标(车辆)跟踪与检测框架 RetinaTrack
- 数据科学和人工智能技术笔记 六、日期时间预处理
- Android MediaPlayer播放raw资源封装类
- pdf安装 adobe acrobat reader DC
- Realtek显示芯片方案设计 RTD2270 RTD2281 RTD2513 RTD2525 RTD2556 RTD2785 RTD2795T 2796 VGA DVI HDMI DP转LVDS
- 数据仓库与数据挖掘复习题目
- lg android tv遥控器,LG TV Remote遥控器
- 毛[文强]老师的一堂前端课程
- 人工智能(强化学习)
- 为什么OpenCV计算的帧率是错误的?
- DDL、DML介绍及常用语句
- arduino 水位传感器_Arduino教程┃数字传感器和模拟传感器的区别
- NT、WDM、WDF驱动概念
- 科技云报道:科技感拉满,盘盘这届冬奥会的虚拟数字人
热门文章
- 使用电脑浏览器获取狗东账号cookie
- 安卓修改gps定位模拟百度地图移动
- pe系统清除服务器密码吗,雨林木风pe系统登录密码清除工具使用教程?
- TongWeb7微服务适配方案
- PLSQL 64位的安装
- 重邮大学计算机基础考试试题及答案,重庆邮电大学《大学计算机基础(2015》考试试卷.pdf...
- android app hilink,HUAWEI HiLink
- win10怎么卸载linux小红帽,win10下使用Linux(ubuntu18.04)
- linux c语言 utf8转gbk,GBK(gb2312)和UTF-8互转(转载)
- 【Semantic segmentation】Scratching the surface