深入理解spring(動態代理源碼2)(六)

總結與回顧(接上篇)

1、上一篇講到我們自己實現動態代理。
1.1 自己模擬的動態代理

不需要手動創建類文件(因爲一旦手動創建類文件,就會產生類爆炸),通過接口反射生成一個類文件,然後調用第三方的編譯技術,動態編譯這個產生的類文件成class文件,繼而利用UrlclassLoader(因爲這個動態產生的class不在工程當中所以需要使用UrlclassLoader)把這個動態編譯的類加載到jvm當中,最後通過反射把這個類實例化。

那麼JDK實現的動態代理,又是怎樣實現的呢?

上一篇有一個問題,我們的邏輯都是打印語句。那我們的邏輯如何動態?怎麼代理任何邏輯?

帶着疑問我們來看一下 jdk的動態代理。如下圖是jdk代理源碼的核心方法,jdk爲什麼需要一個classloader?

在這裏插入圖片描述

jvm加載圖,如下。jvm先回加載所有預編譯好的class文件,但是動態產生的類怎麼辦呢?就需要我們傳入classloader 然後再load一遍到jvm中。

在這裏插入圖片描述

1.2jdk動態代理是怎麼回事?

如下代碼片段,我們要調用JDK動態代理。只需要傳入三個參數。類加載器,目標類實現的接口,與InvocationHandler對象。

 //第一個參數,傳入類加載器。怎麼證明一個類在項目中的唯一呢?很多人會說hashcode,
 //其實是根據類加載器來判斷。因爲一個類只會加載一次
        //第二個參數,傳入被代理類(目標對象的類)的接口數組。因爲類是可以多實現,所以是一個數據
        //第三個參數,傳入InvocationHandler對象,但是InvocationHandler是一個接口。
        //接口中只有一個方法,invoke(代理對象,目標對象,參數) 目標對象.invoke()調用反射傳入目標對象與方法實現動態代理
        UserDao dao = (UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(),
         new Class[]{UserDao.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("假裝增強,打印sql語句");
                return method.invoke(new UserDaoImpl(),args);
            }
        });
        dao.query();
        System.out.println("------------------華麗分分割線--------------------");
        System.out.println(dao.query("JDK動態代理。。。。"));
    }
-------------------------控制檯輸出---------------------------------------------
假裝增強,打印sql語句
假裝查詢數據庫
------------------華麗分分割線--------------------
假裝增強,打印sql語句
JDK動態代理。。。。

1.3如上代碼片段,只需要傳入三個參數就可以對代理對象實現動態的業務與增強?那麼jdk底層是怎麼做的呢?

·我們先猜想,來改進一下上一篇的ProxyUtile工具類。

