代理技術的最大的做用是增長了代碼的擴展性,代碼的質量很重要的一個指標是代碼的擴展性,因此學好代理,能很好的提升代碼質量。著名的spring的aop特性,底層的核心是使用了動態代理的技術,spring支持cglib和原生的動態代理,具體兩種切換,能夠自行查找,本文不作探討,還有hibernate、mybatis等orm框架也是有用到這個代理技術。除了前面提到的兩個動態代理技術,還有一個就是鼎鼎大名的Jboss的javaassist。除了原始的動態代理,其餘兩種動態代理是經過操做.class的字節碼實現的。在效率方面cglib最好(底層使用的ASM字節碼操做框架),其次是javaassist,最後是原生代理,可是api友好度方面,我的以爲是javaassist最好了。java
關於字節碼實現動態代理的原理能夠參看這篇博文《Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM)》web
在擼動態代理的例子以前,先看看靜態是怎麼實現代理的,也比較好理解什麼是代理,先寫一個被代理類mouth:spring
public class Mouth { public void sayHello(){ System.out.println("i am mouth ,hello world!"); } }
再來寫一個代理類mouthProxy:api
public class MouthProxy { private Mouth mouth; public MouthProxy(Mouth mouth){ this.mouth = mouth; } public void invoke(){ System.out.println("static proxy before"); mouth.sayHello(); System.out.println("static proxy after"); } }
經過上面寫的代理類和被代理類,我來看看測試下,相似代碼以下:mybatis
public class StaticProxyTest { public static void main(String[] args) { Mouth mouth = new Mouth(); MouthProxy proxy = new MouthProxy(mouth); proxy.invoke(); } }
測試結果以下:app
這個不就是一個代理嗎,若是寫好點,能夠把Mouth寫成一個接口,因此實現Mouth接口的類都能進行代理。框架
原生生成動態代理的過程能夠分爲下面幾步:經過實現InvocationHandler接口建立本身的調用處理器、經過爲Proxy類指定ClassLoader對象和一組interface來建立動態代理類、經過反射機制得到動態代理類的構造函數,其惟一參數類型是調用處理器接口類型、經過構造函數建立動態代理類實例,構造時調用處理器對象做爲參數被傳入。下面來一段代碼:jvm
public class JdkProxy { interface Fruit { void whatMyName(); } static class Apple implements Fruit { public void whatMyName() { System.out.println("my name is apple"); } } //jdk的動態代理是實現InvocationHandler static class WhatIsYourNameHandler implements InvocationHandler { private Fruit fruit; private WhatIsYourNameHandler(Fruit fruit) { this.fruit = fruit; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("what is your name before!!"); Object invoke = method.invoke(fruit, args); System.out.println("what is your name after!!"); return invoke; } } public static void main(String[] args) throws NoSuchMethodException { //獲取代理類 Fruit instance = (Fruit)Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), new Class[]{Fruit.class}, new WhatIsYourNameHandler(new Apple())); instance.whatMyName(); } }
看這個jdk的動態代理實現簡直和靜態代理一毛同樣,就是在建立代理類的時候有一些區別,那有的同窗就會問了,既然幾乎一毛同樣那爲何就不用靜態代理呢,畢竟動態代理的這個代理類和實現還有要看下才能懂的,goodquestion!這邊只是實現了一個簡單的代理而已,其中的方法只有一個,若是要實現多個方法的話,並且每一個方法都是在調用方法打印方法名字的話,靜態代理是否是得有幾個方法就寫幾個先後打印的代碼了,這個一點都不cool。函數
看上去jdk已經實現了這種動態代理,but,實現歸實現,原生代理只能對接口進行代理,並且效率也是一個問題,因此就有了cglib,cglib底層是使用asm字節碼操做框架(多說一句,像groovy也是經過ASM框架實現對Java的加強的,groovy負責詞法、語法解析groovy文件,而後用asm生成普通的java字節碼文件,供jvm使用)實現了對字節碼的直接操做,咱們都知道越接近底層代碼的效率是越高的,字節碼層面是jvm操做的對象了,因此效率毋庸置疑好於jdk,不過jdk動態代理也在優化,因此新版本的效率沒有好那麼多,可是仍是好。測試
下面來一段使用cglib實現的動態代理:
public class CglibProxy { interface Car{ void showYourBrand(); } static class Toyota implements Car{ public void showYourBrand(){ System.out.println("my brand name is toyota"); } } static class CarSelectorHandler implements MethodInterceptor{ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before show your brand"); Object o = proxy.invokeSuper(obj, args); System.out.println("after show your brand"); return o; } } public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Toyota.class); enhancer.setCallback(new CarSelectorHandler()); Car car = (Car)enhancer.create(); car.showYourBrand(); } }
運行結果是:
剛開始使用的cglib的版本爲3.1的時候會報錯,org.objectweb.asm版本爲3.1.0時版本衝突,報錯java.lang.IncompatibleClassChangeError: class net.sf.cglib.core.DebuggingClassWriter has interface org.objectweb.asm.ClassVisitor as super class,使用cglib 2.2 可解決此問題,該版本中的DebuggingClassWriter的父類爲ClassWriter
一種是使用代理工廠建立,另外一種經過使用動態代碼建立。使用代理工廠建立時,方法與CGLIB相似,也須要實現一個用於代理邏輯處理的Handler:例如createJavassistDynProxy();使用動態代碼建立,生成字節碼,這種方式能夠很是靈活,甚至能夠在運行時生成業務邏輯,如createJavassistBytecodeDynamicProxy()方法。
public class JavaassistProxy { public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("proxy.Test"); CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[]{}); cm.insertBefore(" System.out.println(\"say hello before\");"); cm.insertAfter(" System.out.println(\"say hello after\");"); Class clazz = cc.toClass(); Object obj = clazz.newInstance();//經過調用無參構造器,建立新的對象 Method method = clazz.getDeclaredMethod("sayHello"); method.invoke(obj); } } class Test { public void sayHello() { System.out.println("say hello"); } }
運行結果:
咱們看到了javaassist直接就是能夠把代碼經過insertXxx進行操做字節碼,除了這麼作,還能夠之間建立新的類,這個有興趣的能夠去研究javaassist的其餘功能,因爲方法是在字節碼層面加進去的,因此必需要用反射進行調用。
文章參考了:https://blog.csdn.net/liutengteng130/article/details/46565309和https://blog.csdn.net/luanlouis/article/details/24589193