用大白話講Java動態代理的原理


動態代理是什么

首先說下代理模式,代理模式是常見的一種java設計模式,特征是代理類委托類實現了同樣的接口,代理類主要負責為委托類預處理、過濾、轉發,以及事后處理等。代理類與委托類之間通常會存在關聯關系,一個代理類的實例與它的委托類的實例是關聯的。代理類的實例本身是並不真正關心被調用方法的內部邏輯,而是會通過內部訪問調用 委托類的實例真正實現了的方法,來為調用者提供服務。

有代理的話,在訪問實際對象時,是通過代理實例來訪問、調用委托類方法的,代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。

動態代理對比靜態代理,最大的特點是代理類是在程序運行時生成的,並非在編譯期生成,能做的事情也多了,自然風險也高了。

動態代理最簡單的用法

用一個比較接近生活的例子:中午,餓了的室友 委托 持家有道的你 去點外賣

Hungry.java :接口

public interface Hungry {
     void callLunch();
}

Roommate.java :Hungry接口的實現類,也就是委托類

public class Roommate implements Hungry{
    private String name;
    public Roommate(String name) {
        this.name = name;
    }

    @Override
    public void callLunch() {
        System.out.println("好餓,今天午飯點外賣吧");
    }
}

public class RoommateInvocationHandler<T> implements InvocationHandler {

    private T rommate;

    public RoommateInvocationHandler(T roommate){
        this.rommate = roommate;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("下單前,我先幫你看下有沒有平台優惠券吧");
        Object result = method.invoke(rommate , args);
        return result;
    }
}

InvocationHandler是一個接口,由代理實例內部的invocation handler實現的接口。每個代理實例都有一個關聯的invocation handler。當代理實例上調用方法時,method.invoke(baseImpl, args),此方法將被編碼並織入到代理實例內部的 invocation handler實現的invoke方法中。

利用 Proxy 的方式實現動態代理,調用 委托類接口 的方法,完成午餐點外賣這個操作

public static void main(String[] args) {
	    Roommate roommate = new Roommate("zhangsan");
        Hungry proxyInstance = (Hungry) Proxy.newProxyInstance(
                roommate.getClass().getClassLoader(),
                roommate.getClass().getInterfaces(),
                new RoommateInvocationHandler<Roommate>(roommate)
        );
        proxyInstance.callLunch();
}
//輸出結果
下單前,我先幫你看下有沒有平台優惠券吧
好餓,今天午飯點外賣吧

代理實例proxyInstance的類型是Hungry,所以只能調用Hungry里規定的方法。Roommate作為接口實現類,不是來自接口的其他的方法,是無法通過動態代理調用的。

可以看到代理實例在調用委托類實現的方法時,可以很方便地在調用方法的前后執行一些操作,在示例代碼中則是在調用方法前簡單輸出了一行: System.out.println("下單前,我先幫你看下有沒有平台優惠券吧"),還可以有其他用途,例如記錄這個方法的耗時時間,對方法的參數或者返回結果進行修改等等。這也是Spring,Dagger進行AOP編程的原理。

那為什么繼承InvocationHandler接口和持有委托類引用的RoommateInvocationHandler調用來自Hungry接口的callLunch()方法時可以調用到委托類對callLunch()的邏輯實現呢,看看它的背后原理:

動態代理的實現原理

Proxy.newProxyInstance() 入手,逐步分析 InvocationHandler 如何建立代理實例和委托實例的關聯:

public static Object newProxyInstance(ClassLoader loader ,  Class<?>[] interfaces,
                               InvocationHandler h) throws IllegalArgumentException {
    	//InvocationHandler必須非空,說明是個重要角色
        Objects.requireNonNull(h);
		//獲取委托類的接口
        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<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         * 利用指定的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) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

來到這一步好像就停下了,那么接下來探究 cl 這個實例創建過程發生了什么:

在上面示例代碼main函數的后面接着補充。利用ProxyGenerator.generateProxyClass生成這個動態生成的類文件,寫入了指定路徑的class文件內

$Proxy0 是 代理類在系統內部的編號,在示例代碼只生成了一個代理類所以編號是 $Proxy0

        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Roommate.class.getInterfaces());
        String filePath = "C:\\Users\\ODM\\Desktop\\RoommateProxy.class";
        try(FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(classFile);
            fos.flush();
        }catch (IOException e){
            e.printStackTrace();
            System.out.println("error:寫入文件");
        }

使用反編譯工具,我這里用的是jd-gui反編譯,這個$Proxy0類,實現了Proxy類,繼承了和委托類相同的接口

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy_test.Hungry;

public final class $Proxy0 extends Proxy implements Hungry{
  private static Method m1;
  private static Method m3; //由下方靜態代碼塊得知,m3代表callLunch()這一個方法
  private static Method m2;
  private static Method m0;
  
  /*
  * 父類Proxy的構造器,其中 h 屬性為 InvocationHandler引用
  * protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
  */
  public $Proxy0(InvocationHandler paramInvocationHandler) throws {
    super(paramInvocationHandler);
  }
 //關鍵!可供外界調用,方法名與委托類實現接口的方法相同,利用 InvocationHandler調用invoke
  public final void callLunch() throws {
    try{
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError){
      throw localError;
    }
    catch (Throwable localThrowable){
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
    
  public final boolean equals(Object paramObject) throws {}
  public final String toString() throws {...}
  public final int hashCode() throws {...}
  
  static{
    try{
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("proxy_test.Hungry").getMethod("callLunch", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException){
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException){
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

事情逐漸明朗起來,從這個動態類的源碼,可以分析出: $Proxy0 ,在構建這個類時,會調用了父類Proxy的構造方法,將InvocationHandler引用傳遞給了父類Proxy的 h 屬性,於是當我們在外界使用 代理實例 調用了 callLunch() 這個方法時,就會來到這一句 this.h.invoke(this, m3, null); 由於h屬性其實是InvocationHandler引用,調用了它的invoke,也就導致了上面示例代碼中的RoommateInvocationHandler類的重寫過的invoke方法也就被調用了,RoommateInvocationHandler也持有委托類的引用,所以委托類的方法也被調用起來了。

Java的繼承機制是單繼承,多接口。代理類因為必須要繼承Proxy類,所以java的動態代理只能對接口進行代理,無法對一個class類進行動態代理。

動態代理原理總結

用大白話的方式講:

有一個類InvocationHandler,它的性質類似一個中介,中介類構建時持有了委托對象,所以可以在它的invoke方法中調用了委托對象實現接口的具體方法。當外部調用這個InvocationHandler的invoke方法時,對 invoke 的調用最終都轉為對委托對象的方法調用。

創建明面上負責代理的代理實例時,在內存中動態生成的類不但繼承了Proxy,也實現了與委托對象相同的接口,因此代理實例可以調用此接口的方法,然后通過持有的中介類對象來調用中介類對象的invoke方法,最終達到代理實例執行了委托者的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM