利用ClassFileTransformer兑现aop

利用ClassFileTransformer实现aop看了文章http://www.iteye.com/topic/1116696(这个文章非常好)中的字节码

利用ClassFileTransformer实现aop

看了文章http://www.iteye.com/topic/1116696(这个文章非常好)中的字节码?部分,第一次接触到了java5新特性?instrumentation的ClassFileTransformer类,Instrumentation 的最大作用,就是类定义动态改变和操作。在 Java SE 5 及其后续版本当中,开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 –javaagent 参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。在类的字节码载入jvm前会调用ClassFileTransformer的transform方法,从而实现修改原类方法的功能,实现aop,这个的好处是不会像动态代理或者cglib技术实现aop那样会产生一个新类,也不需要原类要有接口。详细如下:

1.原类Business.java:

?

?

package model;

?

public class Business ?{

?

?public boolean doSomeThing() {

? ? ? ? System.out.println("执行业务逻辑");

? ? ? ? return true;

? ? }

?

?public void doSomeThing2() {

? ? ? ? String s = "执行业务逻辑2";

? ? ? ? System.out.println(s);

? ? }

}

2.创建MyClassFileTransformer.java:
package bci;
import java.io.IOException;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.lang.instrument.Instrumentation;import java.security.ProtectionDomain;
import test.MyClassloader;
import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javassist.NotFoundException;import model.Business;
/**?* 字节码转换器?*??*??*/public class MyClassFileTransformer implements ClassFileTransformer {
? ? /**? ? ?* 字节码加载到虚拟机前会进入这个方法? ? ?*/? ? @Override? ? public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,? ? ? ? ? ? ? ? ? ? ? ? ? ? ProtectionDomain protectionDomain, byte[] classfileBuffer)? ? ? ? ? ? throws IllegalClassFormatException {? ? ? ? System.out.println(className);? ? ? ? //如果加载Business类才拦截? ? ? ? if (!"model/Business".equals(className)) {? ? ? ? ? ? return null;? ? ? ? }
? ? ? ? //javassist的包名是用点分割的,需要转换下? ? ? ? if (className != null && className.indexOf("/") != -1) {? ? ? ? ? ? className = className.replaceAll("/", ".");? ? ? ? }? ? ? ? try {? ? ? ? ? ? //通过包名获取类文件? ? ? ? ? ? CtClass cc = ClassPool.getDefault().get(className);? ? ? ? ? ? //获得指定方法名的方法? ? ? ? ? ? CtMethod m = cc.getDeclaredMethod("doSomeThing");? ? ? ? ? ? //在方法执行前插入代码? ? ? ? ? ? m.insertBefore("{ System.out.println("记录日志"); }");? ? ? ? ? ? return cc.toBytecode();? ? ? ? } catch (NotFoundException e) {? ? ? ? } catch (CannotCompileException e) {? ? ? ? } catch (IOException e) {? ? ? ? ? ? //忽略异常处理? ? ? ? }? ? ? ? return null;? ? }
? ? /**? ? ?* 在main函数执行前,执行的函数? ? ?*?? ? ?* @param options? ? ?* @param ins? ? ?*/? ? public static void premain(String options, Instrumentation ins) {? ? ? ? //注册我自己的字节码转换器? ? ? ? ins.addTransformer(new MyClassFileTransformer());? ? }}3.测试类Test.java:package bci;
import model.Business;
public class Test {
? ? /**? ? ?* @param args? ? ?*/? ? public static void main(String[] args) {? ? ? ? new Business().doSomeThing(); ??? ? ? ? new Business().doSomeThing2(); ??
? ? }}4.将bci.MyClassFileTransformer类打成jar包,名为aop.jar,同时修改改jar中的META-INF/MANIFEST.MF内容为:Manifest-Version: 1.0Premain-Class: bci.MyClassFileTransformerCan-Redefine-Classes: true ?Can-Retransform-Classes: true ?Can-Set-Native-Method-Prefix: true ??说明:Premain-Class用来允许 JAR 文件作为一个 Java 代理运行5.运行Test.java,添加jvm启动参数:-javaagent:D:\aop.jar
利用ClassFileTransformer兑现aop
?点击run运行结果如下:
利用ClassFileTransformer兑现aop
?

也可将bci.MyClassFileTransformer及bci.Test同时打到aop.jar包中,且将aop.jar放在d:\下,由于MyClassFileTransformer需要依赖misc.javassist-3.9.0.GA.jar包,所以同时将该包也放在d:\下,

修改aop.jar包的manifest.mf文件如下:

?

Manifest-Version: 1.0

Premain-Class: bci.MyClassFileTransformer

Class-Path: misc.javassist-3.9.0.GA.jar

Main-Class: bci.Test

注意格式(这些规则当时调式了好久。。真怪异):

1.每个属性都是回车结尾;

2.class-path当前路径是jar包所在的路径,由于aop.jar的路径是d:\aop.jar,所以其所引用的路径是d:\misc.javassist-3.9.0.GA.jar;如果依赖多个jar包,则以空格分隔;

3.如果Class-Path需要分行写,那么要注意,第一行的最后要留一个空格,下一行的开头要留一个空格,如果第三行不是Class-Path的内容了,则第二行末尾不用留空格。?

4.将Main-Class放在Class-Path后定义。

5.属性名:(空格)属性值,注意加上空格。

在d:\下运行命令:

java -javaagent:aop.jar -jar aop.jar(作为可执行jar运行)

或者:java -javaagent:aop.jar bci.Test(运行bci.Test类)

结果如下:


利用ClassFileTransformer兑现aop

?

如果在manifest.mf文件中不设置class-parh属性,也可以利用-cp参数(依赖包)运行aop.jar:

java -javaagent:aop.jar -cp??misc.javassist-3.9.0.GA.jar bci.Test(运行bci.Test类)

注意此时不可java -javaagent:aop.jar -cp?misc.javassist-3.9.0.GA.jar -jar aop.jar(作为可执行jar运行)这样运行,因为-jar 和-cp不可同时使用,-cp会被忽略,最终报misc.javassist-3.9.0.GA.jar中的类找不到错误。