MyBatis ソース コードを読むための予備知識 - 例外、シリアル化、リフレクション、アノテーション

1. 例外

异常これは、プログラムの実行中に予期せぬ事態が発生したことを意味しますが、この異常な状況を説明するために、Java の標準ライブラリにはいくつかの一般例外が設けられています。Throwable は親クラスであり、Error クラス、Exception クラス、およびそのサブクラスを派生します。

  • エラーは、
    JVM 自体の例外を表します。このタイプの例外が発生した場合、プログラムでは修正できないため、JVM をできるだけ早く停止する必要があります。

  • 例外は、
    プログラムの実行中に予期しない何かが発生したことを表します。これら以外の状況は、Java の例外処理メカニズムによって処理できます。例外には 2 つの派生サブクラスがあります

  1. RuntimeExceptionなどの例外
    は実際にはプログラミング エラーであり、範囲外の添字やゼロによる除算の例外など、プログラムを修正することで回避できます。
  2. 非 RuntimeExceptionなどの例外は
    通常、外部要因によって発生し、避けられません。例:IO異常、ネットワーク異常

複雑なクラス関係に遭遇した場合、UML クラス図を描くことは、以前の関係を明確にするための非常に良い方法です。
ここに画像の説明を挿入します
上図に示されている例外のうち、Error と RuntimeException は未チェック例外と呼ばれます。これら 2 種類の例外を強制する必要はありません。 . 調べます。これら 2 種類の例外を除いた他の例外は、必須検出例外と呼ばれます。コードを記述するときは、このタイプの例外を処理する必要があります。

スロー可能なオブジェクト。その主なメンバー変数は、detailMessage と Cause です。

  • DetailMessage は、例外の詳細を保存するために使用される文字列です。
  • Cause は、例外の原因を格納するために使用される別の Throwable オブジェクトです。これは、例外が発生すると、通常例外を発生させる上位プログラムでも例外が発生し、 と呼ばれる一連の例外が発生するためです异常链例外の原因属性は、例外を発生させた下位例外を指すことができるため、例外チェーン全体を節約できます。ここに画像の説明を挿入します

2.連載

シリアル化: オブジェクトをバイト シーケンスに変換するプロセスです。
逆シリアル化: バイト シーケンスをオブジェクトに復元するプロセスです。

オブジェクトのシリアル化には通常、次の 2 つの目的があります。

  1. オブジェクトをバイトにシリアル化し、記憶媒体に保存します。オブジェクトを永続化するためのものです
  2. オブジェクトをバイトにシリアル化し、ネットワーク経由で送信します。オブジェクトを転送するためのものです

dubbo などの RPC フレームワークを使用する場合、ネットワーク間で送信するにはオブジェクトをシリアル化する必要があるため、エンティティ オブジェクトは Serializable インターフェイスを実装する必要があります。

Java では、クラスがシリアル化可能であることを示すには、Serializable インターフェイスまたは Externalizable インターフェイスに基づいている必要があります。
Serializable インターフェイスの使用は非常に簡単で、シリアル化する必要があるクラスに Serializable インターフェイスを実装するだけで済みます。メソッドを追加する必要はありません。

2.1 シリアル化時のバージョンの問題

シリアル化および逆シリアル化のプロセス中に、バージョンの問題に直面する必要があります。
たとえば、User クラス オブジェクト user をハードディスクに永続化します。次に、User クラスに新しい属性が追加されますが、この時点で、ハードディスクに保存されているユーザー オブジェクトを新しい User クラス オブジェクトに逆シリアル化できますか?

この質問に対する答えには、Serializable インターフェイスの SerialVersionUID フィールドが関係します。SerialVersionUID フィールドはシリアル化バージョン管理フィールドと呼ばれ、Serializable インターフェイスを実装するクラスでよく見られます。

public class User implements Serializable {
    
    
    private static final long serialVersionUID = 1L;
}

逆シリアル化プロセス中に、オブジェクト バイト シーケンス内の SerialVersionUID が現在のクラスの値と異なる場合、逆シリアル化は失敗します。それ以外の場合は成功します。
SerialVersionUID 属性がクラスに対して明示的に定義されていない場合は、自動的に生成されます。自動的に生成されるシリアル化されたバージョン管理フィールドは、クラス名、クラスとプロパティの修飾子、インターフェイスとインターフェイスの順序、プロパティ、コンストラクターなどに関連しています。これらのいずれかを変更すると、serialVersionUID が変更されます。

