代理模式
代理模式,顧名思義,即一個客戶不想或者不能直接訪問一個對象,需要通過一個稱為代理的第三方對象來實現(xiàn)間接引用。代理對象的作用就是客戶端和目標對象
之間的一個中介,通過代理對象可以隱藏不讓用戶看到的內容或實現(xiàn)額外的服務。
代理機制應用的場景有很多:比如在代理對象中實現(xiàn)緩存,驗證,權限控制等功能,真正的業(yè)務邏輯封裝在真實對象中。RMI遠程方法調用也用到了代理。當你調用一個遠程方法的時候,相當于調用這個方法的代理對象,
在代理對象中封裝了網絡請求等部分,真實對象存在于另一個進程上。重構老舊代碼的時候也常常會用到代理模式。
代理分兩種:靜態(tài)代理和動態(tài)代理
靜態(tài)代理
靜態(tài)代理即在代碼中手動實現(xiàn)代理模式。代理模式涉及到三個角色:
真實對象RealSubject、抽象主題Subject、代理對象Proxy


public class ProxyTest {
public static void main(String[] args) {
new Proxy("hello").request();
}
}
interface Subject {
void request();
}
class Proxy implements Subject{
String str;
RealSubject subject;
public Proxy(String string) {
str = string;
subject = new RealSubject(str);
}
@Override
public void request() {
System.out.println("代理對象驗證機制....");
subject.request();
}
}
class RealSubject implements Subject{
String str;
public RealSubject(String string) {
str = string;
}
@Override
public void request() {
System.out.println("真實對象打印str: " + str);
}
}
輸出:
代理對象驗證機制....
真實對象打印str: hello
上面的代碼模擬了一個代理對象實現(xiàn)驗證機制的過程??梢钥吹剑a很簡單,代理模式也很好理解。
(我們在真實生活中不也有代理么,,比如黃牛,幫你買到你買不到的火車票)
JDK動態(tài)代理實現(xiàn)
動態(tài)代理時較為高級的一種代理模式。典型的應用有Spring AOP,RMI。
在上面的靜態(tài)代理模式中,真實對象是事先存在的,并且作為代理對象的內部成員屬性。一個真實的對象必須對應一個代理對象,如果真實對象很多的話會導致類膨脹。
另外,如何在事先不知道真實對象的情況下使用代理代理對象,這都是動態(tài)代理需要解決的問題。
比如有n個類需要在執(zhí)行前打印幾行日志,而這n個類是無法通過源代碼修改的(從jar包中引入的)。通過靜態(tài)代理實現(xiàn)的話將會有n個新的代理類產生,而使用動態(tài)代理的話,只需一個類即可。
動態(tài)代理的實現(xiàn)方式有很多,我們只討論JDK中的動態(tài)代理實現(xiàn)。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
Subject subject = (Subject) new DynamicProxy().bind(new RealSubject("hello"));
subject.request();
}
}
interface Subject {
void request();
}
class RealSubject implements Subject{
String str;
public RealSubject(String string) {
str = string;
}
@Override
public void request() {
System.out.println("真實對象打印str: " + str);
}
}
class DynamicProxy implements InvocationHandler {
Object object;
public Object bind(Object object) {
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("代理對象驗證...");
return method.invoke(object, args);
}
}
輸出:
代理對象驗證...
真實對象打印str: hello
可以看到,動態(tài)代理實現(xiàn)了與靜態(tài)代理一樣的功能,但他的優(yōu)點在于代理的真實對象不是確定的,可以在運行時指定,增大了靈活性。如果我們有很多的真實對象需要代理訪問,并且他們代理對象中的內容
都實現(xiàn)了相同的功能,那么我們只需要一個動態(tài)代理類即可。
動態(tài)代理原理
我們通過觀察java.lang.reflect.Proxy的源碼來了解動態(tài)代理的原理。下面的代碼截取自openjdk7-b147 (安利一個不錯的搜索java源碼的網站:http://grepcode.com)

上面的方法截取自Proxy.newProxyInstance,可以看到,調用getProxyClass方法獲取到一個代理類class對象,然后使用該class對象通過反射方法實例化一個對象返回。
接下來觀察getProxyClass方法。

這部分代碼截取自getProxyClass,先從緩存中查詢是否已經生成過對應的class,若有,則直接返回該對象,沒有,則繼續(xù)下一步生成class

這部分代碼是代理類class對象的生成過程。其中:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);這行代碼調用ProxyGenerator.generateProxyClass返回了代理類class對象的字節(jié)碼byte序列,
proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);這一行則進行了類加載的工作,最終生成了代理類class對象。

generateProxyClass,其中的gen.generateClassFile()方法實現(xiàn)了字節(jié)碼的生成。

generateClassFile方法的實現(xiàn)。開頭調用的三個addProxyMethod方法將object類中的hashcode、equals、toString方法重寫,故對這三個方法的調用會傳遞到InvocationHandler.invoke方法當中。
注意,除了上述三個方法之外,調用代理類中Object定義的其他方法不會傳遞到invoke方法當中,也就是說,調用這些方法會執(zhí)行Object中的默認實現(xiàn)。
如果想要查看ProxyGenerator.generateProxyClass這個方法在運行時產生的代理類中寫了些什么,可以在main方法中加入:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
運行時會將生成的class文件保存到硬盤當中:$Proxy0.class
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0
extends Proxy
implements Subject
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void request()
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("Subject").getMethod("request", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
上面的代碼很好理解??梢钥吹絜quals、hashCode、toString以及我們Subject接口request方法的實現(xiàn)中都是調用了InvocationHandler.invoke方法,而這個InvocationHandler實例就是我們在Proxy.newProxyInstance中傳入的對象。
綜上,可以看到實現(xiàn)動態(tài)代理的幾個步驟:
1.實現(xiàn)InvocationHandler
2.獲得動態(tài)代理類,這一步又涉及到運行時代理類字節(jié)碼的生成和類加載
3.通過反射機制(getConstructor(InvocationHandler.class))獲取代理類的實例并返回該對象
4.調用代理對象的目標方法(也就是request方法,代理類也實現(xiàn)了Subject這個接口),調用轉發(fā)到InvocationHandler.invoke方法當中,執(zhí)行invoke的邏輯(我們自己的InvocationHandler實現(xiàn))
至此,我們就了解了動態(tài)代理的運行原理。動態(tài)代理的機制也有一些缺陷,比如他代理的必須是接口方法。看一下我們上面生成的$Proxy0.class,可知這個代理類已經默認繼承了類Proxy,所以,他只能通過實現(xiàn)我們提供的接口來代理我們的方法。在invoke方法中,我們可以通過對傳入的代理類、方法和參數(shù)來進行判斷,對不同的方法實現(xiàn)不同的業(yè)務邏輯。