Javaでの反射(reflection)とエージェント(proxy)

22827 ワード

Javaでの反射とエージェント
特別声明:本文は主に学習反射とエージェントの学習過程を記録し、エージェント部分の大部分のテキストはすべて転載に属する.原文を読むことができます.それ以外は、すべてのコードと反射部分が個人的に書かれています.
はんしゃ
最近、「Javaプログラミング思想」の第4版の第14章--タイプ情報を読んだばかりで、利益を得て、一部の内容を抜粋しました.具体的には以下の通りです.
反射:実行時のクラス情報はC++システムのRTTIと似ており、反射は実行時にクラスの情報を取得します.Classクラスとjava.lang.reflectクラスライブラリは、Field、MethodおよびConstructorクラス(クラスごとにメンバーインタフェースが実装されています).これらのタイプのオブジェクトは、未知のクラスに対応するメンバーを表すためにJVMによって実行時に作成されます.これにより、Constructorを使用して新しいオブジェクトを作成し、get()およびset()メソッドでFieldオブジェクトに関連付けられたフィールドを読み取り、変更できます.Methodオブジェクトの関連付けに関するメソッドをinvokeメソッドで呼び出します.また、getFields()、getMethods()、getConstructors()などの便利なメソッドを呼び出して、フィールド、メソッド、コンストラクタを表すオブジェクトの配列を返すこともできます.
RTTIと反射の本当の違いを理解する:RTTIにとって、コンパイラはコンパイル時に開いて検査する.classファイル.反射機構としては、classファイルはコンパイル時に取得できないので、実行時に開いてチェックします.classファイル.
例は次のとおりです.
//   《Java    》   
package me.ethan.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;

public class ShowMethods {
    private static String usage = "usage:
"
+ "ShowMethods qualified.class.name
"
+ "To show all methods in class or:
"
+ "ShowMethods qualified.class.name word
"
+ "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if (args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { // Class Class> c = Class.forName(args[0]); Method[] methods = c.getMethods(); // Method Constructor[] constructors = c.getConstructors(); // Constructor if (args.length == 1) { for (Method method : methods) { System.out.println(p.matcher(method.toString()).replaceAll("")); } for (Constructor constructor : constructors) { System.out.println(p.matcher(constructor.toString()).replaceAll("")); } lines = methods.length + constructors.length; } else { for (Method method : methods) { if (method.toString().indexOf(args[1]) != -1) { System.out.println(p.matcher(method.toString()).replaceAll("")); lines++; } } for (Constructor constructor : constructors) { if (constructor.toString().indexOf(args[1]) != -1) { System.out.println(p.matcher(constructor.toString()).replaceAll("")); lines++; } } } } catch (ClassNotFoundException e) { System.out.println("No such Class"+e); } } }

出力結果:
public static void main(String[])
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods()    //           

上のmain()メソッドを実行するには、パッケージ名+クラス名(すなわちクラスの全制限名)のパラメータを手動で設定する必要があります.
エージェント
エージェントの役割を例に挙げて説明します.もし私たちがスターを招待したいとしたら、スターを直接接続するのではなく、スターのマネージャーに連絡して、同じ目的を達成します.スターは目標の対象で、彼は活動中の番組を担当している限り、他の些細なことは彼の代理人(マネージャー)に任せて解決します.これが代理思想の現実の一例です.
エージェントモードのキーは、エージェントオブジェクトとターゲットオブジェクトです.プロキシオブジェクトは、ターゲットオブジェクトの拡張であり、ターゲットオブジェクトが呼び出されます.
スタティツクエージェント
静的エージェントを使用する場合は、インタフェースまたは親を定義し、被エージェントオブジェクトがエージェントオブジェクトとともに同じインタフェースを実装するか、同じ親を継承する必要があります.例は次のとおりです.
//    (               )
package me.ethan.reflect;

public interface Image {
    void display();
}
//    
package me.ethan.reflect;

public class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    @Override
    public void display() {
        System.out.println("Displaying "+fileName);
    }

    public void loadFromDisk(String fileName) {
        System.out.println("Loading "+fileName);
    }
}
//   
package me.ethan.reflect;

public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName);
        }
        realImage.display();
    }
}
//   
package me.ethan.reflect;

public class Test {
    public static void main(String[] args) {
        Image image = new ProxyImage("test.jpg");
        //         loadFromDisk()  ,     
        image.display();

        System.out.println("");
        //      
        image.display();
    }
}

出力は次のとおりです.
Loading test.jpg
Displaying test.jpg

Displaying test.jpg