上記の質問については 1 回です。ユーザー オブジェクトを新しい User クラス オブジェクトに復元できるかどうかは、ケースバイケースで議論する必要があります。

  1. 古いUserクラスと新しいUserクラスの両方にserialVersionUIDフィールドがあり、その値が同じである場合。新しい User クラス オブジェクトに復元できます。新しい属性が新しい User クラスに追加される場合、その値は null になります
  2. 古いクラスと新しいクラスのserialVersionUIDが矛盾している場合、またはserialVersionUID属性値が明示的に設定されていない場合。システムが自動的にserialVersionUIDを生成するためです。逆シリアル化は失敗します。例外が報告されますInvalidClassException

通常、serialVersionUID を使用する場合は、Serializable インターフェイスを実装するクラスに対して明示的に宣言されます。これにより、次のことが行われます。

  • クラスのバージョン間でシリアル化と逆シリアル化の互換性が必要な場合は、serialVersionUID 値を変更しないでください。
  • クラスのバージョン間でシリアル化と逆シリアル化に互換性がないようにする場合は、serialVersionUID 値が変更されていることを確認してください。

3.反省

Java のリフレクション メカニズムは、プログラムの実行状態で、任意のクラスのオブジェクトを構築でき、任意のオブジェクトが属するクラスを理解でき、任意のクラスのメンバー変数とメソッドを理解でき、任意のメソッドを呼び出すことができることを意味します。オブジェクトのプロパティとメソッド。この動的にプログラム情報を取得し、動的にオブジェクトを呼び出す機能をJava言語のリフレクション機構と呼びます。リフレクションは動的言語の鍵とみなされます。--百度エントリーより

  1. リフレクション メカニズムにより、プログラムの柔軟性と拡張性が大幅に向上し、モジュールの結合が減少し、プログラム自体の適応性が向上します。
  2. リフレクション メカニズムを使用すると、ターゲット クラスを事前にハードコーディングしなくても、プログラムで任意のクラスのオブジェクトを作成および制御できます。
  3. リフレクション メカニズムを使用すると、実行時にクラスのオブジェクトを構築し、クラスのメンバー変数とメソッドを決定し、オブジェクトのメソッドを呼び出すことができます。
  4. リフレクション メカニズムはフレームワーク テクノロジを構築するための基礎であり、リフレクションを使用すると、フレームワークにコードを書き込む必要がなくなります。

リフレクションは上記の特性を備えているからこそ、動的にオブジェクトをコンパイルして作成することができ、プログラミング言語の柔軟性を大いに刺激し、ポリモーフィックな特性を強化し、オブジェクト指向プログラミングの抽象化能力をさらに高めることができるため、多くの人々に好まれています。プログラミングコミュニティに感謝します。

リフレクション メカニズムは優れた柔軟性と利便性をもたらしますが、デメリットもあります。リフレクション メカニズムは非常に強力ですが、悪用することはできません。リフレクションを使用せずに実行できる場合は、次の理由からリフレクションを使用しないようにしてください。

  1. パフォーマンスの問題。Java リフレクション メカニズムにはいくつかの動的タイプが含まれているため、Java 仮想マシンはこれらの動的コードを最適化できません。したがって、リフレクション操作は通常の操作よりも効率が大幅に低くなります。パフォーマンスが重要なプログラムや頻繁に実行されるコードではリフレクションを使用しないようにする必要があります。さらに、リフレクションをどのように使用するかによってパフォーマンスのレベルが決まります。プログラムのあまり実行されない部分であれば、パフォーマンスは問題になりません。
  2. セキュリティ制限。通常、リフレクションを使用するには、セキュリティ制限なしでプログラムを実行する必要があります。プログラムにセキュリティ要件がある場合は、リフレクションを使用しないことが最善です。
  3. プログラムの堅牢性。リフレクションを使用すると、通常は許可されない操作をコードで実行できるため、リフレクションを使用すると予期しない結果が生じる可能性があります。リフレクティブコードは Java プログラムの構造の抽象化を破壊するため、プログラムが実行されるプラットフォームが変わると、抽象的な論理構造を認識できないため、コードの効果が以前とは異なります。

リフレクションは、すべてのフレームワークに必須の機能です。したがって、フレームを読み取るには反射を理解することが重要です。

3.1 反映例

要件: 2 つのクラスの属性が一貫しているかどうかを比較すること。

  1. エンティティクラス
public class Student {
    
    
    private Integer id;
    private String name;
}
public class Car {
    
    
    private String name;
    private String type;
}
  1. ツール クラスは、2 つの Student オブジェクトのプロパティが一貫しているかどうかを比較します。
