java梳理-靜態代理和動態代理有什麼區別
為某個物件提供一個代理,以控制對這個物件的訪問。 代理類和委託類有共同的父類或父介面,這樣在任何使用委託類物件的地方都可以用代理物件替代。代理類負責請求的預處理、過濾、將請求分派給委託類處理、以及委託類執行完請求後的後續處理。
代理模式是經典設計模式中的一種,屬於物件建立型設計模式。
從圖中可以看出,代理介面(Subject)、代理類(ProxySubject)、委託類(RealSubject)形成一個“品”字結構。 根據代理類的生成時間不同可以將代理分為靜態代理和動態代理兩種。
下面以一個模擬需求說明靜態代理和動態代理: 二 靜態代理 由程式設計師建立或工具生成代理類的原始碼,再編譯代理類。所謂靜態也就是在程式執行前就已經存在代理類的位元組碼檔案,代理類和委託類的關係在執行前就確定了。
/** * 代理介面。處理給定名字的任務。 */ public interface Subject { /** * 執行給定名字的任務。 * @param taskName 任務名 */ public void dealTask(String taskName); }
RealSubject(真實角色):/** * 真實類,處理具體業務 * @author zhangliang * * 2016年4月5日 下午6:53:16 */ public class RealSubject implements Subject { @Override public void dealTask(String taskName) { // TODO Auto-generated method stub System.out.println("realSubject正在執行任務:"+taskName); } }
/** * 代理類,實現類代理介面 * @author zhangliang * 2016年4月5日 下午6:58:06 */ public class ProxySubject implements Subject { //代理類持有一個委託類的物件引用 private Subject delegate; public ProxySubject(Subject delegate) { this.delegate = delegate; } @Override public void dealTask(String taskName) { // TODO Auto-generated method stub System.out.println("do something before"); delegate.dealTask(taskName); System.out.println("do something after"); } }
靜態代理類優缺點
優點:業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點。
缺點:
1)代理物件的一個介面只服務於一種型別的物件,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程式規模稍大時就無法勝任了。
2)如果介面增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。 三 動態代理 代理類處理的邏輯很簡單:在呼叫某個方法前及方法後做一些額外的業務。換一種思路就是:在觸發(invoke)真實角色的方法之前或者之後做一些額外的業務。那麼,為了構造出具有通用性和簡單性的代理類,可以將所有的觸發真實角色動作交給一個觸發的管理器,讓這個管理器統一地管理觸發。這種管理器就是Invocation Handler。
動態代理模式的結構跟上面的靜態代理模式稍微有所不同,多引入了一個InvocationHandler角色。
先解釋一下InvocationHandler的作用:
在靜態代理中,代理Proxy中的方法,都指定了呼叫了特定的realSubject中的對應的方法:
在上面的靜態代理模式下,Proxy所做的事情,無非是呼叫在不同的request時,呼叫觸發realSubject對應的方法;更抽象點看,Proxy所作的事情;在Java中 方法(Method)也是作為一個物件來看待了,動態代理工作的基本模式就是將自己的方法功能的實現交給 InvocationHandler角色,外界對Proxy角色中的每一個方法的呼叫,Proxy角色都會交給InvocationHandler來處理,而InvocationHandler則呼叫具體物件角色的方法。如下圖所示:
在這種模式之中:代理Proxy 和RealSubject 應該實現相同的功能,這一點相當重要。(我這裡說的功能,可以理解為某個類的public方法)
在面向物件的程式設計之中,如果我們想要約定Proxy 和RealSubject可以實現相同的功能,有兩種方式:
a.一個比較直觀的方式,就是定義一個功能介面,然後讓Proxy 和RealSubject來實現這個介面。
b.還有比較隱晦的方式,就是通過繼承。因為如果Proxy 繼承自RealSubject,這樣Proxy則擁有了RealSubject的功能,Proxy還可以通過重寫RealSubject中的方法,來實現多型。
其中JDK中提供的建立動態代理的機制,是以a 這種思路設計的,而cglib 則是以b思路設計的。
JDK的動態代理建立機制----通過介面
動態代理類的原始碼是在程式執行期間由JVM根據反射等機制動態的生成,所以不存在代理類的位元組碼檔案。代理類和委託類的關係是在程式執行時確定。
1、先看看與動態代理緊密關聯的Java API。1)java.lang.reflect.Proxy
這是 Java 動態代理機制生成的所有動態代理類的父類,它提供了一組靜態方法來為一組介面動態地生成代理類及其物件。 // 方法 1: 該方法用於獲取指定代理物件所關聯的呼叫處理器 static InvocationHandler getInvocationHandler(Object proxy)
// 方法 2:該方法用於獲取關聯於指定類裝載器和一組介面的動態代理類的類物件
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
// 方法 3:該方法用於判斷指定類物件是否是一個動態代理類
static boolean isProxyClass(Class cl)
// 方法 4:該方法用於為指定類裝載器、一組介面及呼叫處理器生成動態代理類例項
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
2)java.lang.reflect.InvocationHandler 這是呼叫處理器介面,它自定義了一個 invoke 方法,用於集中處理在動態代理類物件上的方法呼叫,通常在該方法中實現對委託類的代理訪問。每次生成動態代理類物件時都要指定一個對應的呼叫處理器物件。
invoke(Object obj,Method method, Object[] args)。在實際使用時,第一個引數obj一般是指代理 類,method是被代理的方法,args為該方法的引數陣列。 這個抽 象方法在代理類中動態實現。
2、動態代理實現步驟
具體步驟是:
a. 實現InvocationHandler介面建立自己的呼叫處理器
b. 給Proxy類提供ClassLoader和代理介面型別陣列建立動態代理類
c. 以呼叫處理器型別為引數,利用反射機制得到動態代理類的建構函式
d. 以呼叫處理器物件為引數,利用動態代理類的建構函式建立動態代理類物件 demo如下:
/** * 動態代理類對應的呼叫處理程式類 */ public class SubjectInvocationHandler implements InvocationHandler { //代理類持有一個委託類的物件引用 private Object delegate; public SubjectInvocationHandler(Object delegate) { this.delegate = delegate; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy:"+proxy.getClass().getName()); System.out.println("method:"+method.getName()); System.out.println("args:"+args[0].getClass().getName()); System.out.println("Before invoke method..."); Object object=method.invoke(delegate, args);//普通的Java反射程式碼,通過反射執行某個類的某方法 System.out.println("After invoke method..."); return object; } }
後來看了下,補充如下動態代理的核心其實就是代理物件的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。
原始碼如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.獲取代理類class
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler. 獲取待invocation handler引數的構造器
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}
其中newInstance只是呼叫Constructor.newInstance來構造相應的代理類例項,我們看下getProxyClass(loader, interfaces)方法用於獲取代理類,它主要做了三件事情:在當前類載入器的快取裡搜尋是否有代理類,沒有則生成代理類並快取在本地JVM裡。清單三:查詢代理類。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 代理的介面數量不能超過65535(沒有這種變態吧)
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// JDK對代理進行了快取,如果已經存在相應的代理類,則直接返回,否則才會通過ProxyClassFactory來建立代理
return proxyClassCache.get(loader, interfaces);
}
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
具體的快取邏輯這裡暫不關心,在這個get方法裡,我們看到了如下程式碼:
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
ProxyClassFactory是Proxy的一個靜態內部類,實現了WeakCache的內部介面BiFunction的apply方法,主要生成並載入程式碼:
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
try {
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
此處分別是:生成代理類的位元組碼檔案並儲存到硬碟中(預設不儲存到硬碟)
使用類載入器將位元組碼載入到記憶體中
ProxyGenerator是sun.misc包中的類,它沒有開源,感興趣的可以用反編譯軟體開啟jre\lib\rt.jar。
再回顧下,由Proxy類的getProxyClass0()方法生成目標代理類,然後拿到該類的構造方法,最後通過反射的newInstance方法,產生代理類的例項物件。使用Java動態代理機制的好處:
1、減少程式設計的工作量:假如需要實現多種代理處理邏輯,只要寫多個代理處理器就可以了,無需每種方式都寫一個代理類。 2、系統擴充套件性和維護性增強,程式修改起來也方便多了(一般只要改代理處理器類就行了)。
缺點:
代理類和委託類需要都實現同一個介面。也就是說只有實現了某個介面的類可以使用Java動態代理機制。但是,事實上使用中並不是遇到的所有類都會給你實現一個介面。因此,對於沒有實現介面的類,目前無法使用該機制。 那怎麼辦呢?幸好我們有cglib。“CGLIB(Code Generation Library),是一個強大的,高效能,高質量的Code生成類庫,它可以在執行期擴充套件Java類與實現Java介面。”待整理。