改造後的山寨版動態代理類核心如下。
    public static Object newInstance(Class[] targetIn, CoustomInvocationHandler h){
        Object proxy=null;
        Class targetInf = targetIn[0];
        Method methods[] =targetInf.getMethods();
        //定義換行
        String line="\n";
        //定義tab空格
        String tab ="\t";
        String infName = targetInf.getSimpleName();
        String content ="";
        String packageContent = "package com.google;"+line;
        String importContent = "import "+targetInf.getName()+";"+line
                +"import com.bing.li.util.CoustomInvocationHandler;"+line
                +"import java.lang.*;"+line
                +"import java.lang.reflect.Method;"+line;
        String clazzFirstLineContent = "public class $Proxy implements "+infName+"{"+line;
        String filedContent  =tab+"private CoustomInvocationHandler h;"+line;
        String constructorContent =tab+"public $Proxy (CoustomInvocationHandler h){" +line
                +tab+tab+"this.h =h;"
                +line+tab+"}"+line;
        String methodContent = "";
        for (Method method : methods) {
            String returnTypeName = method.getReturnType().getSimpleName();
            String methodName =method.getName();
            String argsContent = "";
            String paramsContent="";
            int flag =0;
            methodContent+=tab+"public "+returnTypeName+" "+methodName+"(";
                //獲取方法的所有參數
                Class parameters[] = method.getParameterTypes();
                //參數字符串
                StringBuilder args = new StringBuilder();
                for (int i = 0; i < parameters.length; i++) {
                    //參數的類型,形參 String p0,Sting p1,
                    methodContent+=parameters[i].getSimpleName() + " p" +i;
                    args.append("p" +i);
                    if (i != parameters.length - 1) {
                        methodContent+=",";
                        args.append(",");
                    }
                }
                methodContent+="){"+line
                    +tab+tab+"try {"+line
                    +tab+tab+"Method method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\");"+line;
                     if (!returnTypeName.equals("void")) {
                       //  methodContent+=tab + tab +"Class[] args = new Class[]{" + args + "};"+line;
                         methodContent+= tab + tab + "return (" + returnTypeName + ")h.invoke(method, new Object[]{"+ args +"});" + line;
                     }else {
                         methodContent+= tab + tab + " h.invoke(method,new Object[]{"+paramsContent+"});" + line;
                     }
                     methodContent+= tab+tab+"}catch (Exception e){"+line+tab+tab+"}"+line;
                    if (!returnTypeName.equals("void")) {
                        methodContent+= tab+tab+tab+"return "+ null+ ";"+line;
                    }
                     methodContent+= tab+"}"+line;
                  }
        content=packageContent+importContent+clazzFirstLineContent+filedContent+constructorContent+methodContent+"}";
        File file1 =new File("G:\\com\\google");
        File file =new File("G:\\com\\google\\$Proxy.java");
        try {
            //判斷是否是一個目錄
            if (!file1.isDirectory()){
                //創建目錄
                file1.mkdirs();
            }
            if (!file.exists()) {
                //創建文件
                file.createNewFile();
            }
            //將字符串 寫出到磁盤
            FileWriter fw = new FileWriter(file);
            fw.write(content);
            fw.flush();
            fw.close();

            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
            Iterable units = fileMgr.getJavaFileObjects(file);
            JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
            t.call();
            fileMgr.close();
            //創建需要加載.class的地址
            URL[] urls = new URL[]{new URL("file:G:\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            //加載class對象
            Class clazz = urlClassLoader.loadClass("com.google.$Proxy");
            Constructor constructor = clazz.getConstructor(CoustomInvocationHandler.class);
            proxy = constructor.newInstance(h);
            //clazz.newInstance();
            //Class.forName()
        }catch (Exception e){
            e.printStackTrace();

        }
        return proxy;
    }
調用工具類,控制檯輸出如下,我們也能動態實現業務邏輯部分了。
    public static void main(final String[] args) {
        UserDao proxy = (UserDao) ProxyUti2.newInstance(new Class[]{UserDao.class}, new CoustomInvocationHandler() {
            @Override
            public Object invoke(Method method, Object[] arg){
                System.out.println("山寨版JDK動態代理增強打印sql");
                try {
                    Object invoke = method.invoke(new UserDaoImpl(), arg);
                    return invoke;
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
        proxy.query();
        ------------------------------控制檯輸出----------------------------
        山寨版JDK動態代理增強打印sql
		假裝查詢數據庫
我們自己實現的動態代理缺點有哪些呢?

缺點:首先要生成文件
缺點:動態編譯文件 class
編譯會產生IO操作,軟件性能的最終體現在IO操作。性能會降低。
缺點:需要一個URLclassloader

JDK 動態代理源碼分析:

如下是JDK 動態代理newProxyInstance方法 如下,最重要的一行代碼就是 Class<?> cl = getProxyClass0(loader, intfs); 通過這行代碼 其實就得到了代理類,然後通過代理類的構造方法,就實例化了一個代理對象。

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
		// 克隆一下我們傳進來的 interface
        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.
         */
        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;
                    }
                });
            }
            //通過構造方法 new出我們的要得代理對象
            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);
        }
    }
getProxyClass0(ClassLoader loader,Class<?>… interfaces) 方法。其實就一行代碼proxyClassCache.get(loader, interfaces);通過名字看出,緩存中拿到代理對象。那我們 繼續往下看,看get(loader, interfaces)方法它又做了什麼呢?
    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
