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 实现 AOP 的关键步骤包括: 1. **分析字节码**:首先,我们需要解析目标类的字节码,了解其方法结构,找到要插入切面代码的位置。 2. **创建 ClassWriter 对象**:ASM 提供了 ClassWriter 类,用于创建或...
下面通过一个具体的示例来展示如何使用ASM进行AOP的实现: 1. **创建一个安全检查类**: ```java public class SecurityChecker { public static void checkSecurity() { System.out.println("SecurityChecker....
AOP 的利器:ASM 3_0 介绍,很好的书
总结来说,Java字节码实现AOP是一种高效且灵活的技术手段,通过ASM、CGLIB等字节码工具,可以在运行时动态地修改类的行为,实现面向切面编程。理解并掌握这项技术,对于提升Java开发效率和代码质量具有重要意义。
JavaEE CGLIB字节码增强技术是实现面向切面编程(AOP)的一种常见方法。在JavaEE应用中,AOP允许开发者定义“切面”,这些切面封装了跨越多个对象的横切关注点,如日志、事务管理等。CGLIB(Code Generation Library...
在描述中提到,前一篇博文中介绍了使用ASM实现AOP(面向切面编程),而在这一篇中,作者打算进一步介绍如何使用ASM-Bytecode工具。 首先,为了便于分析和调试字节码,作者推荐安装Eclipse的Bytecode Outline插件,...
详细介绍了AOP的核心功能(拦截功能)在底层是如何实现的;介绍了两种实现AOP的动态代理:jdk动态代理和cglib动态代理,并详细描述了它们在代码层面的实现。附有必须的cglib.jar和asm.jar
然而,这种方法的局限性在于,它只能处理实现了接口的对象,对于没有接口或者接口不够用的情况,我们就需要使用CGlib或者ASM这样的字节码库来创建代理类,实现对非接口类的AOP支持。 总的来说,虽然Spring的AOP功能...
本文将探讨如何利用ASM技术在Android环境中实现AOP。 首先,我们需要了解ASM的基本概念和使用。ASM是一个低级别的库,它提供了一种方式来动态生成或修改Java类和接口的字节码。在ASM中,我们可以通过`ClassWriter`...
使用AOP进行方法监控通常需要定义一个Pointcut来指定要监控的方法,并编写相应的Advice来实现具体的监控逻辑。 **4.1.2 如何配置** 配置方法监控的具体步骤依赖于使用的AOP框架。例如,在Spring框架中,可以通过...
Spring AOP是Java环境中最广泛使用的AOP实现,它支持基于注解和XML配置的切面定义。通过`@Aspect`注解,我们可以创建切面类,使用`@Before`, `@After`, `@Around`等注解定义通知,并使用`@Pointcut`定义切入点。 ...
以下是一个简单的例子,展示了如何使用Java动态代理实现AOP: ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface MyInterface {...
在Java中,我们可以使用代理模式实现AOP,而Java Agent则提供了一种更底层的方式。 ByteBuddy提供了一种友好的API,可以让我们轻松地创建动态类型。它支持类、接口、枚举以及注解的生成,并且可以在运行时插入到已...
ASM库是一个轻量级的Java字节码操控和分析框架,常用于动态代理、代码生成以及AOP实现。本篇文章将深入探讨如何使用ASM库来实现函数监听,特别是如何在函数执行前拦截并改变其参数值。 首先,我们需要理解ASM库的...
在本主题中,我们将探讨如何使用Java动态代理来模拟实现Spring AOP的功能。 首先,我们需要了解Java中的两种动态代理机制:JDK动态代理和CGLIB动态代理。JDK动态代理基于接口实现,适用于目标对象实现了接口的情况...
ASM支持JVM规范的所有版本,包括Java 8及更高版本的特性,如lambda表达式和方法引用来实现反射和AOP(面向切面编程)。 **ASM-Util**是ASM的实用工具模块,提供了许多方便开发者使用的辅助工具和类。这些工具通常...
ASM4是针对Java语言设计的一个工具库,用于在运行时动态生成和转换已编译的Java类。...通过学习和使用 ASM4,开发者可以更加灵活地控制Java字节码,进而实现复杂的编程任务,提高应用程序的性能和安全性。
这个过程涉及到字节码操作,CGLIB使用ASM库来生成和修改字节码。 7. **优化与性能** 虽然CGLIB代理相比JDK动态代理在某些场景下可能稍慢,但它的优势在于无需目标对象实现接口。在实际应用中,应根据项目需求选择...
【标题】"cglib-2.2.jar asm-tree.jar asm-commons.jar asm.jar" 提供的是一组用于Java编程的库,它们主要用于实现动态代理和字节码操作。 【描述】"cglib动态代理模式jar包 cglib-2.2.jar asm-tree.jar asm-...