Javaアノテーションとリフレクションの詳細

注釈

アノテーションとは何ですか?

  • アノテーションは、JDK1.5以降に導入された新しいテクノロジーです。
  • 注釈の役割
    • プログラムそのものではなく、説明することができます(この点はコメントと変わりません)
    • 他のプログラム(コンパイラなど)で読み取ることができます
  • 注釈形式
    • コードには「@アノテーション名」としてアノテーションが存在し、いくつかのパラメーター値を追加することもできます(例:@SuppressWarnings(value = "unchecked"))。
  • アノテーションはどこで使用されますか?
    • パッケージ、クラス、メソッド、フィールドなどに添付することができ、補助情報を追加するのと同じです。リフレクションメカニズムプログラミングを通じてこれらのメタデータにアクセスできます。

例:(親クラスのメソッドをオーバーライドするときに@Overrideアノテーションがあります)
ここに画像の説明を挿入

組み込みの注釈

  • @Override:java.lang.Overrideで定義されています。このアノテーションは修辞メソッドにのみ適用され、メソッド宣言がスーパークラスの別のメソッド宣言をオーバーライドすることを意図していることを示します。
@Target(value=METHOD)
@Retention(value=SOURCE)
public @interface Override
  • @Deprecated:java.lang.Deprecatedで定義されているこのアノテーションは、修辞的なメソッド、属性、クラスに使用できます。これは、通常、危険であるか、より適切なオプションがあるため、プログラマーがそのような要素を使用することを推奨されていないことを示します。
@Documented
@Retention(value=RUNTIME)
@Target(value={CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE})
public @interface Deprecated

@SuppressWarnings:java.lang.SuppressWaringsで定義され、コンパイル時の警告メッセージを抑制するために使用されます。前の2つのコメントとは異なります。正しく使用するには、パラメーターを追加する必要があります。これらのパラメーターは既に定義されています。使用するだけです。選択的に

  • @SuppressWarnings( "all")
  • @SuppressWarnings( "unchecked")
  • @SuppressWarnings(value = {“ unchecked”、“ deprecation”})
  • 等々…
@Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
@Retention(value=SOURCE)
public @interface SuppressWarnings

メタアノテーション

  • メタ注釈の役割は、他の注釈に注釈を付けることです。Javaは、他の注釈タイプの説明を提供するために使用される4つの標準メタ注釈タイプを定義します。
  • これらのタイプとそれらがサポートするクラスは、java.lang.annotationパッケージ(@ Target、@ Retention、@ Documented、@ AliExpress)にあります。

@目標

注釈の使用範囲を説明するために使用されます(つまり、説明された注釈はどこで使用できますか)

例:アノテーションをカスタマイズする
ここに画像の説明を挿入
ElementTypeは列挙型クラスであり、いくつかの簡単な分類を提供し(以下はクラスの部分的なスクリーンショットです)
ここに画像の説明を挿入
、アノテーションを使用します

public class Test01 {
    @MyAnnotation
    public void test() {

    }
}

@MyAnnotationでも、クラスで使用できることが定義されているため、このアノテーションは次のようにクラスでも使用できます。
ここに画像の説明を挿入

@保持

注釈のライフサイクルを説明するために使用される、注釈情報を保存する必要があるレベルを示します(SOURCE <CLASS <  RUNTIME
ここに画像の説明を挿入

@Documented

アノテーションがjavadocに含まれることを説明します
ここに画像の説明を挿入

@遺伝性の

サブクラスが親クラスのアノテーションを継承できることを説明する

ここに画像の説明を挿入

カスタムアノテーション

  • @interfaceカスタムアノテーションを使用する場合、java.lang.annotation.Annotationインターフェースを自動的に継承します
  • 分析:
    • @interfaceは、アノテーションを宣言するために使用されます。形式:public @ interfaceアノテーション名{コンテンツの定義}
    • これらの各メソッドは、実際に構成パラメーターを宣言します
    • メソッドの名前はパラメーターの名前です
    • 戻り値の型はパラメーターの型です(戻り値は基本型、クラス、文字列、列挙型のみにすることができます)
    • パラメータのデフォルト値はデフォルトで宣言できます
    • パラメータメンバーが1つしかない場合、一般的なパラメータ名はvalueです。
    • 注釈要素には値が必要です。注釈要素を定義するときは、空の文字列を使用することがよくあります。デフォルト値は0です。
import java.lang.annotation.*;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {

    // 注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
    @MyAnnotation(age = 18, name = "lisa")
    public void test() {

    }
    
    @DemoAnnotation("注解测试")
    public void test1() {

    }
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    // 注解的参数:参数类型 + 参数名()
    String name() default "";

    int age();

    // 如果默认值为 -1,代表不存在
    int id() default -1;

    String[] schools() default {"大学"};
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface DemoAnnotation {
    String value();
}

反射

静的言語VS動的言語

動的言語

動的言語は、実行時に構造を変更できる一種の言語です。たとえば、新しい関数、オブジェクト、さらにはコードを導入したり、既存の関数を削除したり、その他の構造を変更したりできます。素人の言葉で言えば、コードは実行時の特定の条件に従ってその構造を変更できます。

静的言語

動的言語に対応して、不変のランタイム構造を持つ言語は、Java、C、C ++などの静的言語です

Javaは静的言語ではありませんが、Javaは「準動的言語」と呼ぶことができます。つまり、Javaにはある程度のダイナミクスがあり、リフレクションメカニズムを使用して動的言語と同様の特性を取得できます。Javaの動的な性質により、プログラミングがより柔軟になります

Javaリフレクションメカニズムの概要

    リフレクション(リフレクション)は、Javaが動的言語と見なされるための鍵です。リフレクションメカニズムにより、プログラムは実行中にReflection APIを使用して任意のクラスの内部情報を取得でき、任意の内部プロパティとメソッドを直接操作できます。オブジェクト

Class c = Class.forName("java.lang.String");

クラスがロードされた後、ヒープメモリのメソッド領域にClass型のオブジェクトが生成され(クラスにはClassオブジェクトが1つだけあります)、このオブジェクトにはクラス全体の構造情報が含まれています。このオブジェクトを通してクラスの構造を見ることができます。このオブジェクトは鏡のようなもので、クラスの構造を見ることができるので、鮮やかに呼んでいます。反射

  • 通常の方法:必要な「パッケージクラス」名を導入します-> newでインスタンス化します->インスタンス化されたオブジェクトを取得します
  • リフレクション:オブジェクトをインスタンス化する-> getClass()メソッド->完全な「パッケージクラス」名を取得する

Javaリフレクションメカニズムによって提供される関数

  • 実行時にオブジェクトが属するクラスを決定します
  • 実行時に任意のクラスのオブジェクトを構築します
  • 実行時に任意のクラスのメンバー変数とメソッドを判断します
  • 実行時に一般的な情報を取得する
  • 実行時に任意のオブジェクトのメンバー変数とメソッドを呼び出す
  • 実行時に注釈を処理する
  • 動的プロキシを生成する

Javaリフレクションの長所と短所

利点: オブジェクトの動的な作成とコンパイルを実現でき、優れた柔軟性を示します

短所: パフォーマンスに影響を与えます。リフレクションの使用は基本的に解釈操作です。JVMに何をしたいのか、どのような要件を満たすのかを伝えることができます。このタイプの操作は、同じ操作を直接実行するよりも常に低速です。

リフレクションに関連する主なAPI

  • java.lang.Classはクラスを表します
  • java.lang.reflect.Methodは、クラスのメソッドを表します
  • java.lang.reflect.Fieldは、クラスのメンバー変数を表します
  • java.lang.reflect.Constructorは、クラスのコンストラクターを表します

Classクラスを理解し、Classインスタンスを取得します 

最初にエンティティクラスを定義します

// 定义一个实体类
public class User {

    private int id;

    private String name;

    private int age;

    public User() {
    }

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

メインプログラム

package com.java.demo.reflect;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {
    public static void main(String[] args) throws Exception {
        // 通过反射获取类的 Class 对象
        Class<?> aClass = Class.forName("com.java.demo.reflect.User");
        System.out.println(aClass);
    }
}

出力:

ここに画像の説明を挿入
クラスのメモリにはClassオブジェクトが1つしかありません。クラスが読み込まれると、クラスの構造全体がClassオブジェクトにカプセル化されます。

クラスクラス

次のメソッドはObjectクラスで定義されており、このメソッドはすべてのサブクラスに継承されます

public final native Class<?> getClass();

上記のメソッドの戻り値の型は、JavaリフレクションのソースであるClassクラスです。実際、いわゆるリフレクションは、プログラムの実行結果からもよく理解されています。つまり、クラスはオブジェクトリフレクションを通じて取得できます

クラスごとに、JREは不変のクラスタイプオブジェクトを予約します。クラスオブジェクトには特定の構造が含まれています(クラス/インターフェイス/列挙型/アノテーション/プリミティブ型/ void / [])

  • クラス自体もクラスです
  • クラスオブジェクトは、システムでのみ作成できます
  • ロードされたクラスには、JVMに1つのクラスインスタンスしかありません
  • Classオブジェクトは、JVMにロードされた.classファイルに対応します
  • クラスの各インスタンスは、それが生成されたクラスのインスタンスを記憶します
  • クラスを介して、クラス内のすべてのロードされた構造を完全に取得できます
  • ClassクラスはReflectionのルートです。動的にロードして実行するクラスの場合、最初に応答を取得するClassオブジェクトのみが使用されます。

クラスの一般的なメソッド

メソッド名 機能説明
静的クラスforName(文字列名) 指定されたクラス名nameのClassオブジェクトを返します
オブジェクトnewInstance() デフォルトのコンストラクターを呼び出して、Classオブジェクトのインスタンスを返します
getName() このClassオブジェクトによって表されるエンティティ(クラス、インターフェイス、配列クラス、またはvoid)の名前を返します
クラスgetSuperClass() 現在のClassオブジェクトの親クラスのClassオブジェクトを返します
Class [] getInterfaces() 現在のClassオブジェクトのインターフェースを取得します
ClassLoader getClassLoader() このクラスのクラスローダーを返します
コンストラクター[] getConstructors() いくつかのコンストラクタオブジェクトを含む配列を返します
メソッドgetMethod(文字列名、クラス…T) 仮パラメータタイプがparamTypeであるメソッドオブジェクトを返します
Field [] getDeclaredFields() Fieldオブジェクトの配列を返します

Classクラスのインスタンスを取得します

public class Test01 {
    public static void main(String[] args) throws Exception {
        // 方式一:forName 获得
        Class aClass = Class.forName("com.java.demo.reflect.User");
        System.out.println(aClass);

        // 方式二:通过对象获得
        Class aClass1 = new User().getClass();
        System.out.println(aClass1);

        // 方式三:通过类名.class 获得
        Class<User> aClass2 = User.class;
        System.out.println(aClass2);
    }
}

すべてのタイプのクラスオブジェクト

public class Test01 {
    public static void main(String[] args) {
        Class objectClass = Object.class;            // 类
        Class comparableClass = Comparable.class;    // 接口
        Class aClass = String[].class;               // 一维数组
        Class aClass1 = int[][].class;               // 二维数组
        Class overrideClass = Override.class;        // 注解
        Class elementTypeClass = ElementType.class;  // 枚举
        Class integerClass = Integer.class;          // 基本数据类型
        Class voidClass = void.class;                // void
        Class classClass = Class.class;              // Class

        System.out.println(objectClass);
        System.out.println(comparableClass);
        System.out.println(aClass);
        System.out.println(aClass1);
        System.out.println(overrideClass);
        System.out.println(elementTypeClass);
        System.out.println(integerClass);
        System.out.println(voidClass);
        System.out.println(classClass);
    }
}

出力:
ここに画像の説明を挿入

Javaメモリ分析

ここに画像の説明を挿入

クラスのロードとClassLoaderの理解

  • ロード:クラスファイルのバイトコードコンテンツをメモリにロードし、これらの静的データをメソッド領域のランタイムデータ構造に変換してから、このクラスを表すjava.lang.Classオブジェクトを生成します。

  • リンク:JavaクラスのバイナリコードをJVMの実行状態にマージするプロセス

    • 検証:ロードされたクラス情報がJVM仕様に準拠しており、セキュリティの問題がないことを確認します
    • 準備:クラス変数(静的)にメモリを正式に割り当て、クラス変数のデフォルトの初期値を設定する段階。これらのメモリはメソッド領域に割り当てられます。
    • 解決策:仮想マシンの定数プール内のシンボル参照(定数名)を直接参照(アドレス)に置き換えるプロセス
  • 初期化:

    • クラスコンストラクター<clint>()メソッドを実行するプロセス。クラスコンストラクター<clint>()メソッドは、コンパイル時にクラス内のすべてのクラス変数の割り当てアクションを自動的に収集し、静的コードブロック内のステートメントを組み合わせることによって生成されます。 (クラスコンストラクタこれはクラス情報を構築するためのものであり、このクラスのオブジェクトを構築するためのコンストラクタではありません)
    • クラスを初期化するときに、その親クラスが初期化されていないことがわかった場合は、最初にその親クラスの初期化をトリガーする必要があります
    • 仮想マシンは、クラスの<clint>()メソッドがマルチスレッド環境で正しくロックおよび同期されることを保証します
package com.java.demo.reflect;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {
    public static void main(String[] args) {
        /**
         * 1、加载到内存,会产生一个类对应的 Class 对象
         * 2、链接,链接结束后 m = 0
         * 3、初始化
              <clint>() {
                    System.out.println("A 类静态代码块初始化");
                    m = 300;
                    m = 100;
                }
                m = 100;
         */
        A a = new A();
        System.out.println(A.m);
    }
}

class A {
    static {
        System.out.println("A 类静态代码块初始化");
        m = 300;
    }

    static int m = 100;

    public A() {
        System.out.println("A 类的无参构造函数初始化");
    }
}

出力:
ここに画像の説明を挿入

ランタイムクラスの完全な構造を取得し、オブジェクト実行メソッドを動的に作成します

フィールド、メソッド、コンストラクター、スーパークラス、インターフェイス、アノテーション

  • 実装されたすべてのインターフェース
  • 継承された親クラス
  • 完全なコンストラクター
  • すべての方法
  • すべてのフィールド
  • 注釈

詳細については、以前に書かれたこの記事を読むことができます。これは非常に詳細で 、Javaリフレクションメカニズムの簡単な使用法です。

パフォーマンス比較分析

setAccessible

  • メソッドとフィールド、コンストラクターオブジェクトにはsetAccessible()メソッドがあります
  • setAccessibleは、アクセスセキュリティチェックを有効または無効にするスイッチです。
  • パラメータ値がtrueの場合、リフレクトされたオブジェクトが使用中にJava言語アクセスチェックをキャンセルするように求められます。
    • リフレクションの効率を向上させます。コードでリフレクションを使用する必要があり、ステートメントを頻繁に呼び出す必要がある場合は、trueに設定してください。
    • 他の方法ではアクセスできないプライベートメンバーにもアクセスできるようにする
  • パラメータ値はfalseであり、反映されたオブジェクトがJava言語アクセスチェックを実装する必要があることを示します

 

package com.java.demo.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {

    // 普通方式调用
    public static void test01() {
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行 1 亿次:" + (endTime - startTime) + "ms");
    }

    // 反射方式调用
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行 1 亿次:" + (endTime - startTime) + "ms");
    }

    // 反射方式调用,关闭检测
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("关闭检测方式执行 1 亿次:" + (endTime - startTime) + "ms");
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        test02();
        test03();
    }
}

出力:
ここに画像の説明を挿入

出力結果から、通常の方法が最も速く実行され、2番目は検出方法をオフにすることがわかります。

リフレクションを通じてジェネリックを操作する

  • Javaは、ジェネリック消去のメカニズムを使用してジェネリックを導入します。Javaのジェネリックは、データのセキュリティを確保し、強制的な型変換の問題を回避するために、コンパイラjavacによってのみ使用されます。ただし、コンパイルが完了すると、すべてのジェネリックはジェネリックと互換性があります。関連するタイプは消去されます
  • リフレクションを介してこれらの型を操作するために、JavaはParameterizedType、GenericArrayType、TypeVariable、およびWildcardTypeを追加して、Classクラスにグループ化できないが、元の型と同じ名前を持つ型を表します。
    • ParameterizedType:Collection <String>などのパラメーター化された型を表します
    • GenericArrayType:要素タイプを表す場合のパラメーター化されたタイプまたはタイプ変数配列タイプ
    • TypeVariable:さまざまなタイプの変数のパブリック親インターフェイスです
    • WildcardType:ワイルドカードタイプの式を表します

 

package com.java.demo.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * @author Woo_home
 * @create by 2020/8/1  20:12
 */
public class Test01 {

    public void test01(Map<String, User> map, List<User> list) {
        System.out.println("test01");
    }

    public Map<String, User> test02() {
        System.out.println("test02");
        return null;
    }

    // 通过反射获取泛型
    public static void main(String[] args) throws NoSuchMethodException {
        Method test01 = Test01.class.getMethod("test01", Map.class, List.class);
        // 获取通用参数类型
        Type[] genericParameterTypes = test01.getGenericParameterTypes();
        // 迭代遍历
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("# " + genericParameterType);
            if (genericParameterType instanceof ParameterizedType) {
                // 获取实际参数类型
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        Method test02 = Test01.class.getMethod("test02", null);
        Type genericReturnType = test02.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}

出力:
ここに画像の説明を挿入

リフレクション操作上の注意

  • getAnnotations
  • getAnnotation

最初に2つの注釈を定義し、後で使用します

// 类名注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tables {
    String value();
}

// 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fields {
    String columnName();
    String type();
    int length();
}

次に、エンティティクラスで定義したアノテーションを使用します(JPAに少し似ていませんか?)

@Tables("db_student")
public class Student {

    @Fields(columnName = "db_id", type = "int", length = 10)
    private int id;

    @Fields(columnName = "db_age", type = "int", length = 10)
    private int age;

    @Fields(columnName = "db_name", type = "varchar", length = 3)
    private String name;

    public Student() {
    }

    public Student(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

次に、リフレクトして注釈情報を取得します

public class Test01 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.java.demo.reflect.Student");

        // 通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 获得注解的 value 值
        Tables tables = (Tables) c1.getAnnotation(Tables.class);
        String value = tables.value();
        System.out.println(value);

        // 获得类指定的注解
        Field field = c1.getDeclaredField("name");
        Fields annotation = field.getAnnotation(Fields.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

出力:
ここに画像の説明を挿入

展開

シナリオとカスタムアノテーションの実装

    ログイン、パーミッションインターセプト、ログ処理、およびSpring、Hibernate、JunitなどのさまざまなJavaフレームワーク。アノテーションに関しては、リフレクションを無視することはできません。Javaカスタムアノテーションは、実行時にリフレクションを通じてアノテーションを取得します。

    たとえば、実際の開発では、特定のメソッドの呼び出しログを取得したい場合、AOP(動的プロキシメカニズム)を介してメソッドにアスペクトを追加し、リフレクションを介してメソッドに含まれるアノテーションを取得できます。含まれていると、ログレコードが実行されます。

おすすめ

転載: blog.csdn.net/qq_32445015/article/details/108833149