JDK動態代理詳解

Liusy01發表於2020-09-27

JDK動態代理是代理模式的一種,且只能代理介面。spring也有動態代理,稱為CGLib,現在主要來看一下JDK動態代理是如何實現的?

一、介紹

JDK動態代理是有JDK提供的工具類Proxy實現的,動態代理類是在執行時生成指定介面的代理類,每個代理例項(實現需要代理的介面)都有一個關聯的呼叫處理程式物件,此物件實現了InvocationHandler,最終的業務邏輯是在InvocationHandler實現類的invoke方法上。

也即是在invoke方法上可以實現原方法中沒有的業務邏輯,相當於spring aop的@Before、@After等註解。

二、樣例

(1)介面

public interface ProxySource {
    void test();
}

 

(2)實現類

public class ProxySourceImpl implements ProxySource {
    @Override
    public void test() {
        System.out.println("原有業務邏輯");
    }
}

 

(3)實現InvocationHandler介面和invoke方法

static class MyHandler implements InvocationHandler {
    //需要代理的類
    Object target;
    public MyHandler(Object target) {
        this.target = target;
    }
     /**
     * @param proxy 動態代理例項
     * @param method 需要執行的方法
     * @param args 方法中引數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("do other things befor");
        method.invoke(target, args);
        System.out.println("do other things after");
        return null;
    }
}

 

(4)利用Proxy實現代理類

//此引數設定是為了儲存生成代理類的位元組碼檔案 
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
ProxySource proxySource = (ProxySource) Proxy.newProxyInstance(
        ProxySourceImpl.class.getClassLoader(),
        ProxySourceImpl.class.getInterfaces(),
        new MyHandler(new ProxySourceImpl())
);
//執行方法 
proxySource.test();

 

(5)方法呼叫結果

JDK動態代理詳解

 

可以看到,在原有方法執行前後都執行了其他程式碼。

三、原始碼分析(主要看Proxy.newProxyInstance方法,省略非核心程式碼)

(1)newProxyInstance方法

Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
{      
        final Class<?>[] intfs = interfaces.clone();
        //獲取代理介面class
        Class<?> cl = getProxyClass0(loader, intfs);
        //獲取到class之後用反射獲取構造方法,然後建立代理類例項   
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            return cons.newInstance(new Object[]{h});
        } catch (Exception e) {
            throw new InternalError(e.toString(), e);
        } 
    }

 

由上述程式碼可知,主要是通過getProxyClass0方法獲取到代理介面的class

(2)getProxyClass0方法

Class<?> getProxyClass0(ClassLoader loader,
                        Class<?>... interfaces) {
        //如果已經有相應的位元組碼檔案,則之間返回,否則通過代理類工廠建立代理類
        return proxyClassCache.get(loader, interfaces);
    }

 

而proxyClassCache又是什麼東東呢?

WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

 

proxyClassCache是一個WeakCache物件,可知這是一個快取物件,這個類結構是通過ConcurrentHashMap實現的,

ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
 = new ConcurrentHashMap<>();

 

資料結構是(key,sub-key)->value

存的值也就是<ClassLoader,<interfaces,$Proxy.class>>

(3)get方法

JDK動態代理詳解

 

public V get(K key, P parameter) {
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    //根據classloader為key檢視快取中是否已有  
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }

    //獲取到weakcache種的sub-key 
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    //根據sub-key去當前類載入器下是否有該代理介面的位元組碼    
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;

    while (true) {
        if (supplier != null) {
            //supplier是代理類工廠例項  
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        //建立代理類工廠 
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        //將上述建立的代理類工廠直接賦值給supplier  
        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                supplier = factory;
            }
        } else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
            } else {
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

 

JDK動態代理詳解

 

這個supplier.get方法點進去,核心就是ProxyClassFactory的apply方法

JDK動態代理詳解

 

(4)ProxyClassFactory的apply方法

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


      for (Class<?> intf : interfaces) {          
          Class<?> interfaceClass = null;
          try {
              //通過類許可權定名反射獲取class
              interfaceClass = Class.forName(intf.getName(), false, loader);
          } catch (ClassNotFoundException e) {
          }
          //判斷是否可以通過系統載入器載入
          if (interfaceClass != intf) {
              throw new IllegalArgumentException(
                  intf + " is not visible from class loader");
          }
          //校驗是否是介面
          if (!interfaceClass.isInterface()) {
              throw new IllegalArgumentException(
                  interfaceClass.getName() + " is not an interface");
          }
      }

      long num = nextUniqueNumber.getAndIncrement();
      //生成代理類的許可權定名,例如com.sun.proxy.$Proxy0
      String proxyName = proxyPkg + proxyClassNamePrefix + num;

      //生成位元組碼檔案
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
          proxyName, interfaces, accessFlags);
      try {
          //呼叫本地方法生成class  
          return defineClass0(loader, proxyName,
                              proxyClassFile, 0, proxyClassFile.length);
      } catch (ClassFormatError e) {
          throw new IllegalArgumentException(e.toString());
      }
  }

 

上述程式碼中核心是生成位元組碼,即是ProxyGenerator.generateProxyClass方法。

(5)ProxyGenerator.generateProxyClass方法。

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        //生成位元組碼
        final byte[] var4 = var3.generateClassFile();
        //開頭的設定 
        if (saveGeneratedFiles) {
            //儲存生成代理類的位元組碼檔案  
        }
        return var4;
    }

 

上述程式碼中saveGeneratedFiles點進去是這樣的,也即是開頭樣例中的設定屬性。

private static final boolean saveGeneratedFiles = 
((Boolean)AccessController
.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

 

但主要還是ProxyGenerator的generateClassFile這個方法。

JDK動態代理詳解

 

JDK動態代理詳解

 

其預設給代理類生成了hashcode、equals和toString方法,也限制了代理介面和欄位都不能超過65535個。

現在來看一下儲存的代理類位元組碼檔案是怎麼樣的(通過idea反編譯後)

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

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        //程式碼省略
    }

    public final void test() throws  {
      //h是invocationhandler,所以最後是執行invoke方法 
       super.h.invoke(this, m3, (Object[])null);
    }

    public final String toString() throws  {
        //程式碼省略
    }

    public final int hashCode() throws  {
        //程式碼省略
    }

    static {
          m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
          m3 = Class.forName("com.liusy.lang.ProxySource").getMethod("test");
          m2 = Class.forName("java.lang.Object").getMethod("toString");
          m0 = Class.forName("java.lang.Object").getMethod("hashCode");
      }
}

 

 

可以看到,生成的$Proxy0繼承了Proxy,實現了我定義的介面ProxySource,裡面有四個方法,m0~m3,通過靜態程式碼塊中根據類的全限定名和方法名反射獲取,而最後是執行InvocationHandler的invoke方法。

至此,JDK動態代理已經說完,希望對你有所幫助。

=======================================================

我是Liusy,一個喜歡健身的程式設計師。

歡迎關注微信公眾號【Liusy01】,一起交流Java技術及健身,獲取更多幹貨。

JDK動態代理詳解

相關文章