為什么需要?jiǎng)討B(tài)代理?
- 如spring等這樣的框架,要增強(qiáng)具體業(yè)務(wù)的邏輯方法,不可能在框架里面去寫一個(gè)靜態(tài)代理類,只能按照用戶的注解或者xml配置來(lái)動(dòng)態(tài)生成代理類。
- 業(yè)務(wù)代碼內(nèi),當(dāng)需要增強(qiáng)的業(yè)務(wù)邏輯非常通用(如:添加log,重試,統(tǒng)一權(quán)限判斷等)時(shí),使用動(dòng)態(tài)代理將會(huì)非常簡(jiǎn)單,如果每個(gè)方法增強(qiáng)邏輯不同,那么靜態(tài)代理更加適合。
- 使用靜態(tài)代理時(shí),如果代理類和被代理類同時(shí)實(shí)現(xiàn)了一個(gè)接口,當(dāng)接口方法有變動(dòng)時(shí),代理類也必須同時(shí)修改。
java字節(jié)碼生成方案(還有好些不怎么流行的框架)
- asm,底層字節(jié)碼框架,操縱的級(jí)別是底層JVM的匯編指令級(jí)別,這要求ASM使用者要對(duì)class組織結(jié)構(gòu)和JVM匯編指令有一定的了解。
需要對(duì)字節(jié)碼的結(jié)構(gòu),語(yǔ)法含義等十分了解,所以只貼上一點(diǎn)示例,如果希望通過(guò)asm生成下面這樣一個(gè)class.
package com.samples;
import java.io.PrintStream;
public class Programmer {
public void code()
{
System.out.println("I'm a Programmer,Just Coding.....");
}
}
使用ASM框架提供了ClassWriter 接口,通過(guò)訪問(wèn)者模式進(jìn)行動(dòng)態(tài)創(chuàng)建class字節(jié)碼,看下面的例子:
package samples;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyGenerator {
public static void main(String[] args) throws IOException {
System.out.println();
ClassWriter classWriter = new ClassWriter(0);
// 通過(guò)visit方法確定類的頭部信息
classWriter.visit(Opcodes.V1_7,// java版本
Opcodes.ACC_PUBLIC,// 類修飾符
"Programmer", // 類的全限定名
null, "java/lang/Object", null);
//創(chuàng)建構(gòu)造函數(shù)
MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 定義code方法
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",
null, null);
methodVisitor.visitCode();
methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");
methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V");
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
classWriter.visitEnd();
// 使classWriter類已經(jīng)完成
// 將classWriter轉(zhuǎn)換成字節(jié)數(shù)組寫到文件里面去
byte[] data = classWriter.toByteArray();
File file = new File("D://Programmer.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();
}
}
- javassist,它是一個(gè)開源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫(kù)。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計(jì)算機(jī)科學(xué)系的 Shigeru Chiba (千葉 滋)所創(chuàng)建的。它已加入了開放源代碼JBoss 應(yīng)用服務(wù)器項(xiàng)目,通過(guò)使用Javassist對(duì)字節(jié)碼操作為JBoss實(shí)現(xiàn)動(dòng)態(tài)AOP框架。javassist是jboss的一個(gè)子項(xiàng)目,其主要的優(yōu)點(diǎn),在于簡(jiǎn)單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機(jī)指令,就能動(dòng)態(tài)改變類的結(jié)構(gòu),或者動(dòng)態(tài)生成類。
下面通過(guò)Javassist創(chuàng)建上述的Programmer類:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
public class MyGenerator {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//創(chuàng)建Programmer類
CtClass cc= pool.makeClass("com.samples.Programmer");
//定義code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代碼
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字節(jié)碼
cc.writeFile("d://temp");
}
}
cglib,它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。最流行的OR Mapping工具h(yuǎn)ibernate也使用CGLIB來(lái)代理單端single-ended(多對(duì)一和一對(duì)一)關(guān)聯(lián)(對(duì)集合的延遲抓取,是采用其他機(jī)制實(shí)現(xiàn)的)。EasyMock和jMock是通過(guò)使用模仿(moke)對(duì)象來(lái)測(cè)試java代碼的包。它們都通過(guò)使用CGLIB來(lái)為那些沒有接口的類創(chuàng)建模仿(moke)對(duì)象。[現(xiàn)在cglib好像不怎么維護(hù)了,javassist比較火爆]
jdk (jdk動(dòng)態(tài)代理根據(jù)interface,生成的代理類會(huì)被緩存,每個(gè)接口只會(huì)生成一個(gè)代理類)
動(dòng)態(tài)代理實(shí)現(xiàn)方案
首先定義一個(gè)接口和一個(gè)類:
public interface CountService {
int count();
}
public class CountServiceImpl implements CountService {
private int count = 0;
public int count() {
return count ++;
}
}
- 直接使用asm在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類
private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
String className = CountService.class.getName() + "AsmProxy";
String classPath = className.replace('.', '/');
String interfacePath = CountService.class.getName().replace('.', '/');
classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[] {interfacePath});
MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
initVisitor.visitCode();
initVisitor.visitVarInsn(Opcodes.ALOAD, 0);
initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
initVisitor.visitInsn(Opcodes.RETURN);
initVisitor.visitMaxs(0, 0);
initVisitor.visitEnd();
FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);
fieldVisitor.visitEnd();
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");
methodVisitor.visitInsn(Opcodes.IRETURN);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
classWriter.visitEnd();
byte[] code = classWriter.toByteArray();
CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
private static class ByteArrayClassLoader extends ClassLoader {
public ByteArrayClassLoader() {
super(ByteArrayClassLoader.class.getClassLoader());
}
public synchronized Class<?> getClass(String name, byte[] code) {
if (name == null) {
throw new IllegalArgumentException("");
}
return defineClass(name, code, 0, code.length);
}
}
- 直接使用javassist在被代理類基礎(chǔ)上生成新的字節(jié)碼形成代理類
private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {
ClassPool mPool = new ClassPool(true);
CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");
mCtc.addInterface(mPool.get(CountService.class.getName()));
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));
mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));
Class<?> pc = mCtc.toClass();
CountService bytecodeProxy = (CountService) pc.newInstance();
Field filed = bytecodeProxy.getClass().getField("delegate");
filed.set(bytecodeProxy, delegate);
return bytecodeProxy;
}
- JavaAssit動(dòng)態(tài)代理接口(javassist.util.proxy.MethodHandler)
private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(new Class[] { CountService.class });
Class<?> proxyClass = proxyFactory.createClass();
CountService javassistProxy = (CountService) proxyClass.newInstance();
((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));
return javassistProxy;
}
private static class JavaAssitInterceptor implements MethodHandler {
final Object delegate;
JavaAssitInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object self, Method m, Method proceed,
Object[] args) throws Throwable {
return m.invoke(delegate, args);
}
}
- cglib生成(net.sf.cglib.proxy.MethodInterceptor)
private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setInterfaces(new Class[] { CountService.class });
CountService cglibProxy = (CountService) enhancer.create();
return cglibProxy;
}
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(delegate, objects);
}
}
- jdk動(dòng)態(tài)代理(java.lang.reflect.InvocationHandler)
private static CountService createJdkDynamicProxy(final CountService delegate) {
CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[] { CountService.class }, new JdkHandler(delegate));
return jdkProxy;
}
private static class JdkHandler implements InvocationHandler {
final Object delegate;
JdkHandler(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object object, Method method, Object[] objects)
throws Throwable {
return method.invoke(delegate, objects);
}
}
各種動(dòng)態(tài)代理方案比較
- 速度比較
//測(cè)試結(jié)果
//創(chuàng)建代理的速度
Create JDK Proxy: 13 ms
Create CGLIB Proxy: 217 ms //較慢
Create JAVAASSIST Proxy: 99 ms
Create JAVAASSIST Bytecode Proxy: 168 ms //較慢
Create ASM Proxy: 3 ms //最快
================
Run JDK Proxy: 2224 ms, 634,022 t/s //很慢,jdk生成的字節(jié)碼考慮了太多的條件,所以字節(jié)碼非常多,大多都是用不到的
Run CGLIB Proxy: 1123 ms, 1,255,623 t/s //較慢,也是因?yàn)樽止?jié)碼稍微多了點(diǎn)
Run JAVAASSIST Proxy: 3212 ms, 438,999 t/s //非常慢,完全不建議使用javassist的代理類來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理
Run JAVAASSIST Bytecode Proxy: 206 ms, 6,844,977 t/s //和asm差不多的執(zhí)行速度,因?yàn)樗麄兌际侵簧珊?jiǎn)單純粹的執(zhí)行字節(jié)碼,非常少(所以直接用javassist生成代理類的方式最值得推薦[從速度上講])
Run ASM Bytecode Proxy: 209 ms, 6,746,724 t/s //asm什么都是最快的,畢竟是只和最底層打交道
- 便捷性比較
排序(只是生成字節(jié)碼,語(yǔ)義性):javassist>asm
排序(動(dòng)態(tài)代理):cglib=jdk>javassist bytecode>javassist proxy>asm