代理模式
概述:
代理模式,提供了對目標對象另外的訪問方式。簡單講在不改變目標對象的提前下,為其添加額外功能以供其他對象使用。而對於開發人員來講,其實就是不改變原有的代碼,對相應功能進行擴展,比如限制對原有代碼的訪問權限,記錄原有代碼的執行時間,對運行過的代碼寫日誌.....
代理模式有靜態代理和動態代理。其關鍵點是,代理對象與目標對象、代理對象是對目標對象的擴展,並調用目標對象。
代理模式的角色:
抽象對象:聲明了目標對象和代理對象的共同接口,這樣一來在任何可以使用目標對象的地方都可以使用代理對象。
目標對象:定義了代理對象所代表的目標對象。
代理對象:代理對象內部含有目標對象的引用,從而可以在任何時候操作目標對象。
一、靜態代理
靜態代理有聚合和繼承兩種實現方式。下面我們以“一只程序猿在寫代碼為例子”,分別為其記錄日誌和時間。
1、繼承實現方式:
//抽象對象: public interface People { public void code(); }
//目標對象 public class Programmer implements People { @Override public void code() { System.out.println("我在寫代碼"); } }
//繼承代理 public class TimeProxy extendsProgrammer { public void proxy(){ System.out.println("time Strat"); super.code(); System.out.println("time end"); } }
此時我們對程序猿的代理進行執行:
public class Client { public static void main(String[] args) { TimeProxy timpProxty = new TimeProxy(); timpProxty.proxy(); } }
輸出結果:
這裏代理模式(繼承)就實現了,但這裏有一個問題如果我要先記錄對這只程序猿的行為的時間,然後為他記錄日誌。此時我們的代碼可能是
public class LogAndTimeProxy extends Programmer { public void logAndTimeProxy(){ System.out.println("time start"); System.out.println("log start"); super.code(); } }
但是如果需要先記錄日誌再記錄時間,那此時就要修改原有的代理或者新增代理類,這種做法代碼的實現不靈活。我們應該要做到分別定義一個時間代理,一個日誌代理,不管時間、日誌誰先誰後,都不需要去修改原有的代理或許添加新代理類
2、聚合實現方式:
//抽象對象: public interface People { public void code(); }
//目標對象 public class Programmer implements People { @Override public void code() { System.out.println("我在寫代碼"); } }
//時間代理類(聚合) public class TimeProxy implements People { private People people; public TimeProxy(People people){ this.people=people; } @Override public void code() { System.out.println("time start"); people.code(); System.out.println("time end"); } }
//日誌代理類(聚合) public class LogProxy implements People { private People people; public LogProxy(People people){ this.people=people; } @Override public void code() { System.out.println("logging start"); people.code(); System.out.println("logging end"); } }
//如果我們要先記錄時間,後記錄日誌實現方式 public class Client { public static void main(String[] args) { People p = new Programmer(); TimeProxy timpProxty = new TimeProxy(p); LogProxy logProxty = new LogProxy(timpProxty); logProxty.code(); } }
執行結果:
如果要實現新記錄日誌再記錄時間
public class Client { public static void main(String[] args) { People p = new Programmer(); LogProxy logProxty = new LogProxy(p); TimeProxy timpProxty = new TimeProxy(logProxty); timpProxty.code(); } }
結果:
這樣就可以不用修改代理類的情況下,更改代理類了。
但這裏有一個問題就是每個代理類要繼承一個接口或者實現一個類。如果一個項目比較大的話,那就需要添加很多的類,一旦接口增加方法,目標對象與代理對象都要維護。工作量會很大。
二、動態代理
動態代理有兩種實現方法,分別是jdk代理和Cglib代理。
Jdk代理主要實現Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)。其中ClassLoader 表示當前目標對象使用的類加載器,Class<?>表示目標對象實現的接口的類型,InvocationHandler 表示事件處理,當執行目標對象是會觸發該方法。
這裏同樣使用“一只程序猿在寫代碼,休息中...”為例子
//抽象對象: public interface DynamicPeople { public void codeing(); public void doOtherThing(String things); }
//目標對象 public class DynamicProgrammer implements DynamicPeople { @Override public void codeing() { System.out.println("我在寫代碼..."); } @Override public void doOtherThing(String things) { System.out.println(things+"..."); } }
//動態代理類 public class JdkDynamicProxy { public Object object; public JdkDynamicProxy(Object object){ this.object=object; } //獲取一個代理 public Object getProxyInstance(){ return Proxy.newProxyInstance( object.getClass().getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler(){ @Override //這裏proxy表示代理目標,methos表示方法,args表示參數 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("寫日誌"); //執行對應的方法 Object result =method.invoke(object, args); return result; } }); } }
調用程序猿的方法,看看代理效果:
public class DynamicClient { public static void main(String[] args) { DynamicPeople programmer = new DynamicProgrammer(); DynamicPeople proxy = (DynamicPeople) new JdkDynamicProxy(programmer).getProxyInstance(); proxy.codeing(); System.out.println("*************"); proxy.doOtherThing("休息中"); } }
執行結果
可見動態代理已經成功了。但這時我們需要實現上面所說的“分別定義一個時間代理,一個日誌代理,不管時間、日誌誰先誰後,都不需要去修改原有的代理或許添加新代理類”。這些我們需要改下newProxyInstance的InvocationHandler。我們分別定義時間處理類DynamicTimeHandler和日誌處理類DynamicLogHandler,兩者都繼承InvocationHandler,代碼如下:
public class DynamicTimeHandler implements InvocationHandler { //目標對象 private Object object; public DynamicTimeHandler(Object object){ this.object=object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("開始時間"); Object result = method.invoke(object, args); return result; } }
public class DynamicLogHandler implements InvocationHandler { private Object object; public DynamicLogHandler(Object object){ this.object=object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("開始日誌"); Object result = method.invoke(object, args); return result; } }
修改代理類:
public class JdkDynamicProxy { public Object object; public InvocationHandler h; public JdkDynamicProxy(Object object,InvocationHandler h){ this.object=object; this.h = h ; } //獲取一個代理 public Object getProxyInstance(){ return Proxy.newProxyInstance( object.getClass().getClassLoader(), object.getClass().getInterfaces(), h); } }
調用程序猿的方法,看看代理效果
public class DynamicClient { public static void main(String[] args) { DynamicPeople programmer = new DynamicProgrammer(); InvocationHandler time = new DynamicTimeHandler(programmer); DynamicPeople timeProxy = (DynamicPeople) new JdkDynamicProxy(programmer,time).getProxyInstance(); InvocationHandler log = new DynamicLogHandler(timeProxy); DynamicPeople logProxy = (DynamicPeople) new JdkDynamicProxy(programmer,log).getProxyInstance(); logProxy.codeing(); System.out.println("*************"); logProxy.doOtherThing("休息中"); } }
執行結果:
這樣就實現了上面所說的效果了。但jdk的實現限制,就是目標對象必須實現一個接口。這個缺陷,我們用Cglib代理是可以解決的(Cglib在Spring的核心包中)。
這裏我們以“開車去看美女”為例子
//定義一個目標對象汽車類 public class Car { public void running(){ System.out.println("開著汽車..."); } public void doSomething(String things){ System.out.println("開著汽車"+things); } }
//寫代理方法 public class CglibDynamicProxy implements MethodInterceptor{ public Object b; public CglibDynamicProxy(Object b){ this.b=b; } public Object getProxyInstance(){ //1.工具類 Enhancer en = new Enhancer(); //2.設置父類 en.setSuperclass(b.getClass()); //3.設置回調函數 en.setCallback(this); //4.創建子類(代理對象) return en.create(); } @Override public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy arg3) throws Throwable { System.out.println("開始代理..."); //執行目標對象的方法 Object returnValue = method.invoke(b, arg2); return returnValue; } }
調用方法,看一下執行效果
public class DynamicClient { public static void main(String[] args) { Car car = new Car(); Car newCar= (Car) new CglibDynamicProxy(car).getProxyInstance(); newCar.doSomething("看美女"); newCar.running(); } }
執行結果
對於如何“分別定義一個時間代理,一個日誌代理,不管時間、日誌誰先誰後,都不需要去修改原有的代理或許添加新代理類”還沒研究,等以後補上,O(∩_∩)O
代理模式