1. 定義
代理模式是一種使用代理對象來執行目標對象的方法並在代理對象中增強目標對象方法的一種設計模式。代理對象代爲執行目標對象的方法,並在此基礎上進行相應的擴展。
代理模式遵循開閉原則,代理對象和目標對象實現共同的接口,主要組成如下圖所示。
2. 作用
隔離客戶端和目標對象,而且可以擴展功能
3.分類
按照代理創建的時期來分類,可以分爲兩種:靜態代理、動態代理(jdk動態代理、cglib動態代理、Spring和AspectJ實現的動態代理);
4.具體實現
4.1 靜態代理
(1)創建抽象接口Subject,聲明目標對象需要讓代理對象幫忙做的事;
public interface Subject{
public void buyHouse();
}
(2) 創建目標對象類(RealSubject),實現Subject接口
public class RealSubject implements Subject {
@Override
public void buyHouse() {
System.out.println("我要買房");
}
}
(3)創建代理類,並通過代理類創建真實對象實例並訪問其方法
public SubjectProxy implements Subject {
private RealSubject realSubject;
public SubjectProxy(final RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void buyHouse() {
System.out.println("買房前的準備");
realSubject.buyHouse();
System.out.println("買房後裝修");
}
}
(4)客戶端調用
public class ProxyPattern {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
SubjectProxy proxy = new SubjectProxy(realSubject);
proxy.buyHouse();
}
}
靜態代理可以做到在不修改目標對象的前提下,對目標進行擴展。
但是因爲代理類需要實現與目標類相同的接口,需要爲每個目標對象都創建一個代理類,工作量較大;同時,一旦接口增加方法,目標對象與代理對象都要維護。
如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式
4.2 動態代理
動態代理的代理對象不需要實現接口;
JDK中生成代理對象的API:
代理類所在包:java.lang.reflect.Proxy
JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接受三個參數,完整的寫法是:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
該方法是Proxy類的靜態方法,接收三個參數:
- ClassLoader loader :指定當前目標對象使用的類加載器,獲取加載器的方法是固定的
- Class<?>[] interfaces :目標對象實現的接口類型,使用泛型方式確認類型
- InvocationHandler h : 事件處理,執行目標對象的方法時,會出發事件處理器的方法,會把當前執行目標對象的方法作爲參數傳入
代碼示例
接口類Subject和目標對象類是一樣的,保持不變,在這基礎上增加一個代理工廠類,用來獲取代理對象
(1)實現代理工廠
/**
* 創建動態代理對象
* 動態代理對象不需要實現接口,但需要指定接口類型
*/
public class ProxyFactory {
//維護一個目標對象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//給目標對象生成代理對象
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("買房前準備");
//執行目標對象的方法
Object returnValue = method.invoke(target, args);
System.out.println("買房後裝修");
return returnValue;
}
});
}
}
(2)客戶端測試
public class ProxyPattern {
public static void main(String[] args) {
//目標對象
RealSubject realSubject = new RealSubject();
//創建代理對象
Subject proxy = (Subject) new ProxyFactory(realSubject).getProxyInstance();
//執行方法
proxy.buyHouse();
}
}
動態代理總結:雖然相對於靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務接口的依賴,降低了耦合度。但是和靜態代理一樣,都要求目標對象是一個實現接口的目標對象,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候就可以使用以目標對象子類的方式類實現代理,這種方法就叫做:Cglib代理。
4.3 Cglib代理
Cglib代理,也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展。
- JDK的動態代理有一個限制,就是使用動態代理的對對象必須實現一個或多個接口,如果想代理沒有實現接口的類,就可以使用Cglib實現。
- Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展java類與實現java接口,它被許多AOP框架廣泛使用,例如Spring AOP和synaop,爲他們提供方法的interception(攔截)。
- Cglib包的底層是通過使用一個小而快的字節碼處理框架ASM來轉換字節碼並生成新的類。不鼓勵直接使用ASM,因爲它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。
Cglib子類代理實現方法:
(1)需要引入Cglib的jarw文件,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar
即可.
(2)引入功能包後,就可以在內存中動態構建子類
(3)代理類不能爲final,否則報錯
(4)目標對象的方法如果爲final/static,那麼就不會攔截,即不會執行目標對象額外的業務方法
代碼示例:
(1)目標對象類
/**
* 目標對象,沒有實現任何接口
*/
public class RealSubject{
public void buyHouse() {
System.out.println("我要買房");
}
}
(2)Cglib代理工廠
/**
* Cglib子類代理工廠
* 對RealSubject在內存中動態構建一個子類對象
*/
public class ProxyFactory implements MethodInterceptor{
//維護目標對象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//給目標對象創建一個代理對象
public Object getProxyInstance(){
//1.工具類
Enhancer en = new Enhancer();
//2.設置父類
en.setSuperclass(target.getClass());
//3.設置回調函數
en.setCallback(this);
//4.創建子類(代理對象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("買房前準備");
//執行目標對象的方法
Object returnValue = method.invoke(target, args);
System.out.println("買房後裝修");
return returnValue;
}
}
(3)客戶端測試
public class ProxyPattern {
public static void main(String[] args) {
//目標對象
RealSubject realSubject = new RealSubject();
//創建代理對象
Subject proxy = (Subject) new ProxyFactory(realSubject).getProxyInstance();
//執行方法
proxy.buyHouse();
}
}