看了文章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.0
Premain-Class: bci.MyClassFileTransformer
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
说明:
Premain-Cla
ss用来允许 JAR 文件作为一个 Java 代理运行
5.运行Test.java,
添加jvm启动参数:-javaagent:D:\aop.jar
点击run运行结果如下:
也可将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类)
结果如下:
如果在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中的类找不到错误。