public class StudentUtils {
    
    
    /**
     * 比较两个student对象的不同属性
     */
    public static Map<String,String> diffStudent(Student oldStu,Student newStu){
    
    
        Map<String,String> diffMap=new HashMap<>();
        if(oldStu.getId()!=null && !oldStu.getId().equals(oldStu.getId())){
    
    
            diffMap.put("id","from "+oldStu.getId()+" to " + newStu.getId());
        }
        if(oldStu.getName()!=null && !oldStu.getName().equals(oldStu.getName())){
    
    
            diffMap.put("id","from "+oldStu.getName()+" to " + newStu.getName());
        }
        return diffMap;
    }
}

コーディング時に Student オブジェクトがどのようなプロパティを持っているかがわかっているので、次のように記述できます。では、他のオブジェクトの違いを比較したい場合はどうすればよいでしょうか?
ここで直面する問題は 2 つあります。

  • 比較する必要があるオブジェクトの具体的なタイプがわからない
  • 比較オブジェクトの具体的なプロパティがわからない

上記の 2 つの問題を解決するには、パラメータが渡された後に、受信オブジェクトの型とそれに含まれるプロパティとメソッドを直接決定する必要があります。反省することはそれを解決するのに役立ちます。
Javaのリフレクション機構は主に以下の機能を提供します。

  • 実行時にオブジェクトが属するクラスを決定します。
  • 実行時に任意のクラスのオブジェクトを構築します。
  • 実行時にオブジェクトのメンバー変数を変更します。
  • 実行時に任意のオブジェクトのメソッドを呼び出します。
    public static Map<String,String> diffObject(Object oldObj,Object newObj) throws IllegalAccessException {
    
    
        Map<String,String> diffMap=new HashMap<>();
        Class oldStuClass = oldObj.getClass();
        Class newStuClass = newObj.getClass();
        //判断是否是同一类
        if(!oldStuClass.equals(newStuClass)){
    
    
            return diffMap;
        }
        //获取对象的所有属性
        Field[] declaredFields = oldStuClass.getDeclaredFields();
        //对属性逐一比对
        for (Field field : declaredFields) {
    
    
            field.setAccessible(true);
            Object oldValue = field.get(oldObj);
            Object newValue = field.get(newObj);
            if(oldValue!=null && !oldValue.equals(newValue)){
    
    
                diffMap.put(field.getName(),"from "+oldValue+" to " + newValue);
            }
        }
        return diffMap;
    }

    public static void main(String[] args) throws IllegalAccessException {
    
    
        Student oldStu = new Student(1, "huazige");
        Student newStu = new Student(2, "mybatis");
        Map<String, String> stringStringMap = diffObject(oldStu, newStu);
        System.out.println(JSON.toJSONString(stringStringMap));

        Car oldCar = new Car("比亚迪", "电车");
        Car newCar = new Car("红旗", "油车");
        Map<String, String> carMap = diffObject(oldCar, newCar);
        System.out.println(JSON.toJSONString(carMap));
    }

操作結果:

{
    
    "name":"from huazige to mybatis","id":"from 1 to 2"}
{
    
    "name":"from 比亚迪 to 红旗","type":"from 电车 to 油车"}

diffObject メソッドは、student と Car という 2 つの異なるクラスのオブジェクト属性の比較を完了し、パラメーターの結合を減らして関数の汎用性を高めます。

3.2typeインターフェースとそのサブクラス

Type インターフェイスはメソッドを 1 つだけ定義します。

   default String getTypeName() {
    
    
        return toString();
    }

型インターフェイスとサブクラスのクラス図:
ここに画像の説明を挿入します

  • Class クラス: 実行中の Java プログラム内のクラスとインターフェイスを表します。列挙型 (クラスに属する) と注釈 (インターフェイスに属する) も Class クラスのサブクラスです。
  • WildcardType インターフェイス: ワイルドカード式を表します。たとえば、「?」、「?extends Number」、および「?super Integer」はすべてワイルドカード式です。
  • TypeVariable インターフェイス: 型変数の親インターフェイスです。たとえば、「Map<K,V>」の「K」と「V」は型変数です。
  • ParameterizedType インターフェイス: パラメーター化された型を表します。たとえば、「Collection <String>」はパラメータ化された型です。
  • GenericArrayType インターフェイス: ParameterizedType 要素または TypeVariable 要素を含むリストを表します。

4.注釈

Java アノテーションとも呼ばれる Java アノテーションは、JDK5 で導入された新機能、アノテーション (メタデータとも呼ばれます) です。
Java アノテーションは、あらゆる情報やメタデータをプログラム要素 (クラス、メソッド、メンバー変数など) に関連付ける安全なアノテーションのようなメカニズムを提供します。
Java アノテーションは、コードに付加されたメタ情報であり、一部のツールによってコンパイルおよび実行時に解析および使用され、説明および構成の機能を果たします。

