本文轉自:https://blog.csdn.net/yinwenjie/article/details/77848285
代理模式是23種設計模式中的一種,屬於一種結構模式。用一句大白話解釋這個設計模式:當外部調用者調用A來完成一件事情/一個動作時,並不直接調用A本身,而是調用一個代理者,並再由代理者負責調用真實的執行者A,最終達到間接調用的目的。
代理模式(動態)和Cglib代理是Spring生態中的最基礎設計原理之一,所以要了解Spring的工作原理就必須先討論清楚代理模式的設計思路(無論是靜態代理還是動態代理)。
1. 代理模式(靜態)
(代理模式——靜態)
上圖顯示了一個標準的代理模式(靜態)類關係結構。在標準的代理模式(靜態)中,至少存在以下角色:
- Subject接口或抽象角色:對於外部調用者來說只關心調用操作是否被執行,而不會關心本次調用是被直接執行的還是被代理者間接執行的。所以一般來說代理執行者會實現和被代理者相同的接口(或抽象類),“僞裝”成被代理的對象(上圖中就是僞裝成RealSubject類)。
- RealSubject:被代理的真實業務執行者。簡單來說就是真實業務由這個角色負責執行,只是爲了在業務處理前後能夠執行其它操作,所以真實業務執行者纔會被代理(上圖中就是被Proxy代理)。
- Proxy:代理執行者。代理執行者內部引用了真實執行者,並根據需要在真實業務執行前後,執行其它操作:例如判斷入參是否符合要求、打開數據庫連接、捕獲異常發送到事件蒐集器中……
以下的代碼片段說明了代理模式(靜態)中以上幾個工作角色的簡單實現:
- Subject接口或抽象角色
// 業務接口定義
public interface Subject {
// 該方法是被外部調用者所調用的方法
public void operation();
}
- Proxy 代理者
/**
* 代理者,代理者並不執行最終的業務<br>
* 但是可以在執行最終業務之前、之後做一些其它處理。例如打開數據庫連接、提交事務等等
* @author yinwenjie
*/
public class Proxy implements Subject{
// 真實執行者
private RealSubject realSubject;
// 建議使用slf4j
private static final Logger LOG = LoggerFactory.getLogger(Proxy.class);
public Proxy() {
this.realSubject = new RealSubject();
}
public void operation() {
try {
LOG.info("在執行真實調用前,可以執行一些動作");
this.realSubject.operation();
LOG.info("在執行真實調用後,也可以執行一些動作");
} catch (Exception e) {
LOG.info("在執行真實調用異常後,還是可以執行一些動作");
}
}
}
- RealSubject,真實業務在這裏執行
// 這是真正的業務執行者
public class RealSubject implements Subject {
// 建議使用slf4j
private static final Logger LOG = LoggerFactory.getLogger(RealSubject.class);
public void operation() {
this.exec();
}
// 真實業務在這個私有方法中執行
private void exec() {
LOG.info("執行真實的業務!");
// 有10%的機率拋出異常
float currentValue = new Random().nextFloat();
if(currentValue < 0.01) {
throw new IllegalArgumentException("拋出了異常");
}
}
}
- 以下代碼可以執行這個代理模式設計,並且輸出類似如下的結果:
......
public static void main(String[] args) throws Exception {
Subject subject = new Proxy();
subject.operation();
}
......
// 以下是可能的輸出結果================================
54273 [main] INFO yinwenjie.test.proxy.Proxy - 在執行真實調用前,可以執行一些動作
54273 [main] INFO yinwenjie.test.proxy.RealSubject - 執行真實的業務!
54480 [main] INFO yinwenjie.test.proxy.Proxy - 在執行真實調用異常後,還是可以執行一些動作
2. 代理模式(動態)
靜態代理模式很簡單吧。實際工作中,靜態代理模式只適合寫寫測試讓大家學習和熟悉解決問題的思路,真正有用的還是動態代理模式和Cglib代理,例如Spring中就使用了這兩種動態代理方式。本節我們先介紹java.lang.reflect.Proxy代理。
動態代理模式並不要求對某個業務接口(interface)有任何實現,而是一旦有調用發生,就會通知java.lang.reflect.InvocationHandler接口下的invoke方法。
2.1、基本代碼過程
- 以下是兩個接口定義 TargetOneInterface 和 TargetTwoInterface
// 被代理的接口
public interface TargetOneInterface {
// 這是一個方法
public void doSomething();
// 這是第二個方法
public void handleSomething();
}
// 第二個需要被代理的接口
public interface TargetTwoInterface {
// 查詢某些數據
public List<?> findSomething();
// 按照某種條件進行查詢
public List<?> findSomethingByField(Integer field);
}
- 接着我們定義代理處理器
java中對動態代理的支持是通過java.lang.reflect.InvocationHandler接口來規範的。以下是一個實現示例:
// 代理者處理器
public class ProxyInvocationHandler implements InvocationHandler {
/**
* @param proxy 代理對象,注意是代理者,不是被代理者
* @param method 被代理的方法
* @param args 被執行的代理方法中傳入的參數
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====================================================");
System.out.println("代理者的對象:" + proxy.getClass().getName());
// 被代理的接口
Class<?> targetClass = method.getDeclaringClass();
System.out.println("被代理的接口類:" + targetClass.getName());
System.out.println("被代理的方法:" + method.getName());
if(args == null) {
return null;
}
System.out.println("被代理的調用過程參數類型:");
Arrays.asList(args).stream().forEach((item) -> {
System.out.println("方法類型:" + item.getClass().getName());
});
// 在這裏可以返回調用結果
return null;
}
}
- 接着我們就可以定義如何代理接口了
public class Run {
public static void main(String[] args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//這是代理處理器
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
Object proxy = Proxy.newProxyInstance(classLoader,new Class<?>[]{TargetOneInterface.class , TargetTwoInterface.class}, invocationHandler);
// 開始調用(測試第一個接口)
TargetOneInterface targetOne = (TargetOneInterface)proxy;
targetOne.doSomething();
targetOne.handleSomething();
// 開始調用(測試第二個接口)
TargetTwoInterface targetTwo = (TargetTwoInterface)proxy;
targetTwo.findSomething();
targetTwo.findSomethingByField(111);
}
}
請注意,TargetOneInterface接口和TargetTwoInterface接口可沒有定義任何實現。在調用接口中定義的方法時,應用程序也沒有執行接口的任何實現,而是調用了InvocationHandler代理處理器中的invoke()方法。這個方法中有三個參數,在以上代碼的註釋中已經詳細說明,這裏就不在贅訴了。
- 以下是執行的結果:
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetOneInterface
被代理的方法:doSomething
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetOneInterface
被代理的方法:handleSomething
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetTwoInterface
被代理的方法:findSomething
====================================================
代理者的對象:com.sun.proxy.$Proxy0
被代理的接口類:yinwenjie.test.proxy.dproxy.target.TargetTwoInterface
被代理的方法:findSomethingByField
被代理的調用過程參數類型:
方法類型:java.lang.Integer
<==== 上一篇 =====>
Spring/Boot/Cloud系列知識(1)— — 開篇
<==== 下一篇 =====>
Spring/Boot/Cloud系列知識(3)— — 代理模式(中)