1. 程式人生 > >Java原始碼剖析——動態代理的實現原理

Java原始碼剖析——動態代理的實現原理

在本篇部落格中,博主將和大家一起深入分析Jdk自帶的動態代理的實現原理。如果有同學對代理模式靜態代理動態代理這些概念比較模糊,請先閱讀博主的另一篇文章《一步一步學設計模式——代理模式》

為了方便講解,我們繼續使用代理模式中的購票例子,下面是這個例子的主要程式碼:

  • 首先我們先建立一個介面:
package com.wxueyuan.DesignPettern.StaticProxy;

public interface Operation {
    void buyTicket(Ticket t);
}
  • 接著我們建立一個學生類並實現上面的介面,表示學生需要購票:
package
com.wxueyuan.DesignPettern.StaticProxy; public class Student implements Operation{ @Override public void buyTicket(Ticket t) { // TODO Auto-generated method stub System.out.println("學生買到一張票,票價為"+t.getPrice()); } }
  • 然後我們建立一個TicketOperationInvocationHandler實現InvocationHandler介面:
package com.wxueyuan.DesignPettern.DynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TicketOperationInvocationHandler implements InvocationHandler {

    //將需要代理的委託物件傳入Handler中
    private Object target; 

    public
TicketOperationInvocationHandler(Object target) { this.target = target; } //獲得幫助購票者買票的代理 public Object getProxy() { return Proxy.newProxyInstance(Thread.currentThread() .getContextClassLoader(), target.getClass().getInterfaces(), this); } //實際上黃牛執行的購票操作 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("黃牛收取購票者的錢"); System.out.println("黃牛連夜排隊"); Object ret = method.invoke(target, args); System.out.println("黃牛將票交給購票者"); return ret; } }
  • 最後是測試類
package com.wxueyuan.DesignPettern.DynamicProxy;

import com.wxueyuan.DesignPettern.StaticProxy.Operation;
import com.wxueyuan.DesignPettern.StaticProxy.Student;
import com.wxueyuan.DesignPettern.StaticProxy.Ticket;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        //學生需要購買的ticket例項
        Ticket studentTicket = new Ticket(200);

        //建立為學生買票的代理
        Operation studentProxy = (Operation) new TicketOperationInvocationHandler(new Student()).getProxy();
        studentProxy.buyTicket(studentTicket);

    }

}

執行結果為:
黃牛收取購票者的錢
黃牛連夜排隊
購票者買到一張票,票價為200.0
黃牛將票交給學生

下面我們就一步一步地分析這個簡單的動態代理例子的原理:

首先我們先看我們是如何獲得學生的代理的:

Operation studentProxy = (Operation) new TicketOperationInvocationHandler(new Student()).getProxy();

其中的關鍵就在於我們自定義的InvocationHandler中的getProxy()方法,現在我們就進入這個方法看一下:

public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread()  
                .getContextClassLoader(), target.getClass().getInterfaces(),  
                this);  
    }

這個方法的核心就是使用Proxy類的靜態方法newProxyInstance(),我們具體看一下這個方法究竟在幹什麼,我們以Jdk1.8的原始碼為例,首先來看一下這個方法的註釋:

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to

這個方法用來返回一個實現了一個或多個指定介面的代理類的例項,這個代理類能夠將其實現的方法的呼叫傳遞給指定的方法呼叫處理器。
引數:
     loader:載入這個代理類的類載入器
     interfaces:這個代理類實現的所有介面
     h:將方法呼叫傳至的呼叫處理器

newProxyInstance()這個方法生成例項的核心程式碼有以下幾句:

    //獲得代理類的class類
    Class<?> cl = getProxyClass0(loader, intfs);
    ...
    //獲取代理類的建構函式
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    ...
    //根據建構函式生成一個代理類的例項,至於為什麼代理類的構造方法中的引數是方法引數h,稍後我們就會知道了
    return cons.newInstance(new Object[]{h});

從newProxyInstance()方法中的三行核心程式碼可以看出,如何獲取代理類的class類是重中之重,因為獲取class類之後,我們就可以利用Java反射獲取建構函式並生成例項了。由於反射並不是本篇部落格的主題,我們現在就來著重關注一下getProxyClass0()方法是如何工作的:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        //如果代理類的class在快取中存在,則直接獲取,否則的話,通過ProxyClassFactory來建立
        return proxyClassCache.get(loader, interfaces);
    }

這裡的proxyClassCache是在Proxy類中宣告的WeakCache的例項,那我們就一起來看看這個WeakCache是什麼:

