`
victorzhzh
  • 浏览: 202084 次
  • 来自: ...
社区版块
存档分类
最新评论

ASM系列之五:操作类方法

阅读更多

前面我们了解了如何使用ASM的CoreAPI来操作一个类的属性,现在我们来看一下如何修改一个类方法。

场景:假设我们有一个Person类,它当中有一个sleep方法,我们希望监控一下这个sleep方法的运行时间:

一般我们会在代码里这样写:

public void sleep() {
    long timer = System.currentTimeMillis();


    try {
	System.out.println("我要睡一会...");
	TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
	e.printStackTrace();
    }
    System.out.println(System.currentTimeMillis()-timer);


}

 标红的两行代码是我们希望有的,但是一般不会将这样的代码和业务代码耦合在一起,所以借助asm来实现动态的植入这样两行代码,就可以使业务方法很清晰。因此我们需要能够修改方法的API,在ASM中提供了对应的API,即MethodAdapter,使用这个API我们就可以随心所欲的修改方法中的字节码,甚至可以完全重写方法,当然这样是没有必要的。下面我们来看一下如何使用这个API,代码如下:

public class ModifyMethod extends MethodAdapter {

	public ModifyMethod(MethodVisitor mv, int access, String name, String desc) {
		super(mv);
	}

	@Override
	public void visitCode() {
		mv.visitFieldInsn(Opcodes.GETSTATIC,
				Type.getInternalName(Person.class), "timer", "J");
		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
				"currentTimeMillis", "()J");
		mv.visitInsn(Opcodes.LSUB);
		mv.visitFieldInsn(Opcodes.PUTSTATIC,
				Type.getInternalName(Person.class), "timer", "J");
	}

	@Override
	public void visitInsn(int opcode) {
		if (opcode == Opcodes.RETURN) {
			mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
					"Ljava/io/PrintStream;");
			mv.visitFieldInsn(Opcodes.GETSTATIC,
					Type.getInternalName(Person.class), "timer", "J");
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
					"currentTimeMillis", "()J");
			mv.visitInsn(Opcodes.LADD);
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
					"println", "(J)V");
		}
		mv.visitInsn(opcode);
	}
}

MethodAdapter类实现了MethodVisitor接口,在MethodVisitor接口中严格地规定了每个visitXXX的访问顺序,如下:

visitAnnotationDefault?( visitAnnotation | visitParameterAnnotation | visitAttribute )*( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |visitLocalVariable | visitLineNumber )*visitMaxs )?visitEnd

首先,统一一个概念,ASM访问,这里所说的ASM访问不是指ASM代码去调用某个类的具体方法,而是指去分析某个类的某个方法的二进制字节码。

在这里visitCode方法将会在ASM开始访问某一个方法时调用,因此这个方法一般可以用来在进入分析JVM字节码之前来新增一些字节码,visitXxxInsn是在ASM具体访问到每个指令时被调用,上面代码中我们使用的是visitInsn方法,它是ASM访问到无参数指令时调用的,这里我们判但了当前指令是否为无参数的return来在方法结束前添加一些指令。

通过重写visitCode和visitInsn两个方法,我们就实现了具体的业务逻辑被调用前和被调用后植入监控运行时间的代码。

 

ModifyMethod类只是对方法的修改类,那如何让外部类调用它,要通过我们上一篇中使用过的类,ClassAdapter的一个子类,在这里我们定义一个ModifyMethodClassAdapter类,代码如下:

public class ModifyMethodClassAdapter extends ClassAdapter {

	public ModifyMethodClassAdapter(ClassVisitor cv) {
		super(cv);
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc,
			String signature, String[] exceptions) {
		if (name.equals("sleep")) {
			return new ModifyMethod(super.visitMethod(access, name, desc,
					signature, exceptions), access, name, desc);
		}
		return super.visitMethod(access, name, desc, signature, exceptions);
	}

	@Override
	public void visitEnd() {
		cv.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC, "timer", "J",
				null, null);
	}

}

 上述代码中我们使用visitEnd来添加了一个timer属性,用于记录时间,我们重写了visitMethod方法,当ASM访问的方法是sleep方法时,我们调用已经定义的ModifyMethod方法,让这个方法作为访问者,去访问对应的方法。

这样两个类就实现了我们要的添加执行时间的业务。

看一下测试类:

public class ModifyMethodTest {
	@Test
	public void modiySleepMethod() throws Exception {
		ClassReader classReader = new ClassReader(
				"org.victorzhzh.common.Person");
		ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		ClassAdapter classAdapter = new ModifyMethodClassAdapter(classWriter);
		classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);

		byte[] classFile = classWriter.toByteArray();

		GeneratorClassLoader classLoader = new GeneratorClassLoader();
		@SuppressWarnings("rawtypes")
		Class clazz = classLoader.defineClassFromClassFile(
				"org.victorzhzh.common.Person", classFile);
		Object obj = clazz.newInstance();
		System.out.println(clazz.getDeclaredField("name").get(obj));
		clazz.getDeclaredMethod("sleep").invoke(obj);
	}
}

 通过反射机制调用我们修改后的Person类,运行结果如下:

zhangzhuo
我要睡一会...
2023

2023就是我们让sleep方法沉睡的时间,看一下我们的原始Person类:

public class Person {
	public String name = "zhangzhuo";

	public void sayHello() {
		System.out.println("Hello World!");
	}

	public void sleep() {
		try {
			System.out.println("我要睡一会...");
			TimeUnit.SECONDS.sleep(2);//沉睡两秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
 

以上几篇文章都是关于ASM的大体介绍,ASM的功能可以说是十分强大,要学好这个东西个人有几点体会:

第一、要熟悉Java字节码结构,及指令:因为我们在很多时候都是要写最原始的字节吗指令的,虽然ASM也为我们提供相应的简化API替我们来做这些事情,但是最基本的东西还是要了解和掌握的,这样才能使用的更好;

第二、充分理解访问者模式有助于我们理解ASM的CoreAPI;

第三、掌握基本的ClassVisitor、ClassAdapter、MethodVisitor、MethodAdapter、FieldVisitor、FieldWriter、ClassReader和ClassWriter这几个类对全面掌握CoreAPI可以有很大的帮助;

第四、在掌握了CoreAPI后再去研究TreeAPI,这样更快速;

最后,希望这几篇文章能对研究ASM的朋友有所帮助

分享到:
评论
6 楼 xwqiang 2015-05-15  
不应该把timer当作static成员变量,如果有多个方法的执行时间要监控就会出问题。
5 楼 xwqiang 2015-05-15  
生成的字节码是错误的:
public class Person
{
  public String name;
  private static long timer;

  public void sayName()
  {
    System.out.println(this.name);
  }

  public void sleep()
  {
    timer -= System.currentTimeMillis();
    try
    {
      System.out.println("我要睡一会...");
      TimeUnit.SECONDS.sleep(2L);
    }
    catch (InterruptedException localInterruptedException)
    {
      localInterruptedException.printStackTrace();
    }
    System.out.println(timer + System.currentTimeMillis());
  }
}
4 楼 kakarottoz 2015-01-15  
您好 这几篇关于ASM的文章都看了 很赞
但现在有个问题不清楚请教一下
我现在想用ASM动态的new一个对象,找到了Opcodes.NEW这个属性,但是不知道怎么创建,使用visitFiledInsn还是visitMethodInsn?或是其他的?
望指教
3 楼 oh_boo 2013-07-11  
  
急求怎么操作修改带有返回值的方法??
我有个子类继承父类,然后重写父类方法,不知道怎么发重写的返回,你的文章里都是void方法
2 楼 Blackbaby 2012-12-06  
你好  想请问下怎么通过asm读取到Annotation默认值?visitAnnotation试了下只能读到被重设的name和value,默认值不会被visit到, 如果你知道麻烦告知下  谢谢
1 楼 sesame 2011-02-10  
卓卓不错~ 
ASM系列文章写的不错,很细,我也跟着学了一把。

期待你继续深入,通过了解ClassLoader机制,真真把ASM在工作使用起来。可以参考CGLIB的源码。

相关推荐

    asm操作指南(中文)

    Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至...

    ASM操作字节码,动态生成Java类class文件

    ASM操作字节码,动态生成Java类class文件,模拟Spring的AOP实现原理。

    ASM 4 Guide 中文版

    在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。ASM相当小巧,并且它有更高的执行效率,是BCEL的7倍,SERP的11倍以上。目前...

    ASM4使用指南

    ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。ASM提供与其他Java字节码...

    ASM4.2 DEMO

    ASM 是java用于操作字节的框架,可以很方便的实现动态创建类,及已有对象的方法重载和更新

    asm-commons-3.3.1.jar

    这可以通过操作类的元数据(如类名、父类、接口等)和字节码指令来实现。 3. 创建和修改方法:可以在运行时动态创建新的方法,或者修改现有的方法。这可以通过操作方法的元数据(如方法名、返回类型、参数类型等)和...

    初学asm的简单例子

    BUF是变量,源操作数为直接寻址 MOV BH,[DI] ;源操作数为寄存器间接寻址 MOV DI,ES:3[SI] ;源操作数为变址寻址,使用跨段前缀 MOV BP,3[BX+SI] ;源操作数为基址加变址寻址 MOV BUFA,DL ;BUFA是一字节变量 MOV [BP...

    asm-tree-3.3.1.jar

    例如,可以使用ASM Tree API遍历一个类的整个字节码结构,查找并修改特定的指令或属性,或者在运行时动态生成新的类和方法。此外,由于ASM Tree API具有较高的抽象层次,因此它适用于各种类型的Java字节码操作,无论...

    asm-analysis-3.2.jar

    asm-analysis-3.2.jar 它包含了一个名为`AsmAnalysis`的类,这个类是ASM库的一部分,主要用于Java字节码的分析和修改。这个工具基于访问者模式实现,可以对字节码进行各种操作和分析。 ASM库提供了一套基于树API的...

    asm-master.zip

    ASM是一个通用的Java字节码操作和分析框架。 它可以用于修改现有类或直接以二进制形式动态生成类。 ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。 ASM提供与其他Java字节码...

    asm-utils:在i386 gnu汇编语言中为FreeBSD,macOS和GNULinux实现的一些UNIX实用程序

    asm-utils asm-utils是一小部分精选的Unix实用程序的集合,这些实用程序以32位x86汇编语言(AT&T语法)实现,适用于FreeBSD,NetBSD,macOS(<= Mojave 10.14)和GNU / Linux。该项目是出于教育目的而开始的,...

    底层编程语言:汇编语言asm.zip

    - **无高级抽象**:缺乏高级语言中的类、对象、函数库等高级抽象概念,所有编程结构如循环、条件分支等都需要手工实现。 - **依赖特定硬件**:汇编程序直接依赖于特定处理器的指令集、寄存器组织和寻址模式,更换...

    fastquery:FastQuery(快速数据库查询的方法)基于Java语言。他的使命是:简化Java操作数据层。做为一个开发者,只需只需要设计DAO接口即可,在项目初始化阶段采用ASM生成好实现类

    在项目初始化阶段采用ASM生成好字节码,因此支持编译前预备,可替换减少运行期的错误,显着提升程序的强壮性 支持安全查询,防止SQL注入 支持与主流数据库连接池框架集成 支持@Query查询,使用@Cond

    世界上最小的操作系统:MenuetOS

    MenuetOS 并不是一个类Unix操作系统,它完全由 Asm语言编写的系统。Menuet 不基于当前任何一款流行的操作系统而运作,主要是为在开发过程中避免复杂的编程及各种不可预料的 Bug。 尽管Menuet是完全用 32位汇编写成的...

    高效Java数据层操作框架FastQuery源码

    通过在项目初始化阶段运用ASM技术动态生成DAO接口的实现类,开发者仅需专注于接口设计。这一机制不仅极大提高了代码的简洁性与优雅性,而且显著提升了开发效率,使得数据层的操作变得更加高效和便捷。

    LocalOS(Java与汇编的混合操作类)

    因为没写过外挂,手里没有相关类库,所以用什么写都一样,嫌分析封包麻烦并且也没时间,想用Java写个汇编类,然后调用游戏本身指令的外挂.目的有二:一是为了巩固相关的Java和汇编知识,二是强调下在软件世界中,Java...

    asmsupport:该项目可以帮助开发人员在运行时简单地创建或修改类。 它基于ASM框架,并提供了像Java struct这样的高级API

    Java字节码操作类库asmsupport是一个字节码操作类库,它能够让程序员非常简单的在动态创建和修改类,该框架是基于开发的,不同与asm的是,它避免了直接操作jvm指令,栈和局部变量。更多内容可见模块模块描述...

    mcmod:基于 ObjectWeb ASM 的 Minecraft mod 框架

    相反,它是一个经过修改的 Minecraft 加载器,它使用 ObjectWeb ASM 字节码操作框架在运行时修改其类(为标识的字段注入“get”和“set”方法),允许使用易于使用的 API 构建自定义修改对于最终用户。 论坛主题

    易的类指针API方式操作

    为您提供易的类指针API方式操作下载,易的类指针API方式操作系统结构:GetObjectPtr_CTest,SetObjectPtr_CTest,写到内存_整数,取指针内容_整数,取变量地址_整数型,GetClassObject_API,GetClassObject_ASM,Set,Get,...

    世界上最小的图形操作系统1.1M Menuet OS,类Linux(纯汇编写的)

    MenuetOS 并不是一个类Unix操作系统,它完全由 Asm语言编写的系统。Menuet 不基于当前任何一款流行的操作系统而运作,主要是为在开发过程中避免复杂的编程及各种不可预料的 Bug。 尽管Menuet是完全用 32位汇编写成的...

Global site tag (gtag.js) - Google Analytics