代理模式
1.生活中:
代理就是一個人或者一個組織代表其他人去做一件事的現實生活中的。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
2.官方:
代理模式是對象的結構模式。代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用
一、靜態代理
類圖結構如下
在代理模式中的角色:
●抽象主題角色:聲明了目標對象和代理對象的共同接口,這樣一來在任何可以使用目標對象的地方都可以使用代理對象。
●真實主題角色:定義了代理對象所代表的目標對象。
●代理主題角色:代理對象內部含有目標對象的引用,從而可以在任何時候操作目標對象;代理對象提供一個與目標對象相同的接口,以便可以在任何時候替代目標對象。代理對象通常在客戶端調用傳遞給目標對象之前或之後,執行某個操作,而不是單純地將調用傳遞給目標對象。它可以增加一些真實主題裡面沒有的功能。
生活中的例子:過年加班比較忙,沒空去買火車票,這時可以打個電話到附近的票務中心,叫他們幫你買張回家的火車票,當然這會附加額外的勞務費。但要清楚票務中心自己並不賣票,只有火車站才真正賣票,票務中心賣給你的票其實是通過火車站實現的。這點很重要!
上面這個例子,你就是“客戶”,票務中心就是“代理角色”,火車站是“真實角色”,賣票稱為“抽象角色”!
代碼
抽象主題角色
//抽象角色:聲明真實對象和代理對象的共同接口;
public interface TicketManager {
/**
* 售票
*/
public void soldTicket();
/**
* 改簽
*/
public void changeTicket();
/**
* 退票
*/
public void returnTicket();
}
真實主題角色
public class TicketManagerImpl implements TicketManager {
@Override
public void soldTicket() {
//checkIdentity();
System.out.println("售票");
}
@Override
public void changeTicket(){
//checkIdentity();
System.out.println("改簽");
}
@Override
public void returnTicket() {
//checkIdentity();
System.out.println("退票");
}
/**
* 身份驗證
*/
public void checkIdentity(){
System.out.println("身份驗證");
}
}
代理主題角色(添加了身份驗證功能)
public class StaticProxyTicketManager implements TicketManager {
TicketManager ticketManager;//目標對象的引用
public StaticProxyTicketManager(TicketManager ticketManager) {
this.ticketManager = ticketManager;
}
@Override
public void soldTicket() {
checkIdentity();
ticketManager.soldTicket();
}
@Override
public void changeTicket() {
checkIdentity();
ticketManager.changeTicket();
}
@Override
public void returnTicket() {
checkIdentity();
ticketManager.changeTicket();
}
/**
* 身份驗證
*/
public void checkIdentity(){
System.out.println("身份驗證--------------");
}
}
第二個代理主題角色(添加了日志功能)
//代理類 實現同一個接口
public class LogProxy implements TicketManager {
TicketManager ticketManager;//目標類的引用
public LogProxy(TicketManager ticketManager){
this.ticketManager=ticketManager;
}
@Override
public void soldTicket() {
ticketManager.soldTicket();
log();//後置增強
}
@Override
public void changeTicket() {
ticketManager.changeTicket();
log();
}
@Override
public void returnTicket() {
ticketManager.returnTicket();
log();
}
//增強
private void log() {
System.out.println("日志...");
}
}
客戶端
public class Test {
public static void main(String[] args) {
//裝飾模式 new TicketManagerImpl() 真實的目標對象
//TicketManager tm=new StaticProxyTicketManager(new TicketManagerImpl());
TicketManager tm=new LogProxy(new StaticProxyTicketManager(new TicketManagerImpl()));
tm.soldTicket();
tm.changeTicket();
tm.returnTicket();
}
}
結果:
身份驗證————–
售票
日志…
身份驗證————–
改簽
日志…
身份驗證————–
改簽
日志…
從上面例子可以看出 客戶端通過代理來購票 而代理實際上不能賣票給客戶,他實際上是通過目標對象賣票給客戶的,也就是說他是通過真實主題的目標對象實現給客戶端賣票的功能,他只是一個中介,但我們可以在它裡面增加一些功能,比如身份驗證或者宣傳打廣告等其他的功能。
靜態代理類:在程序運行前,代理類的.class文件就已經存在了,已確定被代理的對象
靜態代理:
優點:對真實對象進行封裝,不會修改目標類的代碼。
缺點:
1.多個不同類型目標對象需要代理時,我就需要建立多個代理類,造成類的膨脹
2.代碼的冗余
3.編譯期加入,不夠靈活
二、動態代理
描述(這個描述從網上看到的,相對比較容易理解)
動態代理(Dynamic Proxy):相比靜態代理,動態代理具有更強的靈活性,因為它不用在我們設計實現的時候就指定某一個代理類來代理哪一個被代理對象,我們可以把這種指定延遲到程序運行時由JVM來實現。
所謂代理,就是需要代理類和被代理類有相同的對外接口或者說成服務,所以代理類一般都必須實現了所有被代理類已實現的接口,因為接口就是制定了一系列對外服務的標准。
1.JDK實現動態代理
正因為動態代理有這樣靈活的特性,所以我們在設計動態代理類(DynamicProxy)時不用顯式地讓它實現與真實主題類(RealSubject)相同的接口(interface),而是把這種實現推遲到運行時。
為了能讓DynamicProxy類能夠在運行時才去實現RealSubject類已實現的一系列接口並執行接口中相關的方法操作,需要讓 DynamicProxy類實現JDK自帶的java.lang.reflect.InvocationHandler接口,該接口中的invoke() 方法能夠讓DynamicProxy實例在運行時調用被代理類的“對外服務”,即調用被代理類需要對外實現的所有接口中的方法,也就是完成對真實方法的調 用,Java幫助文檔中稱這些真實方法為處理程序。
按照上面所述,我們肯定必須先把被代理類RealSubject已實現的所有interface都加載到JVM中,不然JVM怎麼能夠找到這些方法呢?明白了這個道理,那麼我們就可以創建一個被代理類的實例,獲得該實例的類加載器ClassLoader。
所謂的類加載器ClassLoader,就是具有某個類的類定義,即類的內部相關結構(包括繼承樹、方法區等等)。
更重要的是,動態代理模式可以使得我們在不改變原來已有的代碼結構的情況下,對原來的“真實方法”進行擴展、增強其功能,並且可以達到控制被代理對 象的行為的目的。請詳看下面代碼中的DynamicProxy類,其中必須實現的invoke()方法在調用被代理類的真實方法的前後都可進行一定的特殊 操作。這是動態代理最明顯的優點
類圖
代碼
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyTicketManager implements InvocationHandler {
private Object targetObject;
/**
* 目標的初始化方法,根據目標生成代理類
*
* @param targetObject
* @return
*/
public Object newProxyInstance(Object targetObject) {
this.targetObject = targetObject;
// 第一個參數,目標對象 的裝載器
// 第二個參數,目標接口已實現的所有接口,而這些是動態代理類要實現的接口列表
// 第三個參數, 調用實現了InvocationHandler的對象生成動態代理實例,當你一調用代理,代理就會調用InvocationHandler的invoke方法
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),
this);
}
/**
* 反射,這樣你可以在不知道具體的類的情況下,根據配置的參數去調用一個類的方法。在靈活編程的時候非常有用。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 檢查
checkIdentity();
Object ret = null;
try {
// 調用目標方法
ret = method.invoke(targetObject, args);
// 執行成功,打印成功信息
log();
} catch (Exception e) {
e.printStackTrace();
// 失敗時,打印失敗信息
System.out.println("error-->>" + method.getName());
throw e;
}
return ret;
}
/**
* 身份驗證
*/
public void checkIdentity(){
System.out.println("身份驗證--------------");
}
public void log(){
System.out.println("日志..." );
}
}
客戶端
public class Test {
public static void main(String[] args) {
DynamicProxyTicketManager dynamicProxyTicketManager=new DynamicProxyTicketManager();
TicketManager tm=(TicketManager) dynamicProxyTicketManager.newProxyInstance(new TicketManagerImpl());
tm.soldTicket();
tm.changeTicket();
tm.returnTicket();
}
}
結果同上
優缺點
優點:
1、一個動態代理類更加簡單了,可以解決創建多個靜態代理的麻煩,避免不斷的重復多余的代碼
2、調用目標代碼時,會在方法“運行時”動態的加入,決定你是什麼類型,才調誰,靈活
缺點:
1、系統靈活了,但是相比而言,效率降低了,比靜態代理慢一點
2、動態代理比靜態代理在代碼的可讀性上差了一點,不太容易理解
3、JDK動態代理只能對實現了接口的類進行代理
總結
各有各的好,具體情況具體討論
2.Cglib實現動態代理
描述(網上整理)
AOP的源碼中用到了兩種動態代理來實現攔截切入功能:jdk動態代理和cglib動態代理。
兩種方法同時存在,各有優劣。jdk動態代理是由java內部的反射機制來實現的 ,cglib動態代理底層則是借助asm來實現的。總的來說,反射機制在生成類的過程中比較高效,而asm在生成類之後的相關執行過程中比較高效(可以通 過將asm生成的類進行緩存,這樣解決asm生成類過程低效問題)。還有一點必須注意:jdk動態代理的應用前提,必須是目標類基於統一的接口。如果沒有 上述前提,jdk動態代理不能應用。由此可以看出,jdk動態代理有一定的局限性,cglib這種第三方類庫實現的動態代理應用更加廣泛, 且在效率上更有優勢。
JDK的動態代理機制只能代理實現了接口的類,否則不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為采用的是繼承,所以不能對final修飾的類進行代理。
介紹:
CGLIB的核心類:
net.sf.cglib.proxy.Enhancer – 主要的增強類
net.sf.cglib.proxy.MethodInterceptor – 主要的方法攔截類,它是Callback接口的子接口,需要用戶實現
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method類的代理類,可以方便的實現對源對象方法的調用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//雖然第一個參數是被代理對象,也不會出現死循環的問題。
net.sf.cglib.proxy.MethodInterceptor接口是最通用的回調(callback)類型,它經常被基於代理的AOP用來實現攔截(intercept)方法的調用。這個接口只定義了一個方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;
第一個參數是代理對像,第二和第三個參數分別是攔截的方法和方法的參數。原來的方法可能通過使用java.lang.reflect.Method 對象的一般反射調用,或者使用 net.sf.cglib.proxy.MethodProxy對象調用。net.sf.cglib.proxy.MethodProxy通常被首選使 用,因為它更快
代碼
public class CglibDynamicProxyTicketManager implements MethodInterceptor {
private Object targetObject;//目標對象
/**
* 創建代理對象
*
* @param targetObject
* @return
*/
public Object getInstance(Object targetObject) {
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer(); // 用這個類來創建代理對象(被代理類的子類): 並設置父類;設置回調;
enhancer.setSuperclass(this.targetObject.getClass()); // 設置被代理類作為其父類的代理目標
// 回調方法
enhancer.setCallback(this); // 設置回調--當這個代理對象的方法被調用時 回調方法intercept()會被執行
// 創建代理對象
return enhancer.create();
}
@Override
//回調方法
// methodProxy 代理的類的方法
/**
* methodProxy 會調用父類(目標對象)的被代理的方法,比如soldTicket方法等
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object result = null;
checkIdentity();//前置增強
result=methodProxy.invokeSuper(obj, args); //調用新生成的cglib的代理對象 所屬的父類的被代理的方法
log();//後置增強
return result;
}
/**
* 身份驗證
*/
public void checkIdentity(){
System.out.println("身份驗證--------------");
}
public void log(){
System.out.println("日志..." );
}
}
客戶端
public class Test {
public static void main(String[] args) {
CglibDynamicProxyTicketManager cglibdynamicProxyTicketManager=new CglibDynamicProxyTicketManager();
//生成代理對象
TicketManager tm=(TicketManager) cglibdynamicProxyTicketManager.getInstance(new TicketManagerImpl());
tm.soldTicket();//當調用代理對象的被代理對象的方法時 會自動回調 代理類中的Intercept()方法
tm.changeTicket();
tm.returnTicket();
}
}
結果同上