4.1 Java アノテーション アプリケーション

  1. ドキュメントの生成は、Java によって提供される最も一般的かつ最も初期のアノテーションです。
  2. コンパイル時に形式チェックを実行します。たとえば、メソッドの前に @Override が配置されている場合、メソッドがスーパー クラス メソッドをカバーしていない場合は、コンパイル時にチェックできます。
  3. コードの依存関係を追跡し、代替構成ファイル機能を実装します。より一般的なのは、Spring 2.5 以降のアノテーションベースの構成であり、構成を減らすのに役立ちます。
  4. リフレクションのクラス、メソッド、フィールドなどの関数には、アノテーションに関連するインターフェースが多数あり、リフレクションでアノテーションを解析して利用することができます。

4.2 Java アノテーションの分類

  1. Java 独自の標準アノテーション
    には、@Override、@Deprecated、@SuppressWarnings、@FunctionalInterface があり、コンパイラはこれらのアノテーションを使用した後にチェックします。
  • @Override: その機能は、スーパー クラスのメソッドをオーバーライドするメソッドをマークすることです。マークされたメソッドが実際にスーパー クラスのメソッドをオーバーライドしない場合、コンパイラはエラー警告を発行します。
  • @Deprecated: その機能は、使用されなくなったメソッドにアノテーションを追加することです。プログラマがこれらのメソッドを使用すると、コンパイル時にプロンプ​​ト メッセージが表示されます。
  • @SuppressWarnings: そのパラメータは次のとおりです:
    非推奨: 廃止されたクラスまたはメソッドが
    チェックなしで使用されたときの警告: チェックされていない変換が実行されたときの警告
    フォールスルー: スイッチ ブロックがブレーク パスなしで次の状況に直接つながったときの警告
    : 存在する場合の警告クラスパス、ソースファイルパスなどに存在しないパスです
    。serial : 直列化可能なクラスでserialVersionUID定義が欠落している場合の警告
    finally : 任意のfinally句が正常に完了しない場合の警告
    all : 上記についてすべての状況での警告
  • @FunctionalInterface: Java 8 では、関数型インターフェイスに新しいアノテーション @FunctionalInterface が導入されており、主にコンパイル レベルのエラー チェックに使用されます。このアノテーションを使用すると、作成したインターフェイスが関数型インターフェイスの定義に準拠していない場合、コンパイラはエラーを報告します
  1. メタアノテーション
    メタアノテーションは、@Retention、@Target、@Inherited、@Documented、@Repeatable などのアノテーションを定義するために使用されるアノテーションです。
    メタアノテーションも Java に付属する標準アノテーションですが、非常に特殊なアノテーションを変更するために使用されます。
  • @Retention は、ソース コード (SOURCE)、クラス ファイル (CLASS)、またはランタイム (RUNTIME) のどのレベルでアノテーションを使用できるかを定義するために使用されます。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    
    
    RetentionPolicy value();
}
public enum RetentionPolicy {
    
    
	//此注解类型的信息只会记录在源文件中,编译时将被编译器丢弃,也就是说不会保存在编译好的类信息中
    SOURCE,
    //编译器将注解记录在类文件中,但不会加载到JVM中。如果一个注解声明没指定范围,则系统默认就是CLASS
    CLASS,
    //注解信息会保留在源文件、类文件中,在执行的时也加载到Java的JVM中,因此可以反射性的读取。
    RUNTIME
}
  • @Documented: ドキュメント情報を生成するときに注釈を保持し、クラスに補助的な説明を提供します。
  • @Target: アノテーションの使用範囲を記述するために使用されます (つまり、記述されたアノテーションが使用できる場所)。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    
    
    ElementType[] value();
}
public enum ElementType {
    
    
    /** Class, interface (including annotation interface), enum, or record
     * declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation interface declaration (Formerly known as an annotation type.) */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     * @since 1.8
     */
    TYPE_USE,

    /**
     * Module declaration.
     * 
     * @since 9
     */
    MODULE,

    /**
     * Record component
     *
     * @jls 8.10.3 Record Members
     * @jls 9.7.4 Where Annotations May Appear
     *
     * @since 16
     */
    RECORD_COMPONENT;
}
  • @Inherited: サブクラスが親クラスからこのアノテーションを継承できることを示します
  • @Repeatable は、アノテーションが繰り返し使用できることを示します。
  1. カスタム注釈
    ユーザーは、自分のニーズに応じて注釈を定義できます。

おすすめ

転載: blog.csdn.net/d495435207/article/details/130373825