1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java 動態(tài)代理的底層原理

        共 25694字,需瀏覽 52分鐘

         ·

        2022-07-11 23:30

        1. 代理模式

        代理模式是常用的設計模式之一,其特征是代理類與被代理類有相同的接口,代理類可以為被代理類方法執(zhí)行進行前置后置處理,增強被代理類方法

        代理模式的類結構通常如上圖所示,代理類與被代理類之間會存在關聯關系,一個代理類的對象持有一個被代理類的對象。代理類的對象本身并不真正實現服務,而是通過調用被代理類對象的相關方法來提供特定的服務

        2. 動態(tài)代理使用

        代理類并不是在 Java 代碼中定義,而是在運行時根據在 Java 代碼中的“指示”動態(tài)生成(字節(jié)碼由JVM在運行時動態(tài)生成而非預存在任何一個 .class 文件中), 這種在程序運行時創(chuàng)建代理類的代理方式被稱為動態(tài)代理,它的優(yōu)勢在于可以方便地對代理類的函數進行統(tǒng)一處理。

        這是因為所有被代理執(zhí)行的方法,都是通過InvocationHandler#invoke()方法調用,相當于給被代理類所有方法套了一層殼,所以只要在這個方法中統(tǒng)一處理,就可以對所有被代理的方法進行相同的操作了

        以下代碼展示了動態(tài)代理的簡單使用,其基本步驟如下:

        • 定義一個公共接口,本例中為 IHello,接口中有一個抽象方法
        • 定義一個實現了公共接口的實體類作為被代理類,本例中被代理類 Hello實現了 IHello接口,重寫了接口中的抽象方法
        • 定義一個實現了 InvocationHandler 接口的方法攔截類,重寫 invoke() 方法實現攔截到被代理類方法執(zhí)行時候的處理邏輯
        • 通過 Proxy.newProxyInstance() 方法生成代理對象,持有代理對象之后執(zhí)行接口方法即可
        public class ServiceProxy {

            public interface IHello {
                String sayHi();
            }

            public static class Hello implements IHello {
                @Override
                public String sayHi() {
                    return "Hello";
                }
            }

            // 動態(tài)代理類
            public static class ProxyHandler<Timplements InvocationHandler {
                private T origin;

                public ProxyHandler(T origin) {
                    this.origin = origin;
                }

               /**
                 * @param o 代理對象引用
                 * @param method 正在執(zhí)行目標的方法
                 * @param objects 目標方法執(zhí)行時的入參
                */

                @Override
                public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                    String s = "proxy";
                    s += method.invoke(origin, objects);
                    return s;
                }
            }

            public static void main(String[] args) {
                IHello IHello = (IHello) getInstance(IHello.classnew ProxyHandler<>(new Hello()));

                System.out.println(IHello.toString());

                generateProxyClass();
            }

            // 創(chuàng)建代理對象
            public static <T> Object getInstance(Class<T> clazz, ProxyHandler<T> handler) {
                return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
            }

            private static void generateProxyClass() {
                byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Hello.class.getInterfaces());
                String path = "/Users/nathan.yang/workspace/algorithm_Java/out/StuProxy.class";
                try (FileOutputStream fos = new FileOutputStream(path)) {
                    fos.write(classFile);
                    fos.flush();
                    System.out.println("代理類文件寫入成功");
                } catch (Exception e) {
                    System.out.println("寫文件錯誤");
                }
            }
        }

        3. 動態(tài)代理原理

        1.Proxy#newProxyInstance() 方法是動態(tài)代理的入口,其生成動態(tài)代理對象主要有以下幾個步驟:

        • getProxyClass0() 方法生成代理類
        • 獲取到代理類后將 InvocationHandler 對象入參,反射調用構造方法生成動態(tài)代理對象
        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);
             }

             /*
              * 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;
                         }
                     });
                 }
                 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);
             }
         }

        2.Proxy#getProxyClass0() 方法其實是從一個 WeakCache 中去獲取代理類,其獲取邏輯是如果緩存類中沒有代理類的話就調用ProxyClassFactory#apply(),通過代理類工廠去即時生成一個代理類,其步驟如下:

        • 首先通過指定的類加載器去驗證目標接口是否可被其加載
        • 通過接口所在包等條件決定代理類所在包及代理類的全限定名稱,代理類名稱是包名+$Proxy+id
        • 通過 ProxyGenerator.generateProxyClass() 生成字節(jié)碼數組,然后調用 native 方法 defineClass0() 將其動態(tài)生成的代理類字節(jié)碼加載到內存中
        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);
         }
         
        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 {
                         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.
                  */

                 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) {
                     // 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();
                 String proxyName = proxyPkg + proxyClassNamePrefix + num;

                 /*
                  * Generate the specified proxy class.
                  */

                 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                     proxyName, interfaces, accessFlags);
                 try {
                     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());
                 }
             }

        3.反射獲取到代理類參數為 InvocationHandler.class 的構造器,其實也就是 Proxy 的帶參構造器,調用構造器cons.newInstance(new Object[]{h})生成代理對象

        protected Proxy(InvocationHandler h) {
             Objects.requireNonNull(h);
             this.h = h;
         }

        4.通過以下代碼可以將 JVM 中加載的代理類輸出成 class 文件,之后就可以使用反編譯工具查看代理類的源碼

        private static void generateProxyClass() {
             byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Hello.class.getInterfaces());
             String path = "/Users/nathan/workspace/algorithm_Java/out/StuProxy.class";
             try (FileOutputStream fos = new FileOutputStream(path)) {
                 fos.write(classFile);
                 fos.flush();
                 System.out.println("代理類文件寫入成功");
             } catch (Exception e) {
                 System.out.println("寫文件錯誤");
             }
         }

        5.生成的代理類源碼如下,很明顯可以看到該類實現動態(tài)代理的原理:

        • 通過 static 代碼塊將被代理類中每一個方法封裝為 Method 對象,生成方法表
        • 代理類對象執(zhí)行被代理類同名方法時,通過其父類Proxy保留的指向InvocationHandler對象的引用調用 InvocationHandler#invoke() 方法,完成動態(tài)代理
        public final class $Proxy0 extends Proxy implements IHello {
         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 String sayHi() throws  {
             try {
               // 父類 Proxy 保留的指向 InvocationHandler 對象的引用調用 invoke() 方法
                 return (String)super.h.invoke(this, m3, (Object[])null);
             } catch (RuntimeException | Error var2) {
                 throw var2;
             } catch (Throwable var3) {
                 throw new UndeclaredThrowableException(var3);
             }
         }

         public final String toString() throws  {
             try {
                 return (String)super.h.invoke(this, m2, (Object[])null);
             } catch (RuntimeException | Error var2) {
                 throw var2;
             } catch (Throwable var3) {
                 throw new UndeclaredThrowableException(var3);
             }
         }

        ......

        // 方法表
         static {
             try {
                 m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                 m3 = Class.forName("ServiceProxy$IHello").getMethod("sayHi");
                 m2 = Class.forName("java.lang.Object").getMethod("toString");
                 m0 = Class.forName("java.lang.Object").getMethod("hashCode");
             } catch (NoSuchMethodException var2) {
                 throw new NoSuchMethodError(var2.getMessage());
             } catch (ClassNotFoundException var3) {
                 throw new NoClassDefFoundError(var3.getMessage());
             }
         }
        }

        來源:https://blog.csdn.net/weixin_45505313

        推薦閱讀:

        世界的真實格局分析,地球人類社會底層運行原理

        不是你需要中臺,而是一名合格的架構師(附各大廠中臺建設PPT)

        企業(yè)IT技術架構規(guī)劃方案

        論數字化轉型——轉什么,如何轉?

        華為干部與人才發(fā)展手冊(附PPT)

        企業(yè)10大管理流程圖,數字化轉型從業(yè)者必備!

        【中臺實踐】華為大數據中臺架構分享.pdf

        華為的數字化轉型方法論

        華為如何實施數字化轉型(附PPT)

        超詳細280頁Docker實戰(zhàn)文檔!開放下載

        華為大數據解決方案(PPT)



        瀏覽 32
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            黄色的网站在线观看 | 久热精品在线 | 成人乱人乱一区二区三区一级视频 | AV操操操 | 91精品人妻一区二区三区四区 | 夫妇互换当面做的爱 | 成人毛片18男毛片免费看 | 欧美V亚洲V日韩v | 2O19无码视频 | 公车上的奶水诗锦全文阅读 |