在jdk5.0版本中,添加了一個(gè)新的接口java.lang.instrument.Instrumentation,這個(gè)接口可以用來(lái)在類加載的時(shí)候
對(duì)類的字節(jié)碼進(jìn)行修改,改變類的功能的實(shí)現(xiàn)。通過(guò)這種方法,我們就可以實(shí)現(xiàn)aop的功能,這是和proxy動(dòng)態(tài)代理的不同的另一種aop的實(shí)現(xiàn)。
這種方法的原理很簡(jiǎn)單,就是通過(guò)修改編譯器已經(jīng)編譯好的字節(jié)碼來(lái)修改類,和我們通常修改類源碼來(lái)實(shí)現(xiàn)對(duì)類的修改差不多。只不過(guò)是在兩個(gè)不同的層次進(jìn)行的修改。
一下是使用這個(gè)接口的具體方法。
第一步:
需要實(shí)現(xiàn) java.lang.instrument.Instrumentation這個(gè)接口,通過(guò)在這個(gè)接口的實(shí)現(xiàn)類中,在加載類的時(shí)候修改類的字節(jié)碼。以下是接口的實(shí)現(xiàn) Transformer.java
public class Transformer implements ClassFileTransformer {
//在程序運(yùn)行之前就被jvm調(diào)用的方法
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new Transformer());
}
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] result =null; //定義新的字節(jié)碼存儲(chǔ)變量
//在此通過(guò)修改類的字節(jié)碼 classfileBuffer,來(lái)更改類。
return result; //返回新的字節(jié)碼
}
}
第二步:
需要將Transformer類的class文件生成jar文件,如t.jar,在jar文件的MANIFEST.MF添加 Premain-Class: Transformer 這一項(xiàng)。在運(yùn)行程序時(shí),需要在jvm使用如下參數(shù) -javaagent:t.jar 。這樣jvm在運(yùn)行時(shí),就會(huì)先找到Premain-Class: Transformer 這一項(xiàng)指定的Transformer類的premain方法,運(yùn)行此方法。也就實(shí)現(xiàn)了在以后的類的加載中,對(duì)類的字節(jié)碼進(jìn)行修改的功能。
就是使用java.lang.instrument.Instrumentation接口的方法。那么如何實(shí)現(xiàn)aop功能呢,這就需要在更改字節(jié)碼的時(shí)候,在類的功能上添加aop方面的功能。
現(xiàn)在我們看看如何實(shí)現(xiàn)在方法的調(diào)用前和返回時(shí)打印當(dāng)前時(shí)間的aop功能。
先更改Transformer的transform方法
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] result =classfileBuffer; //定義新的字節(jié)碼存儲(chǔ)變量
//在此通過(guò)修改類的字節(jié)碼 classfileBuffer,來(lái)更改類。
if (loader != ClassLoader.getSystemClassLoader()) {
return classfileBuffer;
}
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter writer = new ClassWriter(true);
ClassAdapter adapter = new PerfClassAdapter(writer, className);
reader.accept(adapter, true);
result = writer.toByteArray();
return result; //返回新的字節(jié)碼
}
這樣就通過(guò)PerfClassAdapter類來(lái)修改字節(jié)碼.
PerfClassAdapter.java
public class PerfClassAdapter extends ClassAdapter {
private String className;
public PerfClassAdapter(ClassVisitor visitor, String theClass) {
super(visitor);
this.className = theClass;
}
public MethodVisitor visitMethod(int arg,
String name,
String descriptor,
String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(arg,
name,
descriptor,
signature,
exceptions);
MethodAdapter ma = new PerfMethodAdapter(mv, className, name);
return ma;
}
}
PerfMethodAdapter.java
public class PerfMethodAdapter extends MethodAdapter {
private String _className, _methodName;
public PerfMethodAdapter(MethodVisitor visitor,
String className,
String methodName) {
super(visitor);
_className = className;
_methodName = methodName;
}
public void visitCode() {
this.visitLdcInsn(_className);
this.visitLdcInsn(_methodName);
this.visitMethodInsn(INVOKESTATIC,
"AOP_LOG",
"start",
"(Ljava/lang/String;Ljava/lang/String;)V");
super.visitCode();
}
public void visitInsn(int inst) {
switch (inst) {
case Opcodes.ARETURN:
case Opcodes.DRETURN:
case Opcodes.FRETURN:
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.RETURN:
case Opcodes.ATHROW:
this.visitLdcInsn(_className);
this.visitLdcInsn(_methodName);
this.visitMethodInsn(INVOKESTATIC,
"AOP_LOG",
"end",
"(Ljava/lang/String;Ljava/lang/String;)V");
break;
default:
break;
}
super.visitInsn(inst);
}
}
AOP_LOG.java
public class AOP_LOG{
public static void start(String className, String methodName) {
System.out.println(new StringBuilder(className)
.append('"t')
.append(methodName)
.append(""tstart"t")
.append(System.currentTimeMillis()));
}
public static void end(String className, String methodName) {
System.out.println(new StringBuilder(className)
.append('"t')
.append(methodName)
.append(""tend"t")
.append(System.currentTimeMillis()));
}
}
這些類結(jié)合起來(lái),就實(shí)現(xiàn)了在方法的調(diào)用前,會(huì)調(diào)用AOP_LOG類的start方法,在方法返回后,調(diào)用AOP_LOG類的end方法。
現(xiàn)在寫(xiě)個(gè)測(cè)試類來(lái)測(cè)試一下。
TSMain.java
public class TSMain {
public static void main(String[] args) {
TSMain tsm = new TSMain();
tsm.printClassName();
}
public void printClassName(){
System.out.println("hello "+this.getClass().getName());
}
}
運(yùn)行命令如下:
=>java -javaagent:t.jar classpath cpdir TSMain # cpdir 需要用到的類的路徑
輸出結(jié)果如下:
TSMain main start 1179374205562
TSMain <init> start 1179374205562
TSMain <init> end 1179374205562
TSMain printClassName start 1179374205562
hello TSMain
TSMain printClassName end 1179374205562
TSMain main end 1179374205562
到這里就完成整個(gè)aop的功能實(shí)現(xiàn)了。需要補(bǔ)充說(shuō)明的是,這里修改類的字節(jié)碼是使用了asm的基礎(chǔ)類庫(kù),所以需要導(dǎo)入這些asm的jar文件。
這種方法的原理很簡(jiǎn)單,就是通過(guò)修改編譯器已經(jīng)編譯好的字節(jié)碼來(lái)修改類,和我們通常修改類源碼來(lái)實(shí)現(xiàn)對(duì)類的修改差不多。只不過(guò)是在兩個(gè)不同的層次進(jìn)行的修改。
一下是使用這個(gè)接口的具體方法。
第一步:
需要實(shí)現(xiàn) java.lang.instrument.Instrumentation這個(gè)接口,通過(guò)在這個(gè)接口的實(shí)現(xiàn)類中,在加載類的時(shí)候修改類的字節(jié)碼。以下是接口的實(shí)現(xiàn) Transformer.java
public class Transformer implements ClassFileTransformer {
//在程序運(yùn)行之前就被jvm調(diào)用的方法
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new Transformer());
}
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] result =null; //定義新的字節(jié)碼存儲(chǔ)變量
//在此通過(guò)修改類的字節(jié)碼 classfileBuffer,來(lái)更改類。
return result; //返回新的字節(jié)碼
}
}
第二步:
需要將Transformer類的class文件生成jar文件,如t.jar,在jar文件的MANIFEST.MF添加 Premain-Class: Transformer 這一項(xiàng)。在運(yùn)行程序時(shí),需要在jvm使用如下參數(shù) -javaagent:t.jar 。這樣jvm在運(yùn)行時(shí),就會(huì)先找到Premain-Class: Transformer 這一項(xiàng)指定的Transformer類的premain方法,運(yùn)行此方法。也就實(shí)現(xiàn)了在以后的類的加載中,對(duì)類的字節(jié)碼進(jìn)行修改的功能。
就是使用java.lang.instrument.Instrumentation接口的方法。那么如何實(shí)現(xiàn)aop功能呢,這就需要在更改字節(jié)碼的時(shí)候,在類的功能上添加aop方面的功能。
現(xiàn)在我們看看如何實(shí)現(xiàn)在方法的調(diào)用前和返回時(shí)打印當(dāng)前時(shí)間的aop功能。
先更改Transformer的transform方法
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] result =classfileBuffer; //定義新的字節(jié)碼存儲(chǔ)變量
//在此通過(guò)修改類的字節(jié)碼 classfileBuffer,來(lái)更改類。
if (loader != ClassLoader.getSystemClassLoader()) {
return classfileBuffer;
}
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter writer = new ClassWriter(true);
ClassAdapter adapter = new PerfClassAdapter(writer, className);
reader.accept(adapter, true);
result = writer.toByteArray();
return result; //返回新的字節(jié)碼
}
這樣就通過(guò)PerfClassAdapter類來(lái)修改字節(jié)碼.
PerfClassAdapter.java
public class PerfClassAdapter extends ClassAdapter {
private String className;
public PerfClassAdapter(ClassVisitor visitor, String theClass) {
super(visitor);
this.className = theClass;
}
public MethodVisitor visitMethod(int arg,
String name,
String descriptor,
String signature,
String[] exceptions) {
MethodVisitor mv = super.visitMethod(arg,
name,
descriptor,
signature,
exceptions);
MethodAdapter ma = new PerfMethodAdapter(mv, className, name);
return ma;
}
}
PerfMethodAdapter.java
public class PerfMethodAdapter extends MethodAdapter {
private String _className, _methodName;
public PerfMethodAdapter(MethodVisitor visitor,
String className,
String methodName) {
super(visitor);
_className = className;
_methodName = methodName;
}
public void visitCode() {
this.visitLdcInsn(_className);
this.visitLdcInsn(_methodName);
this.visitMethodInsn(INVOKESTATIC,
"AOP_LOG",
"start",
"(Ljava/lang/String;Ljava/lang/String;)V");
super.visitCode();
}
public void visitInsn(int inst) {
switch (inst) {
case Opcodes.ARETURN:
case Opcodes.DRETURN:
case Opcodes.FRETURN:
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.RETURN:
case Opcodes.ATHROW:
this.visitLdcInsn(_className);
this.visitLdcInsn(_methodName);
this.visitMethodInsn(INVOKESTATIC,
"AOP_LOG",
"end",
"(Ljava/lang/String;Ljava/lang/String;)V");
break;
default:
break;
}
super.visitInsn(inst);
}
}
AOP_LOG.java
public class AOP_LOG{
public static void start(String className, String methodName) {
System.out.println(new StringBuilder(className)
.append('"t')
.append(methodName)
.append(""tstart"t")
.append(System.currentTimeMillis()));
}
public static void end(String className, String methodName) {
System.out.println(new StringBuilder(className)
.append('"t')
.append(methodName)
.append(""tend"t")
.append(System.currentTimeMillis()));
}
}
這些類結(jié)合起來(lái),就實(shí)現(xiàn)了在方法的調(diào)用前,會(huì)調(diào)用AOP_LOG類的start方法,在方法返回后,調(diào)用AOP_LOG類的end方法。
現(xiàn)在寫(xiě)個(gè)測(cè)試類來(lái)測(cè)試一下。
TSMain.java
public class TSMain {
public static void main(String[] args) {
TSMain tsm = new TSMain();
tsm.printClassName();
}
public void printClassName(){
System.out.println("hello "+this.getClass().getName());
}
}
運(yùn)行命令如下:
=>java -javaagent:t.jar classpath cpdir TSMain # cpdir 需要用到的類的路徑
輸出結(jié)果如下:
TSMain main start 1179374205562
TSMain <init> start 1179374205562
TSMain <init> end 1179374205562
TSMain printClassName start 1179374205562
hello TSMain
TSMain printClassName end 1179374205562
TSMain main end 1179374205562
到這里就完成整個(gè)aop的功能實(shí)現(xiàn)了。需要補(bǔ)充說(shuō)明的是,這里修改類的字節(jié)碼是使用了asm的基礎(chǔ)類庫(kù),所以需要導(dǎo)入這些asm的jar文件。