當用戶但願和某個對象打交道的時候,可是程序可能不但願用戶直接訪問該對象,而是提供一個特殊的對象,這個特殊的對象就被稱爲當前要訪問對象的代理,在程序中,讓用戶直接和這個代理對象打交道java
代理模式必需要讓代理類和目標類實現相同的接口,客戶端經過代理類來調用目標方法,代理類會將全部的方法調用分派到目標對象上反射執行,還能夠在分派過程當中添加"前置通知"和後置處理(如在調用目標方法前校驗權限,在調用完目標方法後打印日誌等)等功能。特別要注意的是,代理只作效果加強,卻不改變結果的類型,若是須要改變返回結果的類型,那麼不推薦使用代理模式node
所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就肯定了,以下代碼,StaticProxy類直接就是Person的實現類緩存
public interface Person { public void eat(String food); public void sleep(String time); }
public class StaticProxy implements Person { private Person person; public StaticProxy(Person person) { this.person = person; } @Override public void eat(String food) { System.out.println("靜態代理提供的 吃" + food); } @Override public void sleep(String time) { System.out.println("靜態代理提供的 睡" + time+"h"); } }
public class TestStatic { public static void main(String[] args){ Person person =new PersonImpl(); StaticProxy staticProxy=new StaticProxy(person); staticProxy.eat("食物"); staticProxy.sleep("24"); } }
運行結果app
爲何要使用動態代理呢,由於靜態代理已經不能知足咱們業務的須要了,若是我要代理20個接口怎麼辦?靜態代理就要建20個代理類,可是對於動態代理,只要直接new它的代理實例就能夠了,這就是動態代理的好處,在下面的代碼中會體現出來ide
public interface Person { public void eat(String food); public void sleep(String time); }
public class PersonImpl implements Person { @Override public void eat(String food) { System.out.println("JDK動態代理提供的 開始吃:" + food); } @Override public void sleep(String time) { System.out.println("JDK動態代理提供的 睡了:" + time + "h"); } }
建立代理類this
a invoke方法在調用指定的具體方法時會自動調用,用於集中處理在動態代理類對象上的方法調用,好比下圖中,調用到person.eat("食物")的時候,會調用到invoke(),這裏能夠對調用的接口作統一的處理,其實jdk的動態代理能夠做爲Aop的實現方式(固然Aop的實現方式有不少種)spa
b newProxyInstance()靜態方法負責建立動態代理類的實例設計
public class JDKProxy implements InvocationHandler { private Person person; public JDKProxy(Person person) { this.person = person; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //插入前置通知 System.out.println("invoke method before------" + method + "------------------"); //重點就是這裏,關心的並非這個method.invoke(person,args),若是下面接口的實例作的是同一件事的話,就至關於橫切 //也就是Aop的實現,前面幹什麼,後面幹什麼 Object result = method.invoke(person, args); //插入後置通知 System.out.println("invoke method after------" + method + "------------------"); return result; } /** * 建立代理 */ public void creatProxy() { //Person.class.getInterfaces(); 獲取某個類下面的接口 //這裏體現了動態代理的好處,能夠代理Person下的全部接口 Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[] {Person.class}, new JDKProxy(new PersonImpl())); //若是要在代理一個接口怎麼辦?直接在這裏實例化一個代理實例就行了 //可是這裏有個問題,新接口要作的是同一件事情,這就是Aop的一種實現方式 person.eat("食物"); person.sleep("24"); } }
參數 ClassLoader loader:類加載器 Class<?>[] interfaces:獲得所有的接口 InvocationHandler h:獲得InvocationHandler接口的子類實例
main方法的調用代理
public class TestJDK { public static void main(String[] args) { Person person = new PersonImpl(); JDKProxy jdkProxy = new JDKProxy(person); jdkProxy.creatProxy(); } }
運行結果日誌
看到這裏,有的小夥伴就有點不明覺厲了,小編看到這裏的時候,會有如下幾個問題
1 這個代理對象究竟是怎麼生成的呢?
http://rejoy.iteye.com/blog/1627405?page=2#comments
網上有篇寫的很是好的博客,小編這篇博客裏面重點給你們圈一下,你們看源碼的時候,能夠跟着這個順序來看
Proxy.newProxyInstance(新增一個代理實例)---> getProxyClass0(loader, intfs)(獲取代理的class字節碼)---> ProxyClassFactory.apply(若是存在的話,從緩存中獲取,不然從代理工廠中獲取)---> ProxyGenerator.generateProxyClass(這個纔是整個代理最精華的部分,獲取代理類的字節碼文件)---> var3.generateClassFile(將字節碼文件寫入內存)
2 這個InvocationHandler 的invoke方法究竟是誰在調用?
咱們把代理類反編譯出來(主要就是用ProxyGenerator.generateProxyClass)
/*** * * 獲取代理的class文件,這個方法是爲了印證,代理是如何實現的,與是否生成代理無關 * 1 跟蹤源碼,獲取代理的class------Proxy.newProxyInstance(跟進去看) * 2 用文件輸出流輸出,而後全看反編譯以後的class */ public static void getProxyClass(Object proxyObject) { Class proxyClass = proxyObject.getClass(); String proxyName=proxyClass.getName(); // 生成代理類的字節碼文件,這裏是動態代理最精華的部分 // 在源碼中,生成字節碼以後,會根據字節碼去生成對應的代理類實例 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyClass.getName(), new Class[] {Person.class}); //生成的class String relativeFilePath = "target/classes/com/proxy/"+proxyName+".class"; FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(relativeFilePath); outputStream.write(proxyClassFile); //java在使用流時,都會有一個緩衝區,按一種它認爲比較高效的方法來發數據:把要發的數據先放到緩衝區, // 緩衝區放滿之後再一次性發過去,而不是分開一次一次地發 //而flush()表示強制將緩衝區中的數據發送出去,沒必要等到緩衝區滿. outputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
調用
public static void main(String[] args) { Person person=new PersonImpl(); Person2 person2=new Person2Impl(); getProxyClass(person); getProxyClass(person2); }
在指定的路徑下查看反編譯以後的class文件
小編的路徑是
String relativeFilePath = "target/classes/com/proxy/"+proxyName+".class";
你們點開反編譯的class文件
public final void eat(String var1) throws { try { //我的理解是,invoke方法是接口在調用,調用到你的接口時,接口調用invoke,如圖,接口eat,調用了invoke super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
3 爲何jdk的動態代理只能代理接口呢?
網上看了不少的帖子,依然不是很明白,因此仍是動手敲一下吧
思路:既然說jdk的動態代理不能代理類,那麼我就嘗試下代理類看看報什麼錯誤
package com.base; /** * 用person4,來驗證,爲何jdk的動態代理只能代理接口 */ public class Person4 { public void say(String word) { System.out.println("say:" + word); } }
public class JDKProxy implements InvocationHandler { private Person4 person4; public JDKProxy(Person4 person4) { this.person4 = person4; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //插入前置通知 System.out.println("invoke method before------" + method + "------------------"); //執行相應的目標方法 Object result = method.invoke(person4, args); //插入後置通知 System.out.println("invoke method after------" + method + "------------------"); return result; } public void creatProxy() { Person4 person4 = (Person4) Proxy.newProxyInstance(Person4.class.getClassLoader(), new Class[] {Person4.class}, this); person4.say("hallo word"); } }
public class TestJDK { public static void main(String[] args) { Person4 person4=new Person4(); JDKProxy jdkProxy=new JDKProxy(person4); jdkProxy.creatProxy(); } }
而後運行結果,duang,果真錯了
仍是咱們上面說的源碼,找到
這裏是生成代理類的class字節碼文件,而後寫入硬盤,這裏清清楚楚的寫明瞭,jdk的動態代理只能代理接口,自我理解下,在Java中,能夠單個繼承類,也能夠實現多個接口,可是在jdk動態代理設計的時候,可能考量到單繼承會有諸多的不便,因此在源碼設計的時候,強行的設計成了接口
上文中咱們能夠知道,jdk的動態代理只能用於接口代理,對於類的代理就力不從心了,那麼咱們就能夠用cglib的動態代理來實現類代理,cglib是怎麼樣實現動態代理的呢,首先經過字節碼技術爲類建立子類,子類中攔截全部父類的方法(也是AOP的實現),咱們依然以上面的person爲例,話很少說,直接上代碼
public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { enhancer.setCallback(this); enhancer.setSuperclass(clazz); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置通知" + method + "----------"); //這裏要比較jdk的動態代理,一個是實現InvocationHandler //一個是實現了MethodInterceptor Object result = proxy.invokeSuper(obj, args); System.out.println("後置通知" + method + "----------"); return result; } }
public class Person3 { public void eat(String food) { System.out.println("吃:" + food); } public void play(String basketball) { System.out.println("玩:" + basketball); } }
public class TestCglib { public static void main(String[] args) { CglibProxy cglibProxy=new CglibProxy(); Person3 person3=(Person3) cglibProxy.getProxy(Person3.class); person3.eat("cglibProxy提供的food"); person3.play("cglibProxy提供的basketBall"); } }
運行結果
cglib的包問題:
這兩個包都是須要的,若是cglib-nodep-2.2.jar這個包若是少了,那麼就會報錯
1 靜態代理的字節碼文件,在程序運行前就已經生成了(代理類和委託關係在程序運行前就肯定了) ,動態代理的字節碼文件是在程序運行過程當中,經過Java的反射機制生成的(代理類和委託關係是在程序運 行中肯定的)
2 靜態代理,要本身實現處理的結果,動態代理中全部的方法都被轉移調用到了處理器的一個集中的地方處理(InvocationHandler.invoke),這樣在接口數量較多的時候,能夠靈活處理,而不須要像靜態代理同樣,對每個方法進行中轉(這是動態代理了最大的優勢)
對於靜態代理來講,若是要新代理一個接口的話,那麼它就要新建一個代理類
可是對於動態代理,若是要代理一個新的接口,他只要從新new一個代理的實例就能夠了,這樣就大大減小的代碼,避免了一些多餘的代碼