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的原理进行了详细的描述和总结。
AOP 的利器:ASM 3_0 介绍,很好的书
详细介绍了AOP的核心功能(拦截功能)在底层是如何实现的;介绍了两种实现AOP的动态代理:jdk动态代理和cglib动态代理,并详细描述了它们在代码层面的实现。附有必须的cglib.jar和asm.jar
asm是assembly的缩写,是汇编的称号,对于java而言,asm就是字节码级别的编程。 而这里说到的asm是指objectweb asm,一种.class的代码生成器的开源项目....现在挺多流行的框架都使用到了asm.所以从aop追溯来到了这。
一个简单的通过ASM修改字节码实现AOP功能的实例,简单易懂,可运行...
ASM4使用指南, 插桩技术.android开发AOP开发必备指南.
ASM操作字节码,动态生成Java类class文件,模拟Spring的AOP实现原理。
Spring mvc Aop+annotation实现系统日志记录功能实现的jar包asm-3.3.jar ,aspectjrt.jar , aspectjweaver.jar , cglib-nodep-2.1_3.jar , spring-aop.jar
asm 字节码操作工具库的代码demo 以及cglib实现简单aop功能的代码demo
Interface- buoy是一个Android gradle插件,使用ASM在构建过程中动态修改Java字节码。 结合使用动态代理和反射来获得与“?”相同的效果。 影响。 请注意,我们只修改了Java接口中定义的方法,该方法将在运行时触发...
asm-all-3.2.jar 全集满足spring aop 开发过程中遇到的问题
spring3aop所用jar包,可以解决和hibernate3的asm.jar、asm-attrs.jar、cglib-2.1.3.jar这几个jar冲突问题。使用时删掉hibernate的jar,用rar中的替换即可
ASM Java字节码操作框架PPT,结合已有AOP实现方法,对比所有对Java字节码操作方法做比较
第二个是访问权限,设为公共 第三个参数类的全路径+类名 第四个参数 第五个参数,类都是Object 第六个为该类实现的接口数组
静态代理--不适合企业开发,适合初学者理解代理。 jdk动态代理--适合企业级开发,但是它要求必须面向接口编程,假如目标类没有实现接口...spring 的AOP功能中 会根据目标类是否实现了接口来判断使用 jdk Proxy还是cglib
spring AOP aspectjrt 自带的所有需要包 aspectjrt-1.6.9.jar aspectjweaver-1.6.9.jar cglib-nodep-2.2.jar spring-2.5.6.jar asm-2.2.3.jar asm.commons-2.2.3.jar asm.util-2.2.3.jar
这是spring aop的一个依赖包这是spring aop的一个依赖包这是spring aop的一个依赖包这是spring aop的一个依赖包这是spring aop的一个依赖包这是spring aop的一个依赖包
JAVA AOP功能的简单实现1.aoplearn项目中使用Proxy实现AOP功能2.asmlearn项目中使用ASM实现简单类
1.介绍了 Instrument 相关技术名词以及概念;... 本文是该系列的第二篇,主要包含以下内容: 代码实战,在 ASM 修改字节码的基础上实现真正的 AOP; 通过 QTrace 中部分代码分析,了解 QTrace 是如何实现代码插桩