有點深度的聊聊JDK動態代理

在接觸SpringAOP的時候,你們必定會被這神奇的功能所折服,想知道其中的奧祕,底層究竟是如何實現的。因而,你們會經過搜索引擎,知道了一個陌生的名詞:動態代理,慢慢的又知道了動態代理有多種實現方式,好比 JDK動態代理Cglib 等等。今天我就來簡單說說JDK動態代理java

JDK動態代理的簡單應用

咱們仍是從一個最簡單的例子着手:數組

首先咱們須要定義一個接口:緩存

public interface UserService {
    void query();
}

而後實現這個接口:安全

public class UserServiceImpl implements UserService {
    public void query() {
        System.out.println("查詢用戶信息");
    }
}

定義一個類,須要實現InvocationHandler:app

public class MyInvocationHandler implements InvocationHandler {

    Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進入了invoke");
        method.invoke(target);
        System.out.println("執行了invoke");
        return null;
    }
}

而後就是Main方法了:ide

public class Main {
    public static void main(String[] args) {
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(new UserServiceImpl());
        Object o = Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{UserService.class}
                , myInvocationHandler);

        ((UserService)o).query();
    }
}

運行:性能

image.png

能夠看到,一切正常,成功的執行了加強的邏輯,也執行了目標方法。測試

三個疑惑

雖說這是最簡單的一個例子了,可是在初學的時候,你們確定和我同樣,有很多疑惑:一是不知道爲何須要傳入接口,二是不知道爲何JDK動態代理只能代理接口,三是不知道類加載器的做用。還有,就是代碼比較複雜。ui

這三個疑惑困擾我好久,直到我跟着博客,本身手擼一個閹割版的JDK動態代理,而且簡單的看了下JDK最終生成的代碼以及源碼才明白。this

寫一個閹割版的JDK動態代理

咱們先來分析下MyInvocationHandler類中的invoke方法,方法有三個參數,第一個參數是代理類,第二個參數是方法,第三個參數是 執行方法須要用到的參數。方法內部實現了兩個邏輯,一個是加強邏輯 ,一個是執行目標方法。咱們不由的想,若是咱們能夠自動生成一個類,去調用MyInvocationHandler中的invoke方法是否是就能夠實現動態代理了。

人有多大膽,地有多大產,這的確是一個大膽瘋狂的想法,可是這確實能夠辦到,主要有以下幾個步驟:

  1. 拼接代理類的代碼
  2. 輸出.java文件
  3. 編譯.java文件成.class文件
  4. 裝載.class文件
  5. 建立並返回代理類對象

爲了方便,就不考慮返回值和帶參的狀況了,我仿照現有的MyInvocationHandler 寫了一個閹割版的MockInvocationHandler類:

public class MockInvocationHandler {

    private Object targetObject;

    public MockInvocationHandler(Object targetObject) {
        this.targetObject = targetObject;

    }

    public void invoke(Method targetMethod) {
        try {
            System.out.println("進入了invoke");
            targetMethod.invoke(targetObject, null);
            System.out.println("結束了invoke");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

要調用到MockInvocationHandler 中的invoke方法,生成的代理類大概可能也許長這個樣子:

public class $Proxy implements 須要代理的接口{
     MockInvocationHandler h;
     public $Proxy (MockInvocationHandler h ) {this.h = h; }
     public void query(){
      try{ 
        //method=須要的執行方法
         this.h.invoke(method);
        }catch(Exception ex){}
    }
}

好了,接下來就是體力活了,直接貼上代碼:

public class MockProxy {

    final static String ENTER = "\n";
    final static String TAB = "\t";

    public static Object newProxyInstance(Class interfaceClass,MockInvocationHandler h) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("package com.codebear;");
        stringBuilder.append(ENTER);
        stringBuilder.append("import java.lang.reflect.*;");
        stringBuilder.append(ENTER);
        stringBuilder.append("public class $Proxy implements " + interfaceClass.getName() + "{");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        stringBuilder.append(" MockInvocationHandler h;");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        stringBuilder.append(" public $Proxy (MockInvocationHandler h ) {this.h = h; }");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        for (Method method : interfaceClass.getMethods()) {
            stringBuilder.append(" public void " + method.getName() + "(){");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append("  try{ ");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append(" Method method = " + interfaceClass.getName() + ".class.getMethod(\"" + method.getName() + "\");");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append(" this.h.invoke(method);");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append("}catch(Exception ex){}");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append("}");
            stringBuilder.append(ENTER);
            stringBuilder.append("}");
        }
        String content = stringBuilder.toString();

        try {
            String filePath = "D:\\com\\codebear\\$Proxy.java";
            File file = new File(filePath);

            File fileParent = file.getParentFile();
            if (!fileParent.exists()) {
                fileParent.mkdirs();
            }

            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.flush();
            fileWriter.close();

            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = compiler.getStandardFileManager
                    (null, null, null);
            Iterable iterable = fileManager.getJavaFileObjects(filePath);
            JavaCompiler.CompilationTask task = compiler.getTask
                    (null, fileManager, null, null, null, iterable);
            task.call();
            fileManager.close();

            URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:D:\\\\")});
            Class<?> clazz = classLoader.loadClass("com.codebear.$Proxy");
            Constructor<?> constructor = clazz.getConstructor(MockInvocationHandler.class);
            return constructor.newInstance(h);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

而後測試一下:

public class Main {
    public static void main(String[] args) {
        MockInvocationHandler mockInvocationHandler=new MockInvocationHandler(new UserServiceImpl());
        UserService userService = (UserService)MockProxy.
                newProxyInstance(UserService.class, mockInvocationHandler);
        userService.query();
    }
}

運行結果:
image.png

好了,在不考慮性能,可維護性,安全性的狀況下,咱們閹割版的動態代理就完成了。代碼難度不是很大,就是比較考驗反射和耐心。

簡單分析下JDK源碼

源碼基於JDK1.8

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        //安全驗證
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 獲得代理類
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        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});//經過構造方法,建立對象,傳入InvocationHandler 對象
        } 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);
        }
    }

