靜態代理和動態代理的理解__Java

來源:互聯網
上載者:User
Java 靜態代理

靜態代理通常用於對原有商務邏輯的擴充。比如持有二方包的某個類,並調用了其中的某些方法。然後出於某種原因,比如記錄日誌、列印方法執行時間,但是又不好將這些邏輯寫入二方包的方法裡。所以可以建立一個代理類實現和二方方法相同的方法,通過讓代理類持有真實對象,然後在原代碼中調用代理類方法,來達到添加我們需要商務邏輯的目的。

這其實也就是代理模式的一種實現,通過對真實對象的封裝,來實現擴充性。

一個典型的代理模式通常有三個角色,這裡稱之為**代理三要素** 共同介面

public interface Action {    public void doSomething();}
真實對象
public class RealObject implements Action{    public void doSomething() {        System.out.println("do something");    }}
代理對象
public class Proxy implements Action {    private Action realObject;    public Proxy(Action realObject) {        this.realObject = realObject;    }    public void doSomething() {        System.out.println("proxy do");        realObject.doSomething();    }}

運行代碼

    Proxy proxy = new Proxy(new RealObject());    proxy.doSomething();

這種代理模式也最為簡單,就是通過proxy持有realObject的引用,並進行一層封裝。 靜態代理的優點和缺點

先看看代理模式的優點: 擴充原功能,不侵入原代碼。

再看看這種代理模式的缺點:

假如有這樣一個需求,有十個不同的RealObject,同時我們要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代碼可能是這樣的:

realObject.doSomething();realObject1.doAnotherThing();realObject2.doTwoAnother();

為瞭解決這個問題,我們有方案一:

為這些方法建立不同的代理類,代理後的代碼是這樣的:

proxy.doSomething();proxy1.doAnotherThing();proxy2.doTwoAnother();

當然,也有方案二:

通過建立一個proxy,持有不同的realObject,實現Action1、Action2、Action3介面,來讓代碼變成這樣:

proxy.doSomething();proxy.doAnotherThing();proxy.doTwoAnother();

於是你的代理模型會變成這樣:

毫無疑問,僅僅為了擴充同樣的功能,在方案一種,我們會重複建立多個邏輯相同,僅僅RealObject引用不同的Proxy。

而在方案二中,會導致proxy的膨脹,而且這種膨脹往往是無意義的。此外,假如方法簽名是相同的,更需要在調用的時候引入額外的判斷邏輯。

java 動態代理

搞清楚靜態代理的缺點十分重要,因為動態代理的目的就是為瞭解決靜態代理的缺點。通過使用動態代理,我們可以通過在運行時,動態產生一個持有RealObject、並實現代理介面的Proxy,同時注入我們相同的擴充邏輯。哪怕你要代理的RealObject是不同的對象,甚至代理不同的方法,都可以動過動態代理,來擴充功能。

簡單理解,動態代理就是我們上面提到的方案一,只不過這些proxy的建立都是自動的並且是在運行期產生的。 動態代理基本用法

使用動態代理,需要將要擴充的功能寫在一個InvocationHandler 實作類別裡:

public class DynamicProxyHandler implements InvocationHandler {    private Object realObject;    public DynamicProxyHandler(Object realObject) {        this.realObject = realObject;    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //代理擴充邏輯        System.out.println("proxy do");        return method.invoke(realObject, args);    }}

這個Handler中的invoke方法中實現了代理類要擴充的公用功能。

到這裡,需要先看一下這個handler的用法:

public static void main(String[] args) {        RealObject realObject = new RealObject();        Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));        proxy.doSomething();}

Proxy.newProxyInstance 傳入的是一個ClassLoader, 一個代理介面,和我們定義的handler,返回的是一個Proxy的執行個體。

仔細體會這個過程,其實有點類似我們在靜態代理中提到的方案一,產生了一個包含我們擴充功能,持有RealObject引用,實現Action介面的代理執行個體Proxy。只不過這個Proxy不是我們自己寫的,而是java幫我們產生的,有沒有一點動態味道。

讓我們再回顧一下代理三要素:真實對象:RealObject,代理介面:Action,代理執行個體:Proxy

上面的代碼實含義也就是,輸入 RealObject、Action,返回一個Proxy。妥妥的代理模式。

綜上,動態產生+代理模式,也就是動態代理。
網上搜了不少文章,到了這裡,接下來就是和cglib等動態代理實現方法做一下橫向比較。本文不做橫向比較,為了不偏離主題,接下來做縱向挖掘。 看一下源碼

道理清楚了,但是這篇文章題目是搞懂,所以來看一下這個Proxy是如何自動被產生的。入口就在newProxyInstance方法,核心代碼如下:

private static final Class<?>[] constructorParams =        { InvocationHandler.class };public static Object newProxyInstance(ClassLoader loader,                                          Class<?>[] interfaces,                                          InvocationHandler h)        throws IllegalArgumentException{    Class<?> cl = getProxyClass0(loader, intfs);    ...    final Constructor<?> cons = cl.getConstructor(constructorParams);    if (!Modifier.isPublic(cl.getModifiers())) {        AccessController.doPrivileged(new PrivilegedAction<Void>() {            public Void run() {            cons.setAccessible(true);            return null;        }        });    }return cons.newInstance(new Object[]{h});}

整體流程就是:

1、組建代理程式類Proxy的Class對象。

2、如果Class範圍為私人,通過 setAccessible 支援訪問

3、擷取Proxy Class建構函式,建立Proxy代理執行個體。 產生Proxy的Class檔案

產生Class對象的方法中,先是通過傳進來的ClassLoader參數和Class[] 數組作為組成鍵,維護了一個對於Proxy的Class對象的緩衝。這樣需要相同Proxy的Class對象時,只需要建立一次。

第一次建立該Class檔案時,為了安全執行緒,方法進行了大量的處理,最後會來到ProxyClassFactory的apply方法中,經過以下流程:

1、校正傳入的介面是否由傳入的ClassLoader載入的。

2、校正傳入是否是介面的Class對象。

3、校正是否傳入重複的介面。

4、拼裝代理類包名和類名,產生.class 檔案的位元組碼。

5、調用native方法,傳入位元組碼,產生Class對象。

proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                proxyName, interfaces, accessFlags);return defineClass0(loader, proxyName,                                    proxyClassFile, 0, proxyClassFile.length);

看一下第四步產生.class檔案位元組碼的過程,主要分為兩個階段:

addProxyMethod(hashCodeMethod, Object.class);addProxyMethod(equalsMethod, Object.class);addProxyMethod(toStringMethod, Object.class);for (int i = 0; i < interfaces.length; i++) {    Method[] methods = interfaces[i].getMethods();    for (int j = 0; j < methods.length; j++) {         addProxyMethod(methods[j], interfaces[i]);    }}methods.add(this.generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) {    for (ProxyMethod pm : sigmethods) {         fields.add(new FieldInfo(pm.methodFieldName,                                   "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));        methods.add(pm.generateMethod());    } }methods.add(generateStaticInitializer());

第一個階段的代碼比較清晰,主要就是添加各種Method,比如toString()、equals,以及傳入的代理介面中的方法。再添加一下構造方法以及靜態初始化方法。這要構成了一個對象,儲存產生Proxy的Class的一些資訊。

到了這裡,已經把要構造的Proxy的方法基本定義完成了,接下來就要產生這個.class檔案了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.