Java設計モードのJDK動的エージェント原理(JDK 8に基づく)


名詞の解釈
  • 静的エージェント:コンパイル期間によってエージェントオブジェクトが決定されます.すなわち,エージェントクラスを符号化する.
  • 動的エージェント:実行時に動的にエージェントオブジェクトを生成します.ログ印刷、呼び出し回数の統計など、被エージェントクラスに対して統一的な処理を行うことができる.
  • JDK動的エージェント:すなわち、JDKに付属する動的エージェント生成方式である.JDK動的エージェントの実装は,被エージェントクラスが自己インタフェースを実装しなければならないことに依存する.
  • cglibダイナミックエージェント:cglibツールパッケージで実現されるダイナミックエージェント生成方式は、バイトコードによってダイナミックエージェントを実現し、被エージェントクラスでインタフェースを実現する必要はありません.

  • ダイナミックエージェントコアソース実装
    public Object getProxy() {
        //jdk          
        return Proxy.newProxyInstance(
          this.getClass().getClassLoader(),
          target.getClass().getInterfaces(),
          this//InvocationHandler         
      );
    }
    

    JDKダイナミックエージェントを使用するには,まずInvocationHandlerインタフェースの実装クラスをカスタマイズし,エージェントクラスの制御ロジックを書く.
    例:
    public class JDKDynamicProxyHandler implements InvocationHandler {
    
      private Object target;
    
      public JDKDynamicProxyHandler(Class clazz) {
        try {
          this.target = clazz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
          e.printStackTrace();
        }
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
        preAction();
        Object result = method.invoke(target, args);
        postAction();
        return result;
      }
    
      public Object getProxy() {
        return Proxy.newProxyInstance(
            this.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
      }
    
      private void preAction() {
        System.out.println("JDKDynamicProxyHandler.preAction()");
      }
    
      private void postAction() {
        System.out.println("JDKDynamicProxyHandler.postAction()");
      }
    }
    

    具体的には、以下のようにエージェントクラスを取得するだけです.
    Object proxy = Proxy.newProxyInstance(
        this.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        invocationHandler);
    

    このコードのコアロジックはProxyのnewProxyInstanceにあります.
    JDK 8に基づく動的エージェント実装.
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            //...
    				//        
            final Class<?>[] intfs = interfaces.clone();
            //...
    				//               
            Class<?> cl = getProxyClass0(loader, intfs);
            try {
                //      
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                //  Proxy             
                return cons.newInstance(new Object[]{h});
            } 
            //...
        }
        
    private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
      			//...         65535
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
      			// WeakCache[], Class>> proxyClassCache=new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
      			//                  ,           ,           。
            return proxyClassCache.get(loader, interfaces);
        }
    
    //proxyClassCache get  
    public V get(K key, P parameter) {
    
      			//...key classloader,parameter    Class  
      			//     entry
            expungeStaleEntries();
    				//  CacheKey key null ,cacheKey object  ,        
            Object cacheKey = CacheKey.valueOf(key, refQueue);
    
            //  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) {
                   //        ,     map,    map   valuesMap
                    valuesMap = oldValuesMap;
                }
            }
    
            //      key,subKey
            Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
      			//             
            Supplier<V> supplier = valuesMap.get(subKey);
            Factory factory = null;
    
            while (true) {
              //               
                if (supplier != null) {
                    //           ,  get       。 supplier WeakCache    Factory
                    V value = supplier.get();
                    if (value != null) {
                        return value;
                    }
                }
                
                if (factory == null) {
                  //      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);
                    }
                }
            }
        }
    
    //Factory get  
    public synchronized V get() { // serialize access
                // re-check
                Supplier<V> supplier = valuesMap.get(subKey);
                if (supplier != this) {
                  //             ,  null,         。
                    return null;
                }
                //       
                V value = null;
                try {
                  //  ProxyClassFactory apply       
                    value = Objects.requireNonNull(valueFactory.apply(key, parameter));
                } finally {
                    if (value == null) { // remove us on failure
                        valuesMap.remove(subKey, this);
                    }
                }
                // CacheValue  value (   )
                CacheValue<V> cacheValue = new CacheValue<>(value);
    
                // cacheValue  reverseMap
                reverseMap.put(cacheValue, Boolean.TRUE);
                return value;
            }
    
    //ProxyClassFactory  apply  
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
                Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
                //  class    ,  class   interface,  class    
      					//...
    						//      
                String proxyPkg = null;     // package to define proxy class in
      					//         
                int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    						//   public         ,          ,       public                  
                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) {
                    //      public    ,    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;
    
                //           
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                  	//  native        
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } 
     					//...
            }
    

    まとめ
  • Proxy.新ProxyInstanceメソッドエージェントクラス取得実行プロセス:
  • Proxy.getProxyClass 0()メソッドエージェントクラスclassを取得します.
  • WeakCache.get()メソッド
  • CacheKey.valueOf(key,refQueue)は、一級キャッシュkey,cacheKeyを取得する.
  • ConcurrentMap.get()メソッドは、2次キャッシュConcurrentMapを取得します.
  • KeyFactoryは二次キャッシュkey,subKeyを生成する.
  • ConcurrentMap.get()メソッドは2次キャッシュvalueを取得し,SupplierはクラスFactoryを実現する.
  • Factoryが存在しない場合、new Factoryにより新しいFactoryが生成される.
  • は、Factoryのgetメソッドによって2次キャッシュ値CacheValueインスタンスを取得する.
  • はFactory内部でConcurrentMapをキャッシュする.get()メソッドは、Supplierインスタンスを取得します.
  • Supplierインスタンスが存在しない場合、ProxyClassFactoryを通過する.apply()メソッドはエージェントクラスclassを生成します.
  • cacheValueパッケージエージェントクラスclassを使用します.



  • Class.getConstructor(InvocationHandler.class)は、パラメータ(InvocationHandler)コンストラクション関数を取得します.
  • Constructor.新Instance(InvocationHandler)はエージェントクラスを取得する.
  • エージェントクラスのパケット名:エージェントクラスによって実装されるインタフェースの限定修飾子によって決定され、非public修飾子がある場合、パケット名は非publicインタフェースが存在するパケットパスである.public修飾子以外の複数のインタフェースの場合、これらのインタフェースは同じパッケージにある必要があります.すべてpublicインタフェースである場合、パケット名はcomとなる.sun.proxy.
  • エージェントクラスのフルパスクラス名:パッケージ名+エージェントクラス名プレフィックス($Proxy)+自増数.

  • Proxy内部にはマルチレベルキャッシュで生成されたエージェントクラスclassを採用し,同じエージェントクラスの繰り返し生成を回避し,パフォーマンスを向上させる.
  • キャッシュで使用されるクラスはWeakCacheです.
    //   
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    
  • 一級キャッシュのkeyはCacheKeyであり、CacheKeyはclassloaderとrefQueue(参照キュー)からなる.
  • 一級キャッシュのvalueはConcurrentMapです.
  • 二次キャッシュのkey,subKeyは、subKeyFactory(KeyFactory)ファクトリクラスによって、プロキシクラスによって実現されるインタフェースの数に基づいて生成される.
  • 二次キャッシュのvalueはSupplierの実装クラス,Factoryである.
  • エージェントクラスclassは二次キャッシュのget()メソッドにより取得され、最終的にエージェントクラスclassを生成するのはProxyClassFactoryのapplyメソッドであり、applyメソッドはバイトコードファイルを生成した後、nativeメソッドdefineClass 0を呼び出すことにより最終的にClassを生成する.
  • エージェントクラスclass逆コンパイル後のコード
    注意:逆コンパイル後のclassファイルを見るには、システム変数を追加する必要があります.sun.misc.ProxyGenerator.saveGeneratedFileはtrueである、テストコードでSystemを手動で指定することもできる.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
    
    package com.sun.proxy;
    
    import com.xt.design.pattern.proxy.dynamic.jdk.HelloService;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements HelloService {
        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  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void sayHello() throws  {
            try {
                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);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (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("com.xt.design.pattern.proxy.dynamic.jdk.HelloService").getMethod("sayHello");
                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());
            }
        }
    }
    
    

    上記で生成されたエージェントクラスclassファイルから、次のことがわかります.
  • エージェントクラスはProxyクラスを継承し、エージェントクラスのインタフェース
  • を実現する.
  • エージェントクラスとProxyには、いずれもコンストラクション関数があり、パラメータはInvocationHandlerオブジェクトです.
  • エージェントクラス呼び出しメソッドは、いずれもInvocationHandlerによって呼び出される.
  • メソッドは、オブジェクトがパッケージタイプであり、intなどの基本データタイプが返された場合、パッケージクラスに変換されて返されます.

  • JDK動的エージェントがエージェントクラスにインタフェースを実装しなければならない理由は,生成されたエージェントクラスがProxyを継承し,Javaが単一継承,多実装であるため,インタフェースを実装することによってのみエージェントクラスを生成できるためである.
    しかしエージェントクラスはなぜProxyを継承するのか??継承Proxyは、InvocationHandlerを転送するためにパラメトリック構造を取得しただけで、具体的な呼び出し方法はInvocationHandlerを介しているが、なぜInvocationHandlerを直接参照しないのか、単一の継承による被エージェントクラスがインタフェースの制限を実現しなければならないのか.
    Stack Overflowではこれが標準だと言われています.
    個人的には知っている答えがもっと合理的かもしれないと思います.