`
xiefeifeihu
  • 浏览: 99257 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

用ASM实现AOP

阅读更多

AOP的关键在于拦截,如果在代码中直接写入要插入的代码则是最直接的AOP。这当然不是指在source中生写代码,而是希望在程序员不知觉的情况下修改了代码。

asm是个开源包,可以很方便地读写class的bytecode。网站是http://asm.ow2.org/。为了方便修改类建议下载Eclipse插件。

使用方法挺简单。首先实现一个ClassAdapter导出类,找到要修改的函数:

public class SOClassAdapter extends ClassAdapter {

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

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc,
			String signature, String[] exceptions) {
		if (shouldModify(name, parasFieldName(name))) {
			MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
					exceptions);
			return new ModifyMethodAdapter(mv);
		}
		return super.visitMethod(access, name, desc, signature, exceptions);
	}

略。

在visitMethod中找到要修改的函数后,通过实现一个MethodAdapter的导出类修改函数,例子如下:

public class ModifyMethodAdapter extends MethodAdapter {

	public ModifyMethodAdapter(MethodVisitor mv) {
		super(mv);
	}

	@Override
	public void visitInsn(int opcode) {
		if (opcode == Opcodes.RETURN) {
			visitVarInsn(Opcodes.ALOAD, 0);
			// visitVarInsn(Opcodes.ALOAD, 1);
			visitMethodInsn(Opcodes.INVOKESTATIC, "asm/TopClass", "print",
					"(Lasm/TopClass;)V");// (Lasm/Bean;)V
		}
		super.visitInsn(opcode);
	}
}
它在函数return前加了一句:asm.TopClass.print(this)静态函数。

最后通过ClassReader读入类,ClassAdapter访问并修改类的字节码,通过ClassWriter回写类:

		ClassReader cr = new ClassReader("asm/Bean");//这是通过系统的ClassLoader加载类
		ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		ClassAdapter claAdapter = new SOClassAdapter(cw);
		cr.accept(claAdapter, ClassReader.SKIP_DEBUG);
		byte[] data = cw.toByteArray();//得到修改后的字节码

可以将修改后的类回写到class文件中:

		String path = c.getResource(c.getSimpleName() + ".class").getPath();
		File file = new File(path);

		FileOutputStream fout = new FileOutputStream(file);
		fout.write(data);
		fout.close();
过程就这么简单。这里的难点在于如何修改类。有一个小技巧就是先在source中临时改写类,再用asm的Eclipse插件看类的bytecode,将其copy到MethodAdapter即可。这样就不必花精力了解java类的字节码。

到此我们已经知道如何去修改类了,但是怎么用呢,这个问题折腾了我好几天。最终确定了两个较简单的方案:

方法一:编译后重写class文件

可以写一个main方法,寻找要修改的类,然后用ClassAdapter重定义类,得到byte[],再用FileOutputStream把字节流写入文件。这个操作完成之后剩下的就是如何把编译、改类、打包、运行一系列流程整合起来。我们用的编译工具是maven:

在pom文件里加一个插件:

			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>exec-maven-plugin</artifactId>
				<version>1.1</version>
				<executions>
					<execution>
						<phase>main</phase>
						<goals>
							<goal>java</goal>
						</goals>
					</execution>
				</executions>
				<configuration>
					<mainClass>asm.Redefine</mainClass>
					<argument>-classpath</argument>
					<classpath />
				</configuration>
			</plugin>
编译打包时执行命令:“clean compile exec:java package”即可。

方法二:运行时在内存中重定义类

这种方法需要用到java5的Instrumentation。首先写一个premain方法:

public class PreMain {

	public static Instrumentation inst;

	public static void premain(String agentArgs, Instrumentation inst) {
		PreMain.inst = inst;
		System.out.println("获取inst----" + inst);
	}

}

将Instrumentation实例保存到static变量中。然后将PreMain类打成jar包(如agent.jar),在MANIFEST.MF中添加一下内容:

Manifest-Version: 1.0
Class-Path: .
Premain-Class: asm.PreMain
Can-Redefine-Classes: true

在虚拟机启动时添加参数:-javaagent:路径/agent.jar。tomcat是在catalina.bat/catalina.sh中加上一句:“SET JAVA_OPTS=%JAVA_OPTS% -javaagent:%CATALINA_HOME%/lib/agent.jar”
然后在自己的类中通过反射获取Instrumentation实例,在用它的redefineClasses重定义类:

			Class<?> premainClass = Class
					.forName("asm.PreMain");
			Field field = premainClass.getField("inst");
			Instrumentation inst = (Instrumentation) field.get(premainClass
					.newInstance());
			log.info("从外部获取的Instrumentation(如果为null,则不会重定义类)----" + inst);
			if (null == inst)
				return;

			List<ClassDefinition> definitions = redefineClass();
			inst.redefineClasses(definitions
					.toArray(new ClassDefinition[definitions.size()]));
 

这两种方法适用于不同的场合,可酌情选择使用。

有一点需要注意:ClassReader读取类的时候,如果用new ClassReader(“类名”)则是从系统ClassLoader中读类,这样很可能找不到类
。Tomcat的是WebappClassLoader。不要紧,ClassReader还提供了一个构造函数:
public ClassReader(final InputStream is) throws IOException。
可以将读好的类的字节流传进来。获取字节流的方式可以用
clazz.getResourceAsStream(clazz.getSimpleName() + ".class")
或者clazz.getClassLoader.getResourceAsStream。
分享到:
评论

相关推荐

    深入字节码 -- 使用 ASM 实现 AOP1

    使用 ASM 实现 AOP 的关键步骤包括: 1. **分析字节码**:首先,我们需要解析目标类的字节码,了解其方法结构,找到要插入切面代码的位置。 2. **创建 ClassWriter 对象**:ASM 提供了 ClassWriter 类,用于创建或...

    AOP 的利器 ASM 3.0

    下面通过一个具体的示例来展示如何使用ASM进行AOP的实现: 1. **创建一个安全检查类**: ```java public class SecurityChecker { public static void checkSecurity() { System.out.println("SecurityChecker....

    AOP 的利器:ASM 3_0 介绍

    AOP 的利器:ASM 3_0 介绍,很好的书

    Java字节码实现Aop

    总结来说,Java字节码实现AOP是一种高效且灵活的技术手段,通过ASM、CGLIB等字节码工具,可以在运行时动态地修改类的行为,实现面向切面编程。理解并掌握这项技术,对于提升Java开发效率和代码质量具有重要意义。

    JavaEE CGLIB字节码增强方式实现AOP编程

    JavaEE CGLIB字节码增强技术是实现面向切面编程(AOP)的一种常见方法。在JavaEE应用中,AOP允许开发者定义“切面”,这些切面封装了跨越多个对象的横切关注点,如日志、事务管理等。CGLIB(Code Generation Library...

    深入字节码 -- 玩转 ASM-Bytecode1

    在描述中提到,前一篇博文中介绍了使用ASM实现AOP(面向切面编程),而在这一篇中,作者打算进一步介绍如何使用ASM-Bytecode工具。 首先,为了便于分析和调试字节码,作者推荐安装Eclipse的Bytecode Outline插件,...

    AOP的实现代码

    详细介绍了AOP的核心功能(拦截功能)在底层是如何实现的;介绍了两种实现AOP的动态代理:jdk动态代理和cglib动态代理,并详细描述了它们在代码层面的实现。附有必须的cglib.jar和asm.jar

    手把手教你用Java实现AOP_.docx

    然而,这种方法的局限性在于,它只能处理实现了接口的对象,对于没有接口或者接口不够用的情况,我们就需要使用CGlib或者ASM这样的字节码库来创建代理类,实现对非接口类的AOP支持。 总的来说,虽然Spring的AOP功能...

    Android AOP之ASM技术研发1

    本文将探讨如何利用ASM技术在Android环境中实现AOP。 首先,我们需要了解ASM的基本概念和使用。ASM是一个低级别的库,它提供了一种方式来动态生成或修改Java类和接口的字节码。在ASM中,我们可以通过`ClassWriter`...

    AOP的实现机制

    使用AOP进行方法监控通常需要定义一个Pointcut来指定要监控的方法,并编写相应的Advice来实现具体的监控逻辑。 **4.1.2 如何配置** 配置方法监控的具体步骤依赖于使用的AOP框架。例如,在Spring框架中,可以通过...

    AOP的实现机制.pdf

    Spring AOP是Java环境中最广泛使用的AOP实现,它支持基于注解和XML配置的切面定义。通过`@Aspect`注解,我们可以创建切面类,使用`@Before`, `@After`, `@Around`等注解定义通知,并使用`@Pointcut`定义切入点。 ...

    动态代理实现AOP思想

    以下是一个简单的例子,展示了如何使用Java动态代理实现AOP: ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface MyInterface {...

    基于Bytebuddy的Java Agent AOP框架.zip

    在Java中,我们可以使用代理模式实现AOP,而Java Agent则提供了一种更底层的方式。 ByteBuddy提供了一种友好的API,可以让我们轻松地创建动态类型。它支持类、接口、枚举以及注解的生成,并且可以在运行时插入到已...

    ASM函数监听实现(三)拦截注入函数的参数值 (函数执行前)

    ASM库是一个轻量级的Java字节码操控和分析框架,常用于动态代理、代码生成以及AOP实现。本篇文章将深入探讨如何使用ASM库来实现函数监听,特别是如何在函数执行前拦截并改变其参数值。 首先,我们需要理解ASM库的...

    个人用java动态代理模拟实现spring aop

    在本主题中,我们将探讨如何使用Java动态代理来模拟实现Spring AOP的功能。 首先,我们需要了解Java中的两种动态代理机制:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口实现,适用于目标对象实现了接口的情况...

    asm 6.0 工具集

    ASM支持JVM规范的所有版本,包括Java 8及更高版本的特性,如lambda表达式和方法引用来实现反射和AOP(面向切面编程)。 **ASM-Util**是ASM的实用工具模块,提供了许多方便开发者使用的辅助工具和类。这些工具通常...

    ASM4使用指南

    ASM4是针对Java语言设计的一个工具库,用于在运行时动态生成和转换已编译的Java类。...通过学习和使用 ASM4,开发者可以更加灵活地控制Java字节码,进而实现复杂的编程任务,提高应用程序的性能和安全性。

    Spring AOP的底层实现技术

    这个过程涉及到字节码操作,CGLIB使用ASM库来生成和修改字节码。 7. **优化与性能** 虽然CGLIB代理相比JDK动态代理在某些场景下可能稍慢,但它的优势在于无需目标对象实现接口。在实际应用中,应根据项目需求选择...

    cglib-2.2.jar asm-tree.jar asm-commons.jar asm.jar

    【标题】"cglib-2.2.jar asm-tree.jar asm-commons.jar asm.jar" 提供的是一组用于Java编程的库,它们主要用于实现动态代理和字节码操作。 【描述】"cglib动态代理模式jar包 cglib-2.2.jar asm-tree.jar asm-...

Global site tag (gtag.js) - Google Analytics