外出旅行、冬季保暖得常备户外袜、速干袜、加厚袜子哦。

猛戳乐途驿站http://zhoupa1188.taobao.com抢购品牌男女式加厚户外袜子,coolmax、全棉、保暖、吸汗、速干、登山、徒步袜子。满10包邮


谢炜的cnblogs

CSDN上比较完整:http://hi.csdn.net/xiefeifeihu

导航

用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。

posted on 2010-08-07 17:29  飞飞狐  阅读(1173)  评论(0编辑  收藏  举报

外出旅行、冬季保暖得常备户外袜、速干袜、加厚袜子哦。

猛戳乐途驿站http://zhoupa1188.taobao.com抢购品牌男女式加厚户外袜子,coolmax、全棉、保暖、吸汗、速干、登山、徒步袜子。满10包邮