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

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 6.0 工具集

    通过ASM-TREE,开发者可以方便地遍历和分析类、字段和方法的结构,进而进行更高级别的操作,比如代码分析、性能优化或静态代码检查。 **ASM-ANALYSIS**是分析工具包,包含了一些用于字节码分析的工具和类。它可以...

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

    1. **ClassWriter**: 这个类负责生成字节码,它是ASM的核心组件之一。通过向ClassWriter发送指令,我们可以构建出完整的类结构,包括类名、父类、接口、字段和方法。 2. **ClassReader**: 这个类用于读取已有的字节...

    asm操作指南(中文)

    ### asm操作指南(中文)知识点总结 #### 一、ASM框架简介 - **定义与功能**:ASM是一个Java字节码操纵框架,主要用于动态生成类或增强现有类的功能。通过直接生成二进制`.class`文件,ASM能够在类被加载到Java...

    asm-all-3.3

    4. **ASM Analysis**:提供了分析类和方法的工具,例如计算变量和操作数栈的使用情况,这在优化代码或检查数据流时非常有用。 5. **ASM Util**:包含了一些实用工具类,帮助开发者处理常见的任务,如类型转换、访问...

    ASM7使用指南.pdf

    - **基于事件的模型**:通过一系列事件来表示类的各个组成部分,如类标头、字段、方法声明、指令等。该模型下,库定义了一系列事件及其发生的顺序,并提供了相应的类分析器和写入器。 - **基于对象的模型**:采用...

    asm-util.jar.zip

    它可能包含了一系列用于操作或分析Java字节码的方法和工具。 2. "asm-2.2.1.jar.license.txt" - 这个文件是ASM库2.2.1版本的许可证文本,通常包含了关于软件许可协议的信息,比如它是遵循什么开源协议,用户可以如何...

    asm4.0_RC1-bin

    2. **创建ClassVisitor**:ClassVisitor是ASM的核心接口,它定义了一系列访问类结构的方法。开发者可以通过实现这个接口来处理类的各个部分,如字段、方法等。 3. **遍历和修改类结构**:通过调用ClassVisitor的...

    ASM字节码操作简单实例

    ASM字节码库是Java字节码操作的强大工具,它允许程序员在运行时动态生成类或者增强已有类的功能。在本实例中,我们将探讨如何利用ASM实现简单的面向切面编程(AOP)功能,这是一种在不修改源代码的情况下,添加额外...

    asm + cglib demo

    1. **ASM 示例**: 使用ASM创建一个新的类,或者修改已有的类,例如添加新的字段、方法,或者在已有方法中插入额外的逻辑。 2. **CGlib 示例**: 创建一个代理对象,该代理对象会在目标方法调用前后插入自定义的行为,...

    asm-giude阅读笔记

    - **Core API**:基于事件的模型,将Java类表示为一系列事件,每个事件对应类的一个元素,如类头、字段、方法等。这种模式类似于XML的SAX解析。 - **Tree API**:基于对象的模型,将Java类视为一个对象树,每个...

    ASM 字节码修改工具中文帮助手册

    - 字段和方法表示:包括 `FieldVisitor` 和 `MethodVisitor` 等接口,用于访问和操作类的字段和方法。 - **树 API**: - 类表示:通过 `AsmClass` 等类来表示和操作类结构。 - 方法表示:通过 `AsmMethod` 等类来...

    ASM函数监听实现(二)之打印注入函数的参数值

    这篇博客"ASM函数监听实现(二)之打印注入函数的参数值"深入探讨了如何利用ASM库来监控并记录函数调用时的参数值,这对于调试、日志记录以及性能分析等场景非常有用。 首先,我们来看`asmAopClassAdapter.java`。...

    Android-Android无痕埋点框架使用ASM插桩实现

    ASM是一个低级别的库,可以直接操作Java字节码,提供了一种动态生成和修改类的方法。它通过ClassWriter、ClassReader、MethodVisitor等接口,允许开发者对字节码进行读取、修改和写入。ASM的核心在于它的事件驱动...

    asm 3.3.1 jar

    ASM是一个流行的Java字节码操作和分析框架,主要用于动态生成类或者增强已有类的功能。ASM 3.3.1 版本是这个框架的一个特定发行版,它在2009年发布,提供了对Java字节码的低级别操作能力。在本文中,我们将深入探讨...

    ASM4.2 DEMO

    ASM提供了一系列的基础访问器,如ClassVisitor、MethodVisitor和FieldVisitor,它们遵循了一种访问者模式,使得我们可以方便地遍历和操作类结构。 在ASM 4.2中,一个主要的改进是对Java 8的支持,包括对lambda...

    asm-util-4.0_RC1.jar.zip

    标题中的"asm-util-4.0_RC1.jar.zip"是一个归档文件,它是一个ZIP格式的压缩包,其中包含了ASM工具库的一个特定版本——ASM Util 4.0 Release Candidate 1(RC1)。ASM是一个Java字节码操控和分析框架,广泛用于动态...

    ASM4使用指南 ASM GUIDE

    2. **基本概念**:在ASM4中,主要涉及的概念包括模型、类、对象、接口、操作、消息传递等。模型是系统的一种抽象表示,类定义了对象的属性和行为,对象是类的实例,接口描述了对象间通信的方式,操作是对象能执行的...

    asm官方代码文档

    4. **字节码操作**:ASM提供了大量的OpCode类,对应于Java字节码指令。通过这些类,开发者可以直接生成或修改字节码,实现低级别的程序逻辑。 5. **动态代理**:ASM可以用来创建动态代理类,实现接口或扩展特定类。...

Global site tag (gtag.js) - Google Analytics