get(loader, interfaces)方法如下。
 public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
        expungeStaleEntries();
		//JDK去 cache中拿緩存,如果緩存中存在。就不在生成,直接拿。
        Object cacheKey = CacheKey.valueOf(key, refQueue);
        // lazily install the 2nd level valuesMap for the particular cacheKey
        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;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                //通過這句代碼得到了我們的value
                V value = supplier.get();
                if (value != null) {
                //value就是我們要的這個代理類。
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }
以上源碼部分可以看出通過V value = supplier.get();這一行代碼得到了我們要的類。接着,我們繼續分析這一行代碼。到底做了什麼?通過斷點的方式,得到如下代碼。
  @Override
        public synchronized V get() { // serialize access
            // re-check
            //從jdkmap衆去獲取。。
            Supplier<V> supplier = valuesMap.get(subKey);
            if (supplier != this) {
                // something changed while we were waiting:
                // might be that we were replaced by a CacheValue
                // or were removed because of failure ->
                // return null to signal WeakCache.get() to retry
                // the loop
                return null;
            }
            // else still us (supplier == this)

            // create new value
            V value = null;
            try {
            //獲取到value 說白了,這個方法最後返回 回去的value取值就是這一行代碼。
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }
            // the only path to reach here is with non-null value
            assert value != null;

            // wrap value with CacheValue (WeakReference)
            CacheValue<V> cacheValue = new CacheValue<>(value);

            // try replacing us with CacheValue (this should always succeed)
            if (valuesMap.replace(subKey, this, cacheValue)) {
                // put also in reverseMap
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {
                throw new AssertionError("Should not reach here");
            }

            // successfully replaced us with new CacheValue -> return the value
            // wrapped by it
            //獲取到我們要的類文件
            return value;
        }
    }
通過如上代碼,得到value = Objects.requireNonNull(valueFactory.apply(key, parameter));value取值的這段代碼就是這一句。接口我們繼續分析,這一句代碼到底做了些什麼。這是一個類部類,外部類的名稱叫ProxyClassFactory 的apply方法。
    @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            //得到我們傳進來的接口然後循環
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
                -- 這裏就非常重要。這裏我們傳進來的已經是一個class,然後它用反射再load一次?爲什麼要再load一次呢?
                -- 這裏不是重複了一次嘛?我們傳進來本身就是一個class
                -- 其實JDK這裏做了一個相對高級的判斷,這裏重複的load一遍。是爲了判斷這兩個類是不是相等。
                -- 上一篇說到,兩個類相等有一個前提條件,同一個類加載器。所以它用傳進來的loader
                -- 再生了了一遍整個類。
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                --這裏對兩個類 做判斷、如果不相等。則拋異常。
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }
			--如果相等,則就往下面走。 這個就是代理類的包名。
            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
             --循環所有的interface 
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                -- 判斷接口是不是 public 如果不是公共的 則不能實現它。
                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) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            -- 跑完以上代碼,就得到了 包名+類名+一個類標識。
            -- 包名是JDK  內部這一段大嘛寫死的public static final String PROXY_PACKAGE = "com.sun.proxy";
            -- proxyClassNamePrefix  是固定的成員靜態變量$Proxy
            -- 然後再生成一個隨機數。防止多線程重複的情況。
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            -- 這一段很重要 ,這一句代碼直接生成一個代理類的class byte數組。
            -- 那麼byte怎麼變成這個方法所需要的class對象呢?
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
            	-- 通過這一段代碼就返回了。我們要的class類。
            	--  這裏調用的native方法,JDK底層的方法。就不在深入研究了。
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags) 講一下這個方法,這個其實是JDK 的一個靜態方法。。
調用這個方法,生成的Class文件如下,其實就跟我們自己上面寫的山寨版的動態代理文件差不多。只不過我們先生成.java文件,在調用URLclassload 生成.class字節碼。然後讀取到內存中,產生對象。這裏一步到位了。這裏就不做多講解。有興趣的可以自己去品。
    byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class});
        FileOutputStream fileOutputStream = new FileOutputStream("G:\\$Proxy18.class");
        fileOutputStream.write(bytes);
        fileOutputStream.flush();

在這裏插入圖片描述

項目源碼地址

總結:

JDK動態代理底層:
通過接口反射,得到字節碼(就是那個byte[])然後通過native defineClass0方法把字節碼轉成calss
JDK動態代碼基於接口實現反射,
Cglib 基於繼承
cglib 是基於asm框架實現。ASM框架是一個操作字節碼的一個框架。
JDK 與cglib 性能呢?
性能基本上可以忽略,看應用場景。沒有接口的情況下就用cglib

預告:下一篇,手寫IOC ,AOP 最近比較忙更新比較慢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章