静的エージェントの長所と短所:利点:ターゲットオブジェクトの機能を変更することなく、ターゲット機能を拡張できます.欠点:エージェントオブジェクトはターゲットオブジェクトと同じインタフェースを実装する必要があるため、多くのエージェントクラスがあり、クラスが多すぎると同時に、インタフェースがメソッドを増加すると、ターゲットオブジェクトとエージェントオブジェクトがメンテナンスされます.
静的エージェントがもたらす不便に対応するために,動的エージェントが誕生した.
JDKダイナミックエージェント
JDKダイナミックエージェントにはいくつかの特徴があります.
  • エージェントオブジェクト、インタフェース
  • を実装する必要はありません.
  • エージェントオブジェクトの生成は、JDKのAPIを利用して、動的にメモリにエージェントオブジェクトを構築する(エージェントオブジェクト/ターゲットオブジェクトを作成するためのインタフェースのタイプを指定する必要がある)
  • である.
  • JDK動的エージェントはJDKエージェントとも呼ばれ、インタフェースエージェント
  • JDKでエージェントオブジェクトを生成するAPI JDK実装エージェントはnewProxyInstance()メソッドのみを使用する必要がありますが、このメソッドは3つのパラメータを受信する必要があります.完全な書き方は次のとおりです.
    static Object newProxyInstance(ClassLoader loader, Class>[] interfaces,InvocationHandler h )
    /*
                     
        ClassLoader loader:   ,        。    :
                         .getClass().getClassLoader();
    
        Class>[] interfaces:             ,          。    :
                         .getClass().getInterfaces();
          
                    Class[Interface1.class,Interface2.class,Interface3.class……]    
    
        InvocationHandler h:     InvocationHandler             
    */

    例は次のとおりです.
    //     
    package me.ethan.reflect;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ProxyFactory {
        //    Object  (            )
        private Object image;
    
        public ProxyFactory(Object image) {
            this.image = image;
        }
    
        //JDK    
        public Object getProxyInstance() {
            return Proxy.newProxyInstance(
                    image.getClass().getClassLoader(),
                    image.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("    ……");
                            //         
                            Object obj = method.invoke(image, args);
                            System.out.println("    ……");
                            return obj;
                        }
                    });
        }
    }
    
    //   
    package me.ethan.reflect;
    
    public class Test {
        public static void main(String[] args) {
            //     
            Image image = new RealImage("test.jpg");
            System.out.println(image.getClass().getSimpleName());    //   RealImage,   Image
    
            System.out.println("");
            //      
            Image proxyImage = (Image) new ProxyFactory(image).getProxyInstance();
            System.out.println(proxyImage.getClass().getCanonicalName());   //           com.sun.proxy.$Proxy0
            proxyImage.display();
        }
    }

    出力は次のとおりです.
    Loading test.jpg
    RealImage
    
    com.sun.proxy.$Proxy0
        ……
    Displaying test.jpg
        ……

    欠点:エージェントオブジェクトはインタフェースを実装する必要はありませんが、ターゲットオブジェクトはインタフェースを実装する必要があります.そうしないと、ダイナミックエージェントは使用できません.
    上記JDKエージェントの他に,インタフェースベースでないCglib動的エージェントがある.
    Cglibダイナミックエージェント
    静的エージェントと動的エージェントモードは,いずれもターゲットオブジェクトが1つのインタフェースを実現するターゲットオブジェクトであることを要求するが,ターゲットオブジェクトが単独のオブジェクトであり,インタフェースが実現されていない場合があり,この場合,ターゲットオブジェクトのサブクラスでエージェントを実現する方法をCglib動的エージェントと呼ぶ.Cglibエージェントは、ターゲットオブジェクトの機能拡張を実現するためにメモリにサブクラスオブジェクトを構築するサブクラスエージェントとも呼ばれます.
  • JDKの動的エージェントには、動的エージェントを使用するオブジェクトが1つ以上のインタフェースを実装しなければならないという制限があり、エージェントがインタフェースを実装クラスがない場合は、Cglibを使用して
  • を実装することができる.
  • Cglibは強力な高性能のコード生成パッケージであり、実行期間中にjavaクラスを拡張しjavaインタフェースを実現することができ、Spring AOPやsynaopなどの多くのAOPのフレームワークで広く使用され、彼らに方法を提供するinterception(ブロック)
  • である.
  • Cglibパッケージの最下位は、小さなバイトコード処理フレームワークASMを使用してバイトコードを変換し、新しいクラスを生成することです.ASMを直接使用することは奨励されません.JVMの内部構造にclassファイルを含むフォーマットとコマンドセットを熟知する必要があるからです.
    Cglibサブクラスエージェントの実装方法:
  • cglibのjarファイルを導入する必要があるが、SpringのコアパッケージにはすでにCglib機能が含まれているため、spring-core:4.3.13.RELEASEを直接導入すれば
  • である.
  • 機能パッケージを導入すると、メモリにサブクラス
  • を動的に構築することができる.
  • エージェントのクラスはfinalではありません.そうしないと、
  • とエラーが発生します.
  • ターゲットオブジェクトのメソッドがfinal/staticの場合、ブロックされません.すなわち、ターゲットオブジェクトの追加のビジネスメソッドは実行されません.

  • 例は次のとおりです.spring-core:4.3.13.RELEASEをインポートします.
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-coreartifactId>
            <version>4.3.13.RELEASEversion>
        dependency>

    主なコード:
    //  Cglib     
    package me.ethan.reflect;
    
    import org.springframework.cglib.proxy.Callback;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class CglibProxyFactory implements MethodInterceptor {
        //       
        public Object image;
    
        public CglibProxyFactory(Object image) {
            this.image = image;
        }
    
        //              
        public Object getProxyInstance() {
            //   
            Enhancer enhancer = new Enhancer();
            //    
            enhancer.setSuperclass(image.getClass());
            //      
            enhancer.setCallback((Callback) this);
            //    (    )
            return enhancer.create();
        }
    
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("    ……");
            //      
            Object obj = method.invoke(image, objects);
            System.out.println("    ……");
            return obj;
        }
    }
    
    //   
    package me.ethan.reflect;
    
    public class Test {
        public static void main(String[] args) {
            //      (          )
            RealImage realImage = new RealImage("test.jpg");
            //      
            RealImage proxyImage = (RealImage) new CglibProxyFactory(realImage).getProxyInstance();
            //         
            proxyImage.display();
        }
    }

    出力は次のとおりです.
    Loading test.jpg
        ……
    Displaying test.jpg
        ……

    Cglibを使用してターゲットオブジェクトを動的に代理する練習中に、最初の実行時に例外を放出するエピソードが表示されます.
    Exception in thread "main" java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given

    理由:Cglibダイナミックエージェントを使用する場合は、ターゲットクラスにパラメトリックなしコンストラクタが必要です.
    非パラメトリックコンストラクタを追加して正常に動作します.