一、定義
1.什麼是代理模式
-
代理(Proxy)模式是結構型的設計模式之一,它可以爲其他對象提供一種代理(Proxy)以控制對這個對象的訪問。
-
所謂代理,是指具有與被代理的對象具有相同的接口的類,客戶端必須通過代理與被代理的目標類交互,而代理一般在交互的過程中(交互前後),進行某些特別的處理。
-
通俗的來講代理模式就是我們生活中常見的中介或者代理人,比如我們想要買房子或者買車,自己弄太麻煩,就可以找一箇中介,幫我全部打理好;或者父母是孩子的代理人,有些事情是需要父母出面代理孩子完成。
2.爲什麼要用代理模式
- 中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委託對象,而代理類對象可以在客戶類和委託對象之間起到中介的作用,其特徵是代理類和委託類實現相同的接口。
- 開閉原則,增加功能:代理類除了是客戶類和委託類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合代碼設計的開閉原則。
3.參與角色
-
抽象主題(Subject):真實主題與代理主題的共同接口。
-
真實主題(RealSubject):實現抽象主題,定義真實主題所要實現的業務邏輯,供代理主題調用。
-
代理主題(Proxy):實現抽象主題,是真實主題的代理。通過真實主題的業務邏輯方法來實現抽象方法,並可以附加自己的操作。
4.應用場景
-
需要控制對目標對象的訪問。
-
需要對目標對象進行方法增強。如:添加日誌記錄,計算耗時等。
-
需要延遲加載目標對象。
代理又分爲靜態代理和動態代理,我們以孩子交學費爲例
二、靜態代理
【抽象主題】
package designpatterns.proxy;
public interface IChild {
void money();
void school();
}
【真實主題】
package designpatterns.proxy;
public class Child implements IChild {
@Override
public void money() {
System.out.println("娃娃去交錢");
}
@Override
public void school() {
System.out.println("娃娃去上學");
}
}
【代理主題】
package designpatterns.proxy;
public class Parent implements IChild {
private IChild child;
public Parent(IChild child) {
this.child = child;
}
@Override
public void money() {
System.out.println("父母去交錢");
}
@Override
public void school() {
child.school();
}
}
【測試環境】
package designpatterns.proxy;
public class Demo {
public static void main(String[] args) {
IChild child = new Child();
IChild proxy = new Parent(child);
proxy.money();
proxy.school();
}
}
【運行結果】
父母去交錢
娃娃去上學
注意:在代理主題中引入的對象是抽象主題類而不是真實主題類。
三、動態代理
動態代理的實現手段:JDK 自帶的 Proxy 類、CGlib、Javaassist 等。
1.Proxy類實現動態代理
抽象主題和真實主題不變
【代理處理類】
package designpatterns.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ChildHandle implements InvocationHandler {
private IChild child;
public ChildHandle(IChild child) {
this.child = child;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
/**
* method.getName()是獲得抽象主題的方法,通過多態定位到具體實現類
* 當在生產環境進行調用的時候,根據不同的方法名執行不同的方法
* */
if ("school".equals(method.getName())) {
obj = method.invoke(child,args);
}
if ("money".equals(method.getName())) {
System.out.println("父母交學費");
}
return obj;
}
}
【測試環境】
package designpatterns.proxy;
import java.lang.reflect.Proxy;
public class Dynamic {
public static void main(String[] args) {
IChild child = new Child();
ChildHandle handler = new ChildHandle(child);
// 代理模式
IChild proxy = (IChild) Proxy.newProxyInstance(
child.getClass().getClassLoader(),
child.getClass().getInterfaces(),
handler);
// 這裏的方法調用,對用的就是ChildHandle類中invoke方法中method.getName()所得到得內容
proxy.money();
proxy.school();
}
}
【運行結果】
父母交學費
娃娃去上學
注意 Proxy.newProxyInstance() 方 法接受三個參數:
- ClassLoader loader: 指定當前目標對象使用的類加載器,獲取加載器的方法是固定的
- Class<?>[] interfaces: 指定目標對 象實現的接口的類型,使用泛型方式確認類型
- InvocationHandler:指定動態處理器,執行目標對象的方法時,會觸發事件處理器的方法
關於proxy動態代理的個人理解:proxy動態代理的InvocationHandler 接口就類似多線程的runnable接口,而invoke方法就相當於run方法,多線程的邏輯寫在run方法中,而代理模式中需要實現的邏輯是寫在invoke方法中,而通過java反射,又可以在運行的時候獲取抽象主題和真實主題的方法,然後重新實現代理邏輯。實現的方式跟多線程的start方式略有不同,代理是通過不同的方法調用來實現的,代理的類的方式通過invocationHandle的invoke,邏輯已經更改。
總結:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務接口的依賴,降低了耦合度。但是還是有一點點小小的遺憾之處,那就是Java 的繼承機制註定了這些動態代理類們無法實現對 class 的動態代理,僅支持 interface 代理。