這一期來聊一聊代理模式:
在代理模式(Proxy Pattern)中,一個類代表另一個類的功能。這種類型的設計模式屬于結(jié)構(gòu)型模式。
代理這個詞 很容易想到最直接的含義 就是A代替B執(zhí)行B的內(nèi)容/A是B的代理人。
我們創(chuàng)建一個接口hero 定義一個方法 attack。這意味它的實現(xiàn)類HeroA要具體實現(xiàn)這個方法。正常的邏輯下這就結(jié)束了。
但是這時候我們不想把HeroA直接暴露出去,我們想讓一個代理類執(zhí)行HeroA的方法。 所以我們再來一個HeroProxy ,
它內(nèi)置了HeroA對象 并初始化一個實例調(diào)用Hero的方法。 這樣調(diào)用HeroProxy.attack()就達到了代理的目的。
(注意如果代理類的方法名稱是和被代理對象的方法相同的話,比如下面代碼中都是attack()方法,那么你需要讓它也實現(xiàn)方法對應的接口。)
public interface Hero {
void attack();
}
--------------------
public class HeroA implements Hero {
@Override
public void attack(){
System.out.println("hero a ! attack!");
}
}
--------------------
public class HeroProxy implements Hero {
private HeroA heroA;
@Override
public void attack(){
System.out.println("hero's proxy is execute!");
heroA=new HeroA();
heroA.attack(); // $$$
}
}
ok 這是代理模式的基礎 也是最簡單的范例,在這個基礎上 有兩個比較重要的拓展:
靜態(tài)代理
和上一個例子性質(zhì)是相同的,可能你發(fā)現(xiàn)了:
1 在$$$標注方法的前后 我們可以手動加一些其他的 作為代理類的增強。但是手動加是比較麻煩的
2 接口的方法一旦改動了 那么代理類和被代理類都要改動 這是非常麻煩的 在實際開發(fā)中
動態(tài)代理
(動態(tài)代理簡直是天才的設計!我個人覺得)
JDK 動態(tài)代理機制
InvocationHandler 接口 和 Proxy 類 是這個機制的核心。
在Proxy 類中使用頻率最高的方法是:newProxyInstance() ,這個方法主要用來生成一個代理對象
在參數(shù)里面 會把被代理類的實現(xiàn)的接口傳進來,(比如上一個例子中 Hero就是代理對象實現(xiàn)的接口)
要實現(xiàn)動態(tài)代理的話,還必須需要實現(xiàn)InvocationHandler 來自定義處理邏輯。 當我們的動態(tài)代理對象調(diào)用一個方法時,這個方法的調(diào)用就會被轉(zhuǎn)發(fā)到實現(xiàn)InvocationHandler 接口類的 invoke 方法來調(diào)用。
invoke() 方法有下面三個參數(shù):
proxy :動態(tài)生成的代理類
method : 與代理類對象調(diào)用的方法相對應
args : 當前 method 方法的參數(shù)
也就是說:你通過Proxy 類的 newProxyInstance() 創(chuàng)建的代理對象在調(diào)用方法的時候,實際會調(diào)用到實現(xiàn)InvocationHandler 接口的類的 invoke()方法。 你可以在 invoke() 方法中自定義處理邏輯,比如在方法執(zhí)行前后做什么事情。
(了解java的朋友看到 這幾個詞匯 invoke InvocationHandler 、貌似眼熟 腦子里瞬間就會出現(xiàn)“反射”的影子,沒錯的它的核心機制就是依靠反射實現(xiàn)的)
(上面的文字描述可能你會很暈 暈是正常的 下面具體來演示)
還是上面那個例子:
首先我們實現(xiàn)了Hero接口 和 HeroA的接口實現(xiàn) 里面有attack()方法。 這時候我們想創(chuàng)建一個代理類。 跟上面代碼一模一樣.
為了方便。 為了更好的理解代碼 我們來從后往前倒著看。
JdkProxyFactory.getProxy(new HeroA()); 這個工廠類直接幫我們實現(xiàn)了HeroA()的代理類。我們不需再手動創(chuàng)建代理類了
被代理類作為一個參數(shù)傳了進去。那么它是怎么實現(xiàn)的呢
public class ExecuteDemo {
public static void main(String[] args) {
//jdk動態(tài)代理
Hero heroproxy = (Hero)JdkProxyFactory.getProxy(new HeroA());
heroproxy.attack();
//動態(tài)代理
HeroProxy heroProxy =new HeroProxy(hero);
heroProxy.attack();
}
}
倒著看代碼 發(fā)現(xiàn) 這個代理類 是被Proxy.newProxyInstance()創(chuàng)建出來的。它的三個參數(shù)都與你傳入的HeroA有關。
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目標類的類加載
target.getClass().getInterfaces(), // 代理需要實現(xiàn)的接口,可指定多個
new HeroInvocationHandler(target) // 代理對象對應的自定義 InvocationHandler
);
}
}
前兩個都是反射獲取類的信息 第三個參數(shù)是一個方法的返回值 這個方法內(nèi)部是什么樣的呢
public class HeroInvocationHandler implements InvocationHandler{
private final Object target;
public HeroInvocationHandler(Object target){
this.target=target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
這里的核心方法就是 public Object invoke。它重寫了反射里面invoke方法。傳入methood,參數(shù),Object對象
在里面你就可以自定義很多事情了 在調(diào)用方法前后 加一些自己你想加的東西 這就實現(xiàn)了代理功能。
“動態(tài)“的精髓就體現(xiàn)在 參數(shù)上面 你可以傳任意object 不需要手動的去創(chuàng)建代理類。
CGLIB 動態(tài)代理類
JDK 動態(tài)代理有一個最致命的問題是其只能代理實現(xiàn)了接口的類。
就是說上一個例子heroA如果沒實現(xiàn)Hero,那么jdk動態(tài)代理就失效了。
為了解決這個問題,我們可以用 CGLIB 動態(tài)代理機制來避免。
CGLIB(Code Generation Library)是一個基于ASM的字節(jié)碼生成庫,它允許我們在運行時對字節(jié)碼進行修改和動態(tài)生成。CGLIB 通過繼承方式實現(xiàn)代理。很多知名的開源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模塊中:如果目標對象實現(xiàn)了接口,則默認采用 JDK 動態(tài)代理,否則采用 CGLIB 動態(tài)代理。
在 CGLIB 動態(tài)代理機制中 MethodInterceptor 接口和 Enhancer 類是核心。(一個接口 一個代理創(chuàng)建類 跟jdk動態(tài)代理有幾分神似對吧)
你需要自定義 MethodInterceptor 并重寫 intercept 方法,intercept 用于攔截增強被代理類的方法。
public interface MethodInterceptor
extends Callback{
// 攔截被代理類中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
obj :被代理的對象(需要增強的對象) heroA
method :被攔截的方法(需要增強的方法) attcak()
args :方法入?yún)?br>
proxy :用于調(diào)用原始方法
Enhancer類和jdk動態(tài)里面的proxy類似 不過后者是工廠模式創(chuàng)建的
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 創(chuàng)建動態(tài)代理增強類
Enhancer enhancer = new Enhancer();
// 設置類加載器
enhancer.setClassLoader(clazz.getClassLoader());
// 設置被代理類
enhancer.setSuperclass(clazz);
// 設置方法攔截器
enhancer.setCallback(new DebugMethodInterceptor());
// 創(chuàng)建代理類
return enhancer.create();
}
}
注意:CGLIB 動態(tài)代理是通過生成一個被代理類的子類來攔截被代理類的方法調(diào)用,因此不能代理聲明為 final 類型的類和方法。
如果你理解了這兩種機制,理解AOP 就是水到渠成了!