/**
 * Cache mapping pairs of (key, sub-key) -> value. 
 * Keys and values are weakly but sub-keys are strongly referenced. 
 * Keys are passed directly to get method which also takes a parameter. 
 * Sub-keys are calculated from keys and parameters using the subKeyFactory function
 * passed to the constructor. 
 * Values are calculated from keys and parameters using the valueFactory function passed
 * to the constructor.
*/

//這個WeakCache能夠將一組(key,sub-key)的值對映成value的值。其中Key的值是直接通過引數傳入的。
//sub-key的值是通過構造方法中的subKeyFactory生成的,value的值是通過構造方法中的valueFactory生成的。
//它的構造方法是:
final class WeakCache<K,P,V> {
  ...

  public WeakCache(BiFunction<K, P, ?> subKeyFactory,BiFunction<K, P, V> valueFactory) {
    this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
    this.valueFactory = Objects.requireNonNull(valueFactory);
  }

  ...
}

知道了WeakCache的構造方法之後,我們一起來看一下我們在getProxyClass0方法中使用到的WeakCache的get方法,它的核心程式碼如下:

public V get(K key, P parameter) {
  //通過subKeyFactory的apply方法生成subKey
  Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
  ...
  //通過subKey從valuesMap中取出可能存在的快取提供者supplier
  Supplier<V> supplier = valuesMap.get(subKey);
  ...
  //如果供應者不為空,就呼叫supplier.get()方法,get方法的返回值就是我們這個方法的返回值
  if (supplier != null) {
    // 這裡的供應者有可能是一個factory或者是一個快取例項
    V value = supplier.get();
    if (value != null) {
        return value;
    }
  }
  ...
  //如果factory沒有成功建立,我們此時建立一個factory
  if (factory == null) {
    factory = new Factory(key, parameter, subKey, valuesMap);
  }
  ...
}

看到這大家也許會問,上面程式碼中factory成功建立之後,如果supplier就是factory,該如何通過factory的get()方法來返回我們需要的value值,這個value值又是怎麼計算得到的呢?原來Factory這個類也實現了Supplier這個函式式介面,因此它也實現了自己的get方法:

private final class Factory implements Supplier<V> {
   @Override
        public synchronized V get() {
            ...
        V value = null;
            try {
        //通過我們的valueFactory的apply方法生成value
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            }
        ...
        return  value;
     }
}

知道了WeakCache中get()方法的實現後,我們看一下在Proxy類中,是如何定義這個WeakCache型別的proxyClassCache的呢?

//根據WeakCache的建構函式可知,KeyFactory就是在get方法中生成sub-key的subKeyFactory;
//ProxyClassFactory就是get方法中生成value的valueFactory
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

看到這裡讀者們應該知道WeakCache中的get方法是怎麼工作的了吧?它利用subKeyFactory(在Proxy類中就是KeyFactory)來生成subKey,再利用valueFactory(在Proxy類中就是ProxyClassFactory)的apply方法生成並返回value值。那麼我們一起來看一下,KeyFactory是如何生成subKey的:

//其實很簡單就是根據引數intefaces的數量,來生成不同的subKey物件
private static final class KeyFactory
        implements BiFunction<ClassLoader, Class<?>[], Object>
    {
        @Override
        public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
            switch (interfaces.length) {
                case 1: return new Key1(interfaces[0]); // 代理類只實現了一個介面
                case 2: return new Key2(interfaces[0], interfaces[1]);//代理類實現了兩個介面
                case 0: return key0;//代理類沒有實現介面
                default: return new KeyX(interfaces);//代理類實現了三個及以上的介面
            }
        }
    }

然後是ProxyClassFactory是如何生成value,也就是我們這裡需要的代理類的class類的:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
  //代理類名字的字首為$Proxy
  private static final String proxyClassNamePrefix = "$Proxy";
  //為了生成唯一的代理類名的計數器
  private static final AtomicLong nextUniqueNumber = new AtomicLong();

  @Override
  public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

    ...

    String proxyPkg = null;     // 代理類的包名
    //對於非公共介面,代理類的包名與介面的包名相同
    for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

  }

  if (proxyPkg == null) {
    // 如果沒有非公共的介面,就使用com.sun.proxy作為包名
    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
  }
  //預設生成的公共代理類的全限定名為com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1,以此數字遞增
  long num = nextUniqueNumber.getAndIncrement();
  String proxyName = proxyPkg + proxyClassNamePrefix + num;

  //生成代理類的位元組碼
  byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
      proxyName, interfaces, accessFlags);

  try {  
        //根據上面產生的位元組碼產生Class例項並返回,至此我們終於獲得了代理類的Class例項
      return defineClass0(loader, proxyName,  
            proxyClassFile, 0, proxyClassFile.length);  
  } catch (ClassFormatError e) {  
            throw new IllegalArgumentException(e.toString());  
  } 
}

ProxyGenerator.generateProxyClass這個方法的原始碼並沒有公開,我們可以反編譯class檔案,然後簡單看一下:

 public static byte[] generateProxyClass(final String var0, Class[] var1) {  
    ProxyGenerator var2 = new ProxyGenerator(var0, var1);  
    final byte[] var3 = var2.generateClassFile();  
    // 這裡根據引數配置,決定是否把生成的位元組碼(.class檔案)儲存到本地磁碟,預設是不儲存的
    if(saveGeneratedFiles) {  
        AccessController.doPrivileged(new PrivilegedAction() {  
            public Void run() {  
                try {  
                    FileOutputStream var1 = new FileOutputStream(ProxyGenerator.dotToSlash(var0) + ".class");  
                    var1.write(var3);  
                    var1.close();  
                    return null;  
                } catch (IOException var2) {  
                    throw new InternalError("I/O exception saving generated file: " + var2);  
                }  
            }  
        });  
    }  
    return var3;  
}  

我們可以通過設定sun.misc.ProxyGenerator.saveGeneratedFiles這個boolean值的屬性,來使方法預設將class檔案儲存到磁碟。那麼我們就來修改一下我們的Test程式碼,來將生成的proxy檔案儲存到磁碟上:

public static void main(String[] args) {
        // TODO Auto-generated method stub

    //學生需要購買的ticket例項
    Ticket studentTicket = new Ticket(200);

    //將是否在系統屬性修改為true,使位元組檔案儲存到磁碟上
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
        //顯示生成的代理類的全限定名    
    System.out.println(Proxy.getProxyClass(Operation.class.getClassLoader(), Operation.class)); 
    //建立為學生買票的黃牛代理
    Operation studentProxy = (Operation) new TicketOperationInvocationHandler(new Student()).getProxy();
    studentProxy.buyTicket(studentTicket);

    }

執行結果為:
class com.sun.proxy.$Proxy0
黃牛收取購票者的錢
黃牛連夜排隊
學生買到一張票,票價為200.0
黃牛將票交給購票者

同時,在com/sun/proxy下生成了$Proxy0.class,(注:如果我們的介面類不是public的,那麼我們生成的Proxy0代理類的包名會和介面類的包名相同哦)。下面我們將這個位元組碼反編譯看一下:

package com.sun.proxy;

import com.wxueyuan.DesignPettern.StaticProxy.Ticket;
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import com.wxueyuan.DesignPettern.StaticProxy.Operation;
import java.lang.reflect.Proxy;

public final class $Proxy0 extends Proxy implements Operation
{
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy0.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    public final void buyTicket(final Ticket ticket) {
        try {
            super.h.invoke(this, $Proxy0.m3, new Object[] { ticket });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy0.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }

    static {
        try {
            $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
            $Proxy0.m3 = Class.forName("com.wxueyuan.DesignPettern.StaticProxy.Operation").getMethod("buyTicket", Class.forName("com.wxueyuan.DesignPettern.StaticProxy.Ticket"));
            $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
        }
        catch (NoSuchMethodException ex) {
            throw new NoSuchMethodError(ex.getMessage());
        }
        catch (ClassNotFoundException ex2) {
            throw new NoClassDefFoundError(ex2.getMessage());
        }
    }
}

還記得我們之前的return cons.newInstance(new Object[]{h});這句程式碼麼,因為生成的Proxy類的建構函式的引數就是就是InvocationHandler,因此我們將newProxyInstance()中的引數h傳遞過來,用來呼叫它的invoke()方法。

分析了這麼多的原始碼,我們來總結一下Java動態代理的流程吧:
1. Proxy.newProxyInstance()方法返回一個代理類的例項,需要傳入InvocationHandler的例項h
2. 當新的代理例項呼叫指定方法時,本質上是InvocationHandler例項呼叫invoke方法,並傳入指定的method型別的引數。

根據我們生成$Proxy0代理類,我們能夠總結出:
1. 所有生成的代理類都繼承了Proxy類,實現了需要代理的介面。正是由於java不能多繼承,所以JDK的動態代理不支援對實現類的代理,只支援介面的代理。
2. 提供了一個使用InvocationHandler作為引數的構造方法。這個引數是由Proxy.newProxyInstance()方法的引數傳入的。當代理類例項呼叫某個方法時,本質上是InvocationHandler例項以該方法的method型別作為引數呼叫invoke方法。

Java的動態代理其實有很多應用場景,比如Spring的AOP或者是最近很火的RPC框架,裡面都涉及到了動態代理的知識,因此從原理上分析一下動態代理的原始碼還是很有幫助的,那麼這次的原始碼分析就到這裡了,我們下次再見~。