簡單的看下源碼,咱們一會兒就能把目光移動到getProxyClass0方法了,這纔是咱們須要關心的,咱們點進去:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //當接口大於65535報錯
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        return proxyClassCache.get(loader, interfaces);
    }

這方法能夠說什麼事情也沒幹,可是經過最後的proxyClassCache.get能夠很容易的知道JDK的動態代理是用了緩存的,咱們須要關注的方法在get裏面,繼續點進去:

public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();
        //經過上游方法,能夠知道key是類加載器,這裏是經過類加載器能夠得到第一層key
       Object cacheKey = CacheKey.valueOf(key, refQueue);
        
       //咱們查看map的定義,能夠看到map變量是一個兩層的ConcurrentMap
       ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//經過第一層key嘗試獲取數據
       //若是valuesMap 爲空,就新建一個ConcurrentHashMap,
       //key就是生成出來的cacheKey,並把這個新建的ConcurrentHashMap推到map
       if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        //經過上游方法能夠知道key是類加載器,parameter是類自己,這裏是經過類加載器和類自己得到第二層key
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                //若是有緩存,直接調用get方法後返回,當沒有緩存,會繼續執行後面的代碼,
                //因爲while (true),會第二次跑到這裏,再get返回出去,
                //其中get方法調用的是WeakCahce中的靜態內部類Factory的get方法
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            //當factory爲空,會建立Factory對象
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    //當沒有代理類緩存的時候,會運行到這裏,把Factory的對象賦值給supplier ,
                    //進行下一次循環,supplier就不爲空了,能夠調用get方法返回出去了,
                    //這個Factory位於WeakCahce類中,是一個靜態內部類
                    supplier = factory;
                }
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else {
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

這裏面的代碼比較複雜,簡單的來講:

  • JDK動態代理是用了兩層的map去緩存,第一個層是類加載器,第二層是 類加載器+自己
  • 當有緩存,直接調用get而且返回,反之繼續執行下面的代碼,爲supplier進行賦值,因爲while (true),會第二次跑到這裏,再調用get()返回出去。核心在於supplier.get(),它調用的是WeakCahce中的靜態內部類Factory的get(),裏面就是 獲取代理類的方法了。

讓咱們看下supplier.get()方法:

value = Objects.requireNonNull(valueFactory.apply(key, parameter));

核心在於這一句話,可是valueFactory是什麼?咱們能夠查看它的定義:

private final BiFunction<K, P, V> valueFactory;

咱們再看下它的WeakCahce構造方法:

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

咱們確定在哪邊調用過這個構造方法了,在Proxy類中有這樣的定義:

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

這個proxyClassCache有沒有很熟悉, 是的,它就在getProxyClass0方法中用到了,這裏建立了WeakCache對象,而且調用了帶兩個參數的構造方法,第二個參數是ProxyClassFactory對象,也就對應了WeakCache中第二個參數BiFunction<K, P, V> valueFactory,而後把值賦值給了final valueFactory,valueFactory.apply因此最終會調用ProxyClassFactory中的apply方法。關鍵在於:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);//生成代理類的二進制數組
            try {
                 //內部是native標記的方法,是用C或者C++實現的,這裏不深究
                //方法內部就是經過類加載器和上面生成的代理類的二進制數組等數據,通過處理,成爲Class
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }

generateProxyClass方法內部生成了代理類的二進制數組,具體是怎麼生成的,你們能夠點進去本身看看,這裏就再也不繼續往下了,由於咱們的目標就是找到generateProxyClass方法,而後本身寫一個方法,去執行generateProxyClass,把返回的byte[]輸出到.class文件,利用idea的反編譯功能,看看最終生成出來的代理類是什麼樣子的:

byte[] $proxies = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserService.class});
        File file=new File("D:\\$Proxy.class");
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            try {
                outputStream.write($proxies);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

運行,發現D盤出現了$Proxy.class文件,咱們把它拖到idea裏面,看看它的真面目,由於生成的代碼仍是比較長的,我這裏只把核心代碼貼出來:

//繼承了Proxy類
public final class $Proxy extends Proxy implements UserService {
    public $Proxy(InvocationHandler var1) throws  {
        super(var1);
    }
    public final void query() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

這代碼有沒有很熟悉,很接近咱們本身手寫動態代理生成的代理類。

解開疑惑

好了,先是本身手寫了一個閹割版的動態代理,而後簡單的看了下JDK動態代理源碼,也看了下JDK動態代理生成的代理類。這樣,就能夠解開上面的三個疑惑了:

  1. 類加載器是幹嗎的:其一:JDK內部須要經過類加載做爲緩存的key 其二:須要類加載器生成class
  2. 爲何須要接口:由於生成的代理類須要實現這個接口
  3. 爲何JDK動態代理只能代理接口:由於生成的代理類已經繼承了Proxy類,Java是單繼承的,因此無法再繼承另一個類了。

有一些博客上可能會說cglib和JDK動態代理的區別,cglib是經過操做字節碼去完成代理的,其實JDK動態代理也操做了字節碼

通過這麼一分析,相信你們對JDK動態代理有了一個新的認識。

相關文章
相關標籤/搜索