本篇介紹靜態代理和動態代理。
代理模式也是設計模式中的一種,主要解決的是在直接訪問對象時帶來的問題,其目的是爲其他對象提供一個代理來控制對目標對象的訪問。代理類爲委託類預處理消息,過濾消息並轉發消息,以及消息被委託類執行後的後續處理。
代理模式的類圖如下:
爲了保持行爲的一致性,委託類和代理類通常會實現相同的接口,客戶端調用Subject時不知道調用的是哪個,事實上它也不需要知道。
按照代理類的創建時機,可以將代理其分爲兩類,分別爲靜態代理和動態代理。
靜態代理
靜態代理在編譯時就已經將接口、代理類、被代理類等確定下來,在程序運行之前,代理類的.class文件就已經生成。
示例代碼如下:
Subject
public interface Subject {
void saySomething();
}
RealSubject
public class RealSubject implements Subject{
@Override
public void saySomething() {
System.out.println("***我是委託類***");
}
}
Proxy
public class Proxy implements Subject{
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
public void saySomething() {
System.out.println("***我是代理類,我要調用委託類了哦***");
subject.saySomething();
System.out.println("***委託類調用完了,我還是代理類***");
}
}
測試代碼
public class ProxyTest {
public static void main(String[] args) {
Subject subject = new RealSubject();
Proxy proxy = new Proxy(subject);
proxy.saySomething();
}
}
輸出結果
***我是代理類,我要調用委託類了哦***
***我是委託類***
***委託類調用完了,我還是代理類***
由於委託類和代理類需要實現同一接口,不僅有大量代碼重複,並且在接口增加方法時,所有的委託類和代理類都需要新增此方法。除此之外,代理對象只能委託同一接口的對象,當有多個接口的對象需要代理時,就要爲每個接口都創建一個代理類,增加了代碼複雜度。
那麼能不能使用一個類就可以代理所有類呢?可以的,這就需要使用動態代理。
動態代理
動態代理的代理類是在程序運行時運用反射機制動態創建而成。動態代理又分爲JDK動態代理和CGLIB動態代理,其中JDK代理要求委託類必須實現接口,CGLIB動態代理可以直接代理類。
JDK動態代理
JDK動態代理涉及一個接口和一個類,一個接口爲InvocationHanlder接口,一個類爲Proxy類。在介紹JDK動態代理前,我們先來看一下InvocationHanlder接口和Proxy類的API。
InvocationHanlder
InvocationHandler接口只有一個invoke()方法。
public Object invoke(Object proxy, Method method, Object[] args)
invoke()方法的第一個參數是委託類實例,第二個接口是被代理的方法實例,第三個參數是被代理的方法的參數數組。我們需要定義一個類來實現InvocationHandler接口,此類實現的invoke()方法通過反射調用委託類的指定方法。
Proxy
Proxy提供了創建動態代理類和實例的靜態方法,它也是所有動態代理類的父類。在Proxy類內部維護了一個InvocationHanlder類型的成員變量,該成員變量的值在Proxy構造函數中初始化。Proxy構造函數如下:
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
常用方法如下:
//獲取代理類的InvocationHandler實例
public static InvocationHandler getInvocationHandler(Object proxy);
//返回代理類的Class實例
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces);
//判斷該類是不是代理類
public static boolean isProxyClass(Class<?> cl);
//返回指定接口的代理類的實例
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
要使用動態代理爲委託類RealSubject創建代理類,首先創建一個SubjectInvocationHandler類實現InvocationHandler接口,在這個類中我們需要把委託類實例傳入但又不能與靜態代理相同(只能代理某一接口的實現類),因此傳入的類型應該是Object。
public class SubjectInvocationHandler implements InvocationHandler {
private Object object;
public SubjectInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是SubjectInvocationHandler的invoke()方法哦...");
System.out.println("即將執行委託類的指定方法...");
Object result = method.invoke(object,args);
System.out.println("執行完委託類的指定方法了哦...");
return result;
}
}
測試方法
public class DynamicProxyTest {
public static void main(String[] args) {
//創建委託類實例
RealSubject rs = new RealSubject();
//創建調用處理類實例
SubjectInvocationHandler sih = new SubjectInvocationHandler(rs);
//獲取委託類的Class實例
Class subClass = rs.getClass();
//創建代理類
Subject proxySubject = (Subject) Proxy.newProxyInstance(subClass.getClassLoader(),subClass.getInterfaces(),sih);
//調用指定方法
proxySubject.saySomething();
//輸出代理類實現的接口
Class[] interfaces = proxySubject.getClass().getInterfaces();
for (Class i:interfaces){
System.out.println(i.getName());
}
}
}
輸入結果
我是SubjectInvocationHandler的invoke()方法哦...
即將執行委託類的指定方法...
***我是委託類***
執行完委託類的指定方法了哦...
org.practice.Subject
java.io.Serializable
由newProxyInstance()方法生成的代理類繼承了Proxy類並實現了委託類實現的全部接口,在使用代理類實例調用接口方法時,通過反射調用了委託類的對應方法。
從上面可以看出,JDK動態代理要求委託類必須實現至少一個接口,這也是JDK動態代理的侷限性。Spring AOP就是通過動態代理實現的,在一個類沒有實現任何接口時Spring AOP還是正常工作的,原因在於在委託類沒有實現任何接口時Spring AOP會使用另一個叫做CGLIB的動態代理。
CGLIB動態代理
CGLIB動態代理不要求委託類實現接口,因爲它是通過使動態生成的代理類繼承委託類實現的,也因此,CGLIB無法代理final修飾的類。CGLIB與JDK動態代理類似,需要一個類來生成代理類以及另一個類來設定增強邏輯,分別爲Enhancer類以及MethodInterceptor接口。
MethodInterceptor接口只有一個方法。
//參數分別爲:委託類對象,要攔截的方法,被攔截方法的參數,代理方法
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
Enhancer類是一個字節碼增強器,功能與JDK動態代理中的Proxy類類似,可以用來爲未實現任何接口的類創建代理。以下使用這兩個類實現CGLIB動態代理的例子。
委託類RealSubject
public class RealSubject {
public void saySomething() {
System.out.println("我是委託類...");
}
}
方法攔截器SubjectInterceptor
public class SubjectInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("進入攔截器...");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("被攔截方法執行完了...");
return object;
}
}
測試類
public class CGLIBProxyTest {
public static void main(String[] args) {
//生成動態代理類的Class文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\WorkSpace\\project");
//創建Enhancer對象
Enhancer enhancer = new Enhancer();
//設置目標類的字節碼文件
enhancer.setSuperclass(RealSubject.class);
//設置回調函數
enhancer.setCallback(new SubjectInterceptor());
//創建代理類
RealSubject rs = (RealSubject) enhancer.create();
//使用代理對象調用目標方法
rs.saySomething();
}
}
輸出結果
CGLIB debugging enabled, writing to 'D:\WorkSpace\project'
進入攔截器...
我是委託類...
被攔截方法執行完了...
CGLIB動態代理並沒有使用反射,因爲反射的效率並不高,CGLIB底層是使用。CGLIB是使用Fastclass機制,即使動態生成的代理類繼承委託類,在代理類裏對委託類的非final方法建立索引,通過索引來調用相應的方法。
下表爲靜態代理,JDK動態代理和CGLIB動態代理的比較。
代理方式 | 實現方式 | 特點 |
---|---|---|
靜態代理 | 委託類和代理類實現同一接口,通過代理類調用委託類的方法 | 只服務一種類型的對象,如果要服務多種類型則需要爲每種類型都創建代理類,增加了代碼的複雜性;由於委託類和代理類實現相同接口,勢必會有大量代碼重複;在編譯時就確定了代理對象 |
JDK動態代理 | 委託類和代理類實現同一接口,創建InvocationHandler的實現類並重寫invoke()方法來自定義增強邏輯,使用Proxy類創建代理類對象 | 通過反射機制實現;委託類必須實現至少一個接口;動態生成的代理類繼承了Proxy類 |
CGLIB動態代理 | 創建MethodInterceptor接口的實現類並重寫intercept()方法來自定義增強邏輯,使用Enhancer類創建代理類對象 | 使動態生成的代理類繼承委託類,底層使用asm字節碼生成框架來生成class文件 |