談談java代理模式的認識二——動態代理(JDK)
讓我們就接著上篇部落格的靜態代理來開始今天的動態代理。
一、動態代理
靜態代理需要在執行之前就寫好代理類,這樣就造成了程式碼的大量重複,所以我們通過動態代理在執行時期動態生成業務類的代理類,那麼動態代理類是如何實現的呢?
動態代理類的位元組碼在程式執行時由Java反射機制動態生成,無需程式設計師手工編寫它的原始碼。動態代理類不僅簡化了程式設計工作,而且提高了軟體系統的可擴充套件性,因為Java 反射機制可以生成任意型別的動態代理類。java.lang.reflect 包中的Proxy類和InvocationHandler 介面提供了生成動態代理類的能力。 原來是利用反射的機制來實現的,今天我們不討論反射,我們看JDK的動態代理的實現。
JDK動態代理中包含一個類和一個介面: InvocationHandler介面,和我們定義的一個實現類“Proxy“,這是一個萬能的代理類,我們就是通過這個代理類來動態代理的。
InvocationHandler介面:
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}
引數說明:
Object proxy:指被代理的物件。
Method method:要呼叫的方法
Object[] args:方法呼叫時所需要的引數
可以將InvocationHandler介面的子類想象成一個代理的最終操作類,替換掉ProxySubject。
Proxy類:
Proxy類是專門完成代理的操作類,可以通過此類為一個或多個介面動態地生成實現類,此類提供瞭如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
引數說明:
ClassLoader loader:類載入器
Class<?>[] interfaces:得到全部的介面
InvocationHandler h:得到InvocationHandler介面的子類例項
在Proxy類中的newProxyInstance()方法中需要一個ClassLoader類的例項,ClassLoader實際上對應的是類載入器,在Java中主要有一下三種類載入器;
Booststrap ClassLoader:此載入器採用C++編寫,一般開發中是看不到的;
Extendsion ClassLoader:用來進行擴充套件類的載入,一般對應的是jre\lib\ext目錄中的類;
AppClassLoader:(預設)載入classpath指定的類,是最常使用的是一種載入器。
二、使用JDK的動態代理
還是使用上篇部落格的程式碼,我們也是簡單的說三步來實現:業務介面,業務實現類的建立;代理類的建立,業務的呼叫。
我們看程式碼實現:
<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"> /**
* 定義一個業務介面
* @author Cassie
*/
public interface Account {
// 查詢
public void queryAccount ();
// 修改
public void updateAccount ();
}
/**
* 介面實現類(包含業務邏輯)
* 即:委託類
* @author Cassie
*/
public class AccountImpl implements Account{
@Override
public void queryAccount() {
System.out.println("查詢方法...");
}
@Override
public void updateAccount() {
System.out.println("修改方法...");
}
}</span></span>
關鍵的動態代理是如下幾行程式碼:該處的代理類Proxy 不去實現具體的某個業務介面,而是實現了JDK提供的InvocationHander類。在Proxy中我們不需要知道具體的業務類,即委託類,在執行之前,講委託類和代理類進行解耦,在執行期才發生的聯絡,是通過這句話實現的:private object target。然後在呼叫的時候通過getInstance 在確定誰是委託物件。
<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">/**
* JDK動態代理代理類
*
* @author Cassie
*
*/
public class Proxy implements InvocationHandler {
private Object target;
/**
* 繫結委託物件並返回一個代理類
* @param target
* @return
*/
public Object GetInstance(Object target) {
this.target = target;
//取得代理物件
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
/**
* 呼叫方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
System.out.println("before");
//執行方法
result=method.invoke(target, args);
System.out.println("after");
return result;
}
} </span></span>
再看我們的客戶端呼叫:這時候才傳入真正的委託類。
<span style="font-family:SimSun;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class TestProxy {
public static void main(String[] args) {
Proxy proxy = new Proxy();
// 在這裡進行真正的物件傳入
Account account= (Account )proxy.getInstance(new AccountImpl());
proxy.queryAccount();
}
}</span></span>
以上過程就是JDK動態代理的實現,我們發現JDK動態代理幫我們講代理類和委託類的繫結關係延遲了,什麼時候用,什麼時候調,這樣我們的業務類不僅得到了增強,還簡化了程式碼。
當然,JDK的動態代理也有缺陷,不知道你發現了沒有,這裡的每個委託類都必須是要有介面的,也就是說JDK的動態代理依靠介面實現,要是我一個沒有介面的類想被代理怎麼辦?
如果有些類並沒有實現介面,則不能使用JDK代理,這就要使用cglib動態代理了。請看下篇部落格。