『javaと設計モード』のエージェントモード

19770 ワード

テキスト接続
概要
Javaプログラミングの目標は現実が完成できないことを実現することであり、現実を最適化して完成できるのは、仮想技術である.生活のあらゆる面でコードに仮想化することができます.エージェントモデルは現実生活の中でこのような概念を言っている:仲介.
≪プロキシ・モードの定義|Proxy Mode Definition|emdw≫:オブジェクトにプロキシを提供し、プロキシ・オブジェクトによって元のオブジェクトへの参照を制御します.
エージェント・モードには、次の役割があります.
  • ISubject:抽象的なトピックロールで、インタフェースです.このインタフェースは、オブジェクトとそのエージェントが共有するインタフェースです.
  • RealSubject:リアルトピックロールは、抽象トピックインタフェースを実装するクラスです.
  • Proxy:リアルオブジェクトRealSubjectへの参照を含むプロキシロールで、リアルオブジェクトを操作できます.プロキシオブジェクトは、リアルオブジェクトと同じインタフェースを提供し、リアルオブジェクトの代わりにいつでも使用できます.また、プロキシオブジェクトは、実際のオブジェクト操作を実行するときに、実際のオブジェクトをカプセル化することに相当する他の操作を追加することができます.

  • ダイナミックエージェントを実現するためのキーテクノロジーは反射です
    スタティツクエージェント
    エージェントモードには、仮想エージェント、カウントエージェント、リモートエージェント、ダイナミックエージェントがいくつかあります.主に2種類に分けられ,静的エージェントと動的エージェントである.静的エージェントは比較的簡単で、プログラマによって作成されたエージェントクラスであり、プログラムが実行される前にコンパイルされ、プログラムによってエージェントクラスが動的に生成されるのではなく、いわゆる静的である.このようなシナリオを考慮すると、管理者はウェブサイト上で操作を実行し、操作結果を生成すると同時に操作ログを記録する必要があるのが一般的です.エージェント・モードを使用できます.エージェント・モードは、集約と継承の2つの方法で実現できます.
    /**   :        
     * @author Goser    (mailto:[email protected]) 
     * @Since 2016 9 7  
     */  
    //1.        
    public interface Manager {  
        void doSomething();  
    }  
    //2.       
    public class Admin implements Manager {  
        public void doSomething() {  
            System.out.println("Admin do something.");  
        }  
    }  
    //3.              
    public class AdminPoly implements Manager{  
        private Admin admin;  
         
        public AdminPoly(Admin admin) {  
            super();  
            this.admin = admin;  
        }  
       
        public void doSomething() {  
            System.out.println("Log:admin    ");  
            admin.doSomething();  
            System.out.println("Log:admin    ");  
        }  
    }  
    //4.      
            Admin admin = new Admin();  
            Manager m = new AdminPoly(admin);  
            m.doSomething();  
    //   :         
    //                   
    //1.     
    public class AdminProxy extends Admin {  
        @Override  
        public void doSomething() {  
            System.out.println("Log:admin    ");  
            super.doSomething();  
            System.out.println("Log:admin    ");  
        }  
    }  
    //2.      
            AdminProxy proxy = new AdminProxy();  
            proxy.doSomething();  
    

    集約実装方式では,エージェントクラスが被エージェントクラスを集約し,エージェントクラスおよび被エージェントクラスが同じインタフェースを実現し,柔軟な多変を実現できる.継承式の実現方式は柔軟ではない.たとえば,管理者の操作と同時に権限の処理,操作内容のログ記録,操作後のデータの変化の3つの機能が必要である.3つの機能の組み合わせは6種類あり、つまり継承を用いてAdminを継承したエージェントクラスを6つ作成し、集約を用いて権限の処理、ログ記録、データ変化の3つの機能に対してエージェントクラスを作成し、ビジネスロジックでは具体的なニーズに応じてコードの順序を変更すればよい.
    ダイナミックエージェント
    一般に、エージェントモードでは、1つのトピッククラスが1つのエージェントクラスに1つずつ対応しており、これも静的エージェントモードの特徴である.ただし,nの各トピッククラスがある場合もあるが,エージェントクラスの「前処理,後処理」はいずれも同じであり,呼び出しトピックのみが異なる.すなわち,複数のトピッククラスが1つのエージェントクラスに対応し,「前処理,後処理」機能を共有し,必要なトピックを動的に呼び出し,プログラム規模を大幅に低減することが,動的エージェントモードの特徴である.
    JDKダイナミックエージェント
    インプリメンテーション
    //1.       
    public interface Moveable {  
        void move()  throws Exception;  
    }  
    //2.       
    public class Car implements Moveable {  
        public void move() throws Exception {  
            Thread.sleep(new Random().nextInt(1000));  
            System.out.println("     …");  
        }  
    }  
    //3.       
    public class TimeHandler implements InvocationHandler {  
        private Object target;  
         
        public TimeHandler(Object target) {  
            super();  
            this.target = target;  
        }  
       
        /** 
         *   : 
         *proxy        
         *method          
         *args       
         *   : 
         *Object       
         */  
        public Object invoke(Object proxy, Method method, Object[] args)  
                throws Throwable {  
            long startTime = System.currentTimeMillis();  
            System.out.println("      …");  
            method.invoke(target, args);  
            long stopTime = System.currentTimeMillis();  
            System.out.println("      …      :" + (stopTime - startTime) + "  !");  
            return null;  
        }  
       
    }  
    //     
    public class Test {  
        public static void main(String[] args) throws Exception{  
            Car car = new Car();  
            InvocationHandler h = new TimeHandler(car);  
            Class> cls = car.getClass();  
            /** 
             *loader      
             *interfaces      
             *h InvocationHandler 
             */  
            Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);  
            m.move();  
        }  
    }  
    

    コード説明:テストコードでProxy.新ProxyInstance()メソッドには、3つのパラメータが必要です.クラスローダ(エージェントを行うクラス)、エージェントクラスによって実装されるインタフェース、トランザクションプロセッサです.まず、Carをインスタンス化し、InvocationHandlerのサブクラスTimeHandlerをインスタンス化し、各パラメータをProxyの静的メソッドnewProxyInstance()に転送すると、Carのエージェントクラスが得られます.前の静的エージェント、エージェントクラスは私たちが作成したものです.動的エージェントは、エージェントクラスを記述する必要はありません.プログラムで動的に生成されます.
    JDK動的プロキシステップ
  • InvocationHandlerインタフェースを実装するクラスを作成します.invoke()メソッド
  • を実装する必要があります.
  • プロキシ対象クラスおよびインタフェース
  • を作成する.
  • Proxyの静的メソッドを呼び出し、プロキシクラス
  • を作成する
  • エージェント呼び出し方法
  • なぜこのような操作を行うのか、ProxyとInvocationHandlerのソースコードから答えを探すことができます.ソースコードに興味がない場合は、次のソースコード部分を省略してください.
    JDKダイナミックエージェントの原理とソースコード
    新ProxyInstance()メソッドのソースコード:
     public static Object newProxyInstance(ClassLoader loader,  
                                             Class>[] interfaces,  
                                             InvocationHandler h)  
            throws IllegalArgumentException{  
            if (h == null) {  
                throw new NullPointerException();  
            }  
            final Class>[] intfs = interfaces.clone();  
            final SecurityManager sm = System.getSecurityManager();  
            if (sm != null) {  
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);  
            }  
            /*           */  
            Class> cl = getProxyClass0(loader, intfs);  
            /*                  .*/  
            try {  
            //          
                final Constructor> cons =cl.getConstructor(constructorParams);  
                final InvocationHandler ih = h;  
                if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {  
                  //             public    ,              ,    doPrivilege(native      )      。  
                    return AccessController.doPrivileged(newPrivilegedAction() {  
                        public Object run() {  
                            return newInstance(cons,ih);  
                        }  
                    });  
                } else {  
                    return newInstance(cons,ih);  
                }  
            } catch (NoSuchMethodException e) {  
                throw new InternalError(e.toString());  
            }  
    }  
    

    エージェントクラスを取得するコードはClass>cl=getProxyClass 0(loader,intfs);これによりエージェントクラスの構築関数が得られ,生成エージェントクラスのインスタンスがメソッドの呼び出し者に返される.getProxyClass 0()メソッドに引き続きフォローします.
    /**      。          checkproxyaccess        。*/  
        private static Class> getProxyClass0(ClassLoader loader,  
                                              Class>... interfaces) {  
        //        ,65535       ,         ,             ,2^16-1   
        if (interfaces.length > 65535) {  
                throw new IllegalArgumentException("interface limit exceeded");  
            }  
            //                        ,            ;     ProxyClassFactory       
            return proxyClassCache.get(loader, interfaces);  
    }  
    

    エージェントクラスがどのように生成されているかは見られませんが、エージェントクラスがproxyClassCacheから取得されていることしか知りません.この変数はキャッシュに関連するオブジェクトであり、その変数の宣言と初期化を表示します.
    private static final WeakCache[], Class>>  
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 
    

    ProxyClassCacheはエージェントクラスをキャッシュするためのクラス変数であることがわかりますが、クラス変数の特徴はクラスに1つずつ対応していることがわかります.1つの仮想マシンではクラスが1つしかなく、1つの仮想マシンでもクラス変数が1つしかないことに対応しています.ここでは、Proxyクラスがロードされたときに値が割り当てられています.割り当て操作のパラメータにはProxyClassFactory()という構造関数があります.これは動的エージェントの鍵です.エージェントクラスのクラスファイルバイトコードを生成します.引き続きフォローして、エージェントクラスの生成場所を見つけました.
    /**                        */  
    private static final class ProxyClassFactory  
        implements BiFunction[], Class>>  
    {  
        //             
        private static final String proxyClassNamePrefix = "$Proxy";  
      
        //                   
        private static final AtomicLong nextUniqueNumber = new AtomicLong();  
      
        @Override  
        public Class> apply(ClassLoader loader,Class>[] interfaces) {  
      
            Map, Boolean>interfaceSet = new IdentityHashMap<>(interfaces.length);  
            for (Class> intf : interfaces) {  
                /*                        。*/  
                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 classloader");  
                }  
                /*             。*/  
                if (!interfaceClass.isInterface()) {  
                    throw new IllegalArgumentException(  
                       interfaceClass.getName() + " is not an interface");  
                }  
                /*      */  
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {  
                    throw new IllegalArgumentException(  
                        "repeated interface: " + interfaceClass.getName());  
                }  
            }  
      
            String proxyPkg = null;     //         
      
            /*           ,              。                  。*/  
            for (Class> intf : interfaces) {  
                int flags = intf.getModifiers();  
                if (!Modifier.isPublic(flags)) {  
                    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 fromdifferent packages");  
                    }  
                }  
            }  
      
            if (proxyPkg == null) {  
                //             ,  com.sun.proxy      
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
            }  
      
            /*          */  
            long num = nextUniqueNumber.getAndIncrement();  
            //       
            String proxyName = proxyPkg + proxyClassNamePrefix + num;  
      
            /*         */  
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,interfaces);  
            try {  
                return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);  
            } catch (ClassFormatError e) {  
                throw new IllegalArgumentException(e.toString());  
            }  
        }  
    

    ProxyClassFactoryでは、伝達する被エージェントクラスとその実現インタフェースに基づいて生成するエージェントクラスのバイトコードがキャッシュにロードされるが、キャッシュにロードされるのは1つにすぎないエージェントクラスを生成する具体的な論理が見られる.JAvaファイルも使えないので、下位層にはコンパイルなどの操作もあります.ここまで,JDKにおける動的エージェントの顔を大まかに見極めることができ,実現するステップは次のようになる.
  • エージェントクラスのソースコードを作成します.
  • ソースコードをバイトコードにコンパイルする.
  • バイトコードをメモリにロードする.
  • エージェントクラスオブジェクトをインスタンス化し、呼び出し者に返す.

  • 最下位のコードは見えませんが、生成されたバイトコードを表示できます.
    //            
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy1", Car.class.getInterfaces());   
            FileOutputStream out = null;   
            try {   
                out = new FileOutputStream(System.getProperty("user.dir") + "\\$Proxy1.class");   
                out.write(classFile);   
                out.flush();   
            } catch (Exception e) {   
                e.printStackTrace();   
            } finally {   
                try {   
                    out.close();   
                } catch (IOException e) {   
                    e.printStackTrace();   
                }   
            }  
       
    //      :  
    importcn.com.goser.proxy.imooc.staticproxy.Moveable;  
    importjava.lang.reflect.InvocationHandler;  
    importjava.lang.reflect.Method;  
    importjava.lang.reflect.Proxy;  
    import java.lang.reflect.UndeclaredThrowableException;  
       
    public final class $Proxy1 extends Proxy  
      implements Moveable  
    {  
      private static Method m1;  
      private static Method m3;  
      private static Method m0;  
      private static Method m2;  
       
      public $Proxy1(InvocationHandler paramInvocationHandler)  
        throws  
      {  
        super(paramInvocationHandler);  
      }  
       
      public final boolean equals(Object paramObject)  
        throws  
      {  
        try  
        {  
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
        }  
        catch (RuntimeException localRuntimeException)  
        {  
          throw localRuntimeException;  
        }  
        catch (Throwable localThrowable)  
        {  
        }  
        throw new UndeclaredThrowableException(localThrowable);  
      }  
       
      public final void move()  
        throws Exception  
      {  
        try  
        {  
          this.h.invoke(this, m3, null);  
          return;  
        }  
        catch (Exception localException)  
        {  
          throw localException;  
        }  
        catch (Throwable localThrowable)  
        {  
        }  
        throw new UndeclaredThrowableException(localThrowable);  
      }  
       
      public final int hashCode()  
        throws  
      {  
        try  
        {  
          return ((Integer)this.h.invoke(this, m0, null)).intValue();  
        }  
        catch (RuntimeException localRuntimeException)  
        {  
          throw localRuntimeException;  
        }  
        catch (Throwable localThrowable)  
        {  
        }  
        throw new UndeclaredThrowableException(localThrowable);  
      }  
       
      public final String toString()  
        throws  
      {  
        try  
        {  
          return (String)this.h.invoke(this, m2, null);  
        }  
        catch (RuntimeException localRuntimeException)  
        {  
          throw localRuntimeException;  
        }  
        catch (Throwable localThrowable)  
        {  
        }  
        throw new UndeclaredThrowableException(localThrowable);  
      }  
       
      static  
      {  
        try  
        {  
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });  
          m3 = Class.forName("cn.com.goser.proxy.imooc.staticproxy.Moveable").getMethod("move", new Class[0]);  
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
          return;  
        }  
        catch (NoSuchMethodExceptionlocalNoSuchMethodException)  
        {  
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
        }  
        catch (ClassNotFoundExceptionlocalClassNotFoundException)  
        {  
        }  
        throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
      }  
    }  
    

    生成されたバイトコードは比較的長いが、バイトコードの中で最も重要な情報はエージェントクラスの宣言である:public final class$Proxy 1 extends Proxyは生成されたエージェントクラスがProxyクラスを継承していることを見ることができ、これはJDKダイナミックエージェントを使用して継承式ダイナミックエージェントを実現できない理由を説明している.Javaはマルチ継承を許さないためである.生成されたエージェントクラス自体がProxyクラスを継承している.これで、JDKのダイナミックエージェントの使用と底の原理の分析が終わり、ダイナミックエージェントの神秘的なベールを剥がして、やはり1枚の美人です.最下層のnativeメソッドがどのようにエージェントクラスのバイトコードを動的に生成するかについても簡単にシミュレーションしてみましょう.まず、エージェントクラスのソースコードを生成し、ソースコードをコンパイルしてエージェントクラスを生成するインスタンスを呼び出し者に返します.この手順に従って、シミュレーションコードの作成を開始します.
    /** 
     * JDK java.lang.reflect.Proxy    
     * @author Goser    (mailto:[email protected]) 
     * @Since 2016 9 7  
     */  
    public class Proxy {  
        private static final String  RT = "\r
    "; public static Object newProxyInstance() throws Exception{ // String sourceCode = "packagecn.com.goser.proxy.jdk.simulate;"+ RT + "importcn.com.goser.proxy.imooc.staticproxy.Admin;" + RT + "importcn.com.goser.proxy.imooc.staticproxy.Manager;" + RT + "// " + RT + "public class $Proxy0 implementsManager{" + RT + " privateAdmin admin;" + RT + " public$Proxy0(Admin admin) {" + RT + " super();" + RT + " this.admin= admin;" + RT + " }" + RT + " publicvoid doSomething() {" + RT + " System.out.println(\"Log:admin \");" + RT + " admin.doSomething();" + RT + " System.out.println(\"Log:admin \");" + RT + " }" + RT + "}"; String filename = System.getProperty("user.dir") + "/src/main/java/cn/com/goser/proxy/jdk/simulate/$Proxy0.java"; File file = new File(filename); // org.apache.commons.io.FileUtils.writeStringToFile() // , , .java FileUtils.writeStringToFile(file,sourceCode); // JavaCompiler complier = ToolProvider.getSystemJavaCompiler(); // StandardJavaFileManager fileMgr =complier.getStandardFileManager(null, null, null); Iterable its =fileMgr.getJavaFileObjects(filename); // CompilationTask task = complier.getTask(null, fileMgr, null, null, null, its); // , .class task.call(); fileMgr.close(); //load ClassLoader loader = ClassLoader.getSystemClassLoader(); Class cls = loader.loadClass("cn.com.goser.proxy.jdk.simulate.$Proxy0"); // Constructor ct = cls.getConstructor(Admin.class); return ct.newInstance(new Admin()); } } class test{ public static void main(String[] args) throws Exception { Manager m = (Manager)Proxy.newProxyInstance(); m.doSomething(); } }

    テストコードを実行し、結果は手書きの結果と一致し、JDKにおける動的エージェントの実装シミュレーションを完了した.
    cglibダイナミックエージェント
    前述の解析では,Javaでは単一継承のみが許可され,JDKで生成されたエージェントクラス自体がProxyクラスを継承しているため,JDKで実現された動的エージェントでは継承式の動的エージェントを完了することはできないが,cglibを用いて継承式の動的エージェントを実現することができる.有名なSpringにはcglibダイナミックエージェントが含まれており、Springに付属するcglibでダイナミックエージェントの実装も完了しています.
    //1.      
    public class Train{  
        public void move(){  
            System.out.println("     …");  
        }  
    }  
    //2.      
    public class CGLibProxy implements MethodInterceptor {  
        private Enhancer enhancer = new Enhancer();  
        public Object getProxy(Class> clazz){  
            enhancer.setSuperclass(clazz);  
            enhancer.setCallback(this);  
            return enhancer.create();  
        }  
        /** 
         *              
         *   : 
         * obj       
         *method           
         * args      
         * proxy       
         */  
        public Object intercept(Object obj, Method method, Object[] args,  
                MethodProxy proxy) throws Throwable {  
            //            
            System.out.println("    ");  
            proxy.invokeSuper(obj, args);  
            System.out.println("    ");  
            return null;  
        }  
    }  
    //3.    
    public class Test {  
        public static void main(String[] args) {  
            CGLibProxy proxy = new CGLibProxy();  
            Train t = (Train) proxy.getProxy(Train.class);  
            t.move();  
        }  
    }  
    

    小結ダイナミックエージェントは静的エージェントと比較して,インタフェースで宣言されたすべてのメソッドが呼び出しプロセッサの1セットのメソッドに移行して処理されることが最大の利点である.インタフェースメソッドの数が多い場合,静的エージェントのように各メソッドやメソッドの組合せを処理する必要がなく,柔軟な処理を行うことができる.Proxyは美しくて強力ですが、interfaceエージェントのみがサポートされています.Javaの単一継承メカニズムは、これらの動的エージェントクラスがclassに対する動的エージェントを実現できないことを定めている.幸いcglibはProxyに補完を提供した.classとinterfaceの違いはもともとあいまいで、java 8の中で更にいくつかの新しい特性を増加して、interfaceをますますclassに接近させて、ある日、javaは単一の継承の制限を突破して、動的なエージェントは更に強大になります.