代理模式與AOP
代理模式基本
設計模式之禪在講解代理模式時用了一個遊戲代練的比喻。這個比喻非常的有代表性,對於理解代理模式很有幫助。它大致的思想是:大家都有過玩遊戲的經歷,也知道遊戲代練。那麼事實上游戲的代練在幫我的遊戲賬號打怪升級的時候,和代理模式裡面的代理類做的事情不正是一樣的事情嗎?
代理模式的定義如下:Provide a surrogate or placeholder for another object to control acess to it.(為其他物件提供一種代理以控制對這個物件的訪問)。其通用類圖如下:
- Subject是一個抽象類也可以是一個介面,是一個最普通的業務型別定義無特殊要求
- RealSubject是Subject的子類或實現類,它才是被代理角色。是業務邏輯的具體執行者。
- Proxy叫做委託類,代理類。它負責對真實物件的應用。它在真實物件處理完畢前後做預處理和善後處理工作。
上面是一個靜態代理的場景。代理一般實現的模式為JDK靜態代理:建立一個介面,然後建立被代理的類實現該介面並且實現該介面中的抽象方法。之後再建立一個代理類,同時使其也實現這個介面。在代理類中持有一個被代理物件的引用,而後在代理類方法中呼叫該物件的方法。
代理模式的優點
- 職責清晰
真實的角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過後期的代理完成一件完成事務,附帶的結果就是程式設計簡潔清晰。
- 高擴充套件性
具體主題角色是隨時都會發生變化的,只要它實現了介面,甭管它如何變化,都逃不脫如來佛的手掌(介面),那我們的代理類完全就可以在不做任何修改的情況下使用。
- 智慧化
這在我們以上講解中還沒有體現出來,不過在我們以下的動態代理章節中你就會看到代理的智慧化,讀者有興趣也可以看看Struts是如何把表單元素對映到物件上的。
動態代理
動態代理是在實現階段不用關心代理誰,而在執行階段才指定代理那一個物件,相對的來說,自己寫代理類的方式就是靜態代理。
現在有一個非常流行的名稱叫做:面向橫切面程式設計,也就是AOP(Aspect Oriented Programming),其核心就是採用了動態代理機制。
JDK動態代理
核心:代理模式加反射。具體說代理類動態實現介面類,在動態生成的實現類裡面委託為hanlder去呼叫原始實現類方法
JDK動態代理與靜態代理有相同之處,都要建立代理類,代理類都要實現介面。但是不同之處在於:
JDK靜態代理是通過直接編碼建立的,而JDK動態代理是利用反射機制在執行時建立代理類的。即JDK動態代理是更加通用的的一種方式,因為我們需要被代理的類往往是不止一個的。
若我們要自己實現一個JDK代理的話,則其核心程式碼如下:
public class JDKProxy implements InvocationHandler {
//產生代理物件,被代理的物件必須實現一個介面
public Object newProxy(Object targetObject) {//將目標物件傳入進行代理
Object object = Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);//返回代理物件
return object;
}
//實現InvocationHandler的 invoke方法
public Object invoke(Object proxy, Method method, Object[] args)//invoke方法
throws Throwable {
System.out.println("before"); //一般我們進行邏輯處理的函式比如這個地方是模擬檢查許可權
Object ret = method.invoke(proxy, args); //呼叫invoke方法,ret儲存該方法的返回值,通過反射獲取代理方法然後進行呼叫,而在cglib中則是直接呼叫的,因為繼承的緣故
System.out.println("after"); //後置增強
return ret;
}
}
先來看第一個方法newProxy。
這個方法的引數即被代理的目標物件。這個方法的目標是根據被代理物件去建立一個代理類。關鍵方法為
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
此方法一共三個引數:
- ClassLoader 物件
- 一組interface介面
- InvocationHandler物件
該方法關鍵實現如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
...
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
...
}
}
- getProxyClass0方法的兩個引數分別是classloader以及被代理類的介面,這個方法會生成一個Class物件出來。即代理類。
- 然後使用反射獲得構造器: final Constructor<?> cons = cl.getConstructor(constructorParams);
- 返回例項:return cons.newInstance(new Object[]{h});
生成的代理類:
- 會去實現代理的介面,由於java不能多繼承,這裡已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支援對實現類的代理,只支援介面的代理。
- 提供了一個使用InvocationHandler作為引數的構造方法
- 生成靜態程式碼塊來初始化介面中方法的Method物件,以及Object類的equals、hashCode、toString方法。
- 重寫了Object類的equals、hashCode、toString,它們都只是簡單的呼叫了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
- 代理類實現代理介面的目標方法中,只是簡單的呼叫了InvocationHandler的invoke方法,我們可以在invoke方法中進行一些特殊操作,甚至不呼叫實現的方法,直接返回。
經過我們的反編譯,該類大致如下:
import com.dr.designPattern.proxy.dynamicProxy.UserManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy extends Proxy implements UserManager {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void addUser(String var1, String var2) throws {
try {
super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void delUser(String var1) throws {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.dr.designPattern.proxy.dynamicProxy.UserManager").getMethod("addUser", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.dr.designPattern.proxy.dynamicProxy.UserManager").getMethod("delUser", new Class[]{Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
第二個方法是實現的InvocationHandler的invoke方法。
在動態代理中InvocationHandler是核心,每個代理例項都具有一個關聯的呼叫處理程式(InvocationHandler)。對代理例項呼叫方法時,將對方法呼叫進行編碼並將其指派到它的呼叫處理程式(InvocationHandler)的 invoke
方法。所以對代理方法的呼叫都是通InvocationHadler的invoke來實現中,而invoke方法根據傳入的代理物件,方法和引數來決定呼叫代理的哪個方法。invoke方法定義如下:
public Object invoke(Object proxy, Method method, Object[] args)
在第一個方法生成的代理類當中會呼叫這個InvocationHandler中的invoke方法,然後把需要代理的方法作為第二個引數傳進去。我們在實現invoke方法時,事實上就是利用反射原理,去呼叫目標方法,同時前後就可以做一些增強。
注意,這裡的代理類是動態生成的,根據目標類和目標方法,因此自然而然,呼叫目標方法以及它的前後增強也就是動態的。
問題1:為什麼JDK動態代理只能代理實現了介面的類?
因為JDK動態代理生成的代理類需要去繼承Proxy類,而java是單繼承的,因此它只能去實現被代理類的介面(實現的介面)
問題2:為什麼JDK動態代理生成的代理類要去繼承Proxy類?
不仔細看的話,這裡的代理類會給人一種繼承Proxy類沒有用的地方,很有迷惑性,我也是將生成的代理類程式碼親自拿來試了一下才發現它的用處。注意,在代理類$Proxy中有多處方法呼叫都是使用的super關鍵字。而Super關鍵字不就正是在呼叫父類的方法嗎,super又不能調有介面中的方法,介面中的方法也沒有任何實現邏輯。
在代理類$Proxy中呼叫父類的方法都是使用了InvocationHandler類的例項,在代理類$Proxy中通過構造方法傳入進來,而我們應該還記得,在Proxy的newProxyInstance方法中的第三個引數,正是InvocationHandler的一個例項。
因此這裡呼叫方法時,自然就是使用這個例項去呼叫invoke方法,同時傳入對應的方法引數。這下,所有的東西都串聯起來了,問題三的答案事實上也已經有了。
這裡同時把oracle介紹代理的官方文件地址放在這裡,作為參考。
問題3:為什麼我們去呼叫代理類的目標方法,它會去呼叫invoke方法?
因為JDK生成真正的代理類中,是繼承了Proxy類並實現了我們定義的被代理介面,而這個代理類在實現我們定義的介面方法時,是通過反射呼叫了InvocationHandlerImpl的invoke方法,然後在這個invoke方法當中,我們實現了增強的邏輯以及對被代理方法的真正呼叫。
CGLib動態代理
核心:代理模式加繼承
具體說被代理類和代理類是繼承關係,所以代理類是可以賦值給被代理類的,如果被代理類有介面,那麼代理類也可以賦值給介面。
方法的呼叫並不是通過反射來完成的,而是直接對方法進行呼叫,因為是繼承,這一點與jdk動態代理是不一樣的
另外JDK代理只能對介面進行代理,Cglib則是對實現類進行代理。重點程式碼如下:
public class CGLibProxy implements MethodInterceptor {
//根據目標物件生成一個子類作為他的代理類
public Object createProxyObject(Object obj) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());//設定父類為被代理類
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;// 返回代理物件
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object obj = methodProxy.invoke(proxy, args);
System.out.println("after");
return obj;
}
}
總的來說思想和JDK動態代理差別不大,都是根據被代理類生成一個代理類,MethodInterceptor類的角色和JDK動態代理中的InvocationHandler是一樣的。但是Cglib的實現邏輯更為複雜。
但是在CGLib代理類中,因為是繼承的緣故,因此會重寫被代理類的方法,重寫的邏輯就是呼叫我們這裡實現的intercept方法,同時傳入對應的引數。
第一個方法createProxyObject
這個方法會生成一個代理類,這個代理類當中重寫了被代理類中的目標方法,同時,也提供了方法直接去呼叫被代理類的方法。
第二個方法intercept方法
在我們實現的intercept方法當中,當然也可以使用反射直接呼叫method方法,但是這裡採取的實現是呼叫了MethodProxy類的方法去呼叫,這個方法沒有用到反射機制。它的實現如下:
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
} catch (IllegalArgumentException var5) {
if (this.fastClassInfo.i1 < 0) {
throw new IllegalArgumentException("Protected method: " + this.sig1);
} else {
throw var5;
}
}
}
可以看到,是用了一個叫做FastClass的類來實現的。
FastClass實現機制簡介
FastClass其實就是對Class物件進行特殊處理,提出下標概念index,通過索引儲存方法的引用資訊,將原先的反射呼叫,轉化為方法的直接呼叫,從而體現所謂的fast,下面通過一個例子瞭解一下FastClass的實現機制。
1、定義原類
class Test {
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
2、定義Fast類
class FastTest {
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
}
在FastTest中有兩個方法,getIndex
中對Test類的每個方法根據hash建立索引,invoke
根據指定的索引,直接呼叫目標方法,避免了反射呼叫。所以當呼叫methodProxy.invoke
方法時,實際上是呼叫代理類的方法,代理類則是直接呼叫了被代理類的原方法(因為是繼承的緣故,可以直接呼叫)。
在CGLibProxy類中重寫的在intercept方法當中就可以進行邏輯增強,事實上,從技術上講這裡也可以通過反射呼叫被代理的原方法。