代理模式是咱們使用率比較高的一個模式。它的定義是爲其餘對象提供一種代理以控制對這個對象的訪問。java
若是隻是從定義上來看,可能沒法理解。爲何要用代理來對這個對象來訪問,我直接訪問不行嗎?行,固然行了。可是咱們使用代理天然是有代理的優點,咱們舉個簡單例子來講明一下。編程
有一個房東,他有一座房子要出售。可是房子買賣呢,咱們知道,須要讓買房的人來看房,有意向以後還須要和顧客簽定一系列的合同,實在複雜。咱們的房東比較懶,他不想作那麼多事情,他只想單純賣房子。因而他找了一箇中介,由中介來代替房東作這些事情。這個中介就能夠說是咱們的代理。若是以爲仍是以爲抽象的話,下面咱們會用實際開發代碼來演示代理模式,深刻理解。設計模式
代理模式的三個重要角色:ide
Subject抽象主題角色this
RealSubject具體主題角色編碼
Proxy代理主題角色.net
關於咱們的代理模式大體能夠分紅兩種,靜態代理模式和動態代理模式。下面咱們會用代碼來分別演示一下兩種代理的區別。設計
靜態代理呢,就是咱們的每個具體主題角色都有本身專門的代理角色。咱們用代碼來講吧,咱們開發業務的時候,須要抽象service接口和具體實現。咱們模擬一個增刪改查。代理
package proxydemo; public interface Service { //好比咱們的業務有這四個方法 void add(); void delete(); void update(); void query(); }
而後就是咱們去實現這個接口日誌
package proxydemo; public class ServiceImpl implements Service { @Override public void add() { System.out.println("增長操做"); } @Override public void delete() { System.out.println("刪除操做"); } @Override public void update() { System.out.println("更新操做"); } @Override public void query() { System.out.println("查詢操做"); } }
這個service接口就能夠看做是咱們的抽象主題角色,具體實現類就是咱們的具體主題角色。咱們能夠在客戶端使用
package proxydemo; public class Client { public static void main(String[] args) { ServiceImpl service = new ServiceImpl(); service.add(); service.delete(); service.update(); service.query(); } } |----------控制檯輸出--------| 增長操做 刪除操做 更新操做 查詢操做 Process finished with exit code 0
可是若是這個時候來了一個需求,說要在咱們使用方法後,日誌可以記錄下來是進行了什麼操做。那咱們容易想到的就是在咱們的實現類去修改,在使用後方法後用日誌記錄下來。可是咱們在開發中最忌諱就是修改原有的代碼,由於咱們是要符合開閉原則的。全部咱們就能夠用代理模式來
package proxydemo; //咱們的代理角色,在不修改原來的代碼狀況下,擴展了日誌的功能 public class ProxyServiceImpl implements Service{ //使用組合模式代替繼承 private ServiceImpl service; public void setService(ServiceImpl service) { this.service = service; } @Override public void add() { System.out.println("日誌輸出:使用add方法"); service.add(); } @Override public void delete() { System.out.println("日誌輸出:使用delete方法"); service.delete(); } @Override public void update() { System.out.println("日誌輸出:使用update方法"); service.update(); } @Override public void query() { System.out.println("日誌輸出:使用query方法"); service.query(); } }
而後在咱們的客戶端使用的時候,只須要給代理角色設置須要被代理的角色就行
package proxydemo; public class Client { public static void main(String[] args) { //實例化咱們原來的功能 ServiceImpl service = new ServiceImpl(); //實例咱們的代理角色 ProxyServiceImpl proxyService = new ProxyServiceImpl(); //設置須要被代理的角色 proxyService.setService(service); //使用有日誌功能的代理方法 proxyService.add(); proxyService.delete(); proxyService.update(); proxyService.query(); } } |----------控制檯輸出--------| 日誌輸出:使用add方法 增長操做 日誌輸出:使用delete方法 刪除操做 日誌輸出:使用update方法 更新操做 日誌輸出:使用query方法 查詢操做 Process finished with exit code 0
好處:
缺點:
動態代理就是爲了解決咱們上面那個代碼量大的解決問題,也是咱們真正在開發中會去使用的代理模式。咱們的動態代理有兩種方式去實現,一個是JDK動態代理(面向接口),一個是CGlib動態代理(面向類)。咱們這裏主要是介紹概念,因此就用JDK動態代理來實現咱們上面的例子,另外一種的使用能夠看這裏(動態代理的兩種方式以及區別)
(補充:如今也出了Javassit去實現了)
關於JDK的動態代理,咱們在java.lang.reflect
包下,有這麼一個接口
Interface InvocationHandler :
InvocationHandler
是由代理實例的調用處理程序實現的接口 。
每一個代理實例都有一個關聯的調用處理程序。 當在代理實例上調用方法時,方法調用將被編碼並分派到其調用處理程序的
invoke
方法。
還有一個Proxy
類,它提供了建立動態代理類和實例的靜態方法,它也是由這些方法建立的全部動態代理類的超類。
能夠經過反射來建立代理
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[] { Foo.class }, handler);
話很少說,咱們用代碼了實現一下更容易明白這些抽象的解釋。
例子仍是調用咱們的上面的例子,只不過這個時候,咱們的代理類不須要去實現了,咱們實現InvocationHandler接口寫調用程序就好了。
package proxydemo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 public Object target; public void setTarget(Object object) { this.target = object; } //經過反射生成獲得代理類 public Object getProxy(){ //三個參數,當前的類加載器,被代理的接口,以及一個InvocationHandler,當前便可 return Proxy.newProxyInstance (this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } //處理代理實例,返回結果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //經過反射來調用方法 Object result = method.invoke(target,args); return result; } }
咱們在咱們的客戶端使用的時候,須要去實例化一個InvocationHandler就好了。
package proxydemo; public class Client { public static void main(String[] args) { //實例一個咱們的須要被代理的角色 ServiceImpl service = new ServiceImpl(); //實例咱們的代理調用處理 ProxyInvocationHandler pih = new ProxyInvocationHandler(); //設計被代理的角色 pih.setTarget(service); //經過反射了來實例咱們的代理類 Service proxyService = (Service) pih.getProxy(); proxyService.add(); proxyService.delete(); proxyService.update(); proxyService.query(); } } |----------控制檯輸出--------| 增長操做 刪除操做 更新操做 查詢操做 Process finished with exit code 0
具體的調用過程就是
這個時候若是咱們要增長一個日誌輸出功能的話,只須要在咱們的ProxyInvocationHandler裏面增長就行。
package proxydemo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target; //設計被代理的接口 public void setTarget(Object object) { this.target = object; } //經過反射生成獲得代理類 public Object getProxy(){ //三個參數,當前的類加載器,被代理的接口,以及一個InvocationHandler,當前便可 return Proxy.newProxyInstance (this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } //處理代理實例,返回結果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //經過反射來調用方法 log(method.getName()); //將方法名傳給咱們的輸出方法 Object result = method.invoke(target,args); return result; } //新增長的日誌輸出功能 public void log(String msg){ System.out.println("日誌輸出,調用了"+msg+"方法"); } }
客戶端輸出:
日誌輸出,調用了add方法 增長操做 日誌輸出,調用了delete方法 刪除操做 日誌輸出,調用了update方法 更新操做 日誌輸出,調用了query方法 查詢操做 Process finished with exit code 0
看到這裏可能仍是會有小夥伴不是很懂, 上面咱們的靜態代理實現了一個代理類,這裏不也是實現了一個調用處理嗎?注意,這裏咱們的調用處理但是一個能夠複用的,只要你是實現了基於接口實現的業務就能夠調用。好比咱們一個龐大的業務的層,有不少的servcie接口,均可以統一使用這個動態代理模板。咱們不須要在實現階段去關心代理誰,在使用的時候才指定代理誰!!!
關於代理咱們也瞭解,它主要的優勢也很明顯
關於代理模式應用的場景,一個比較典型的動態代理就是Spring AOP了,咱們用AOP來面向切面編程。在咱們完成核心業務以後,而後能夠橫向擴展咱們的周邊業務,好比日誌輸出,監控等。具體的能夠去了解AOP,瞭解以後對其餘的場景,下次一見到就能看出來這用到了代理模式了。
碰見狂神說:通俗易懂的23中設計模式教學(視頻)
設計模式之禪(第二版)