Javaでのカスタムアノテーションの作成

1. 前書き

Javaアノテーションは、ソースコードにメタデータ情報を追加するためのメカニズムです。 これらはJavaの強力な部分であり、JDK5で追加されました。 注釈は、XML記述子とマーカーインターフェイスの使用に代わるものを提供します。
それらをパッケージ、クラス、インターフェース、メソッド、およびフィールドに添付できますが、注釈自体はプログラムの実行には影響しません。
このチュートリアルでは、カスタムアノテーションの作成方法とその処理方法に焦点を当てます。 注釈の詳細については、https://www.baeldung.com/java-default-annotations [注釈の基本に関する記事]をご覧ください。

参考文献:

Javaの抽象クラス

Javaのクラス階層の一部として抽象クラスを使用する方法とタイミングを学びます。
link:/java-abstract-class [続きを読む]→

Javaのマーカーインターフェイス

Javaマーカーインターフェースと、それらが典型的なインターフェースおよび注釈と比較する方法について学習します。
link:/java-marker-interfaces [続きを読む]→

2. カスタム注釈の作成

オブジェクトをJSON文字列にシリアル化する目的で、3つのカスタムアノテーションを作成します。
クラスレベルで最初のものを使用して、オブジェクトをシリアル化できることをコンパイラに示します。 次に、2番目のフィールドをJSON文字列に含めるフィールドに適用します。
最後に、メソッドレベルで3番目の注釈を使用して、オブジェクトの初期化に使用するメソッドを指定します。

2.1. クラスレベルの注釈の例

カスタムアノテーションを作成するための最初の手順は、* _ @ interface_キーワードを使用して宣言することです*
public @interface JsonSerializable {
}
次のステップは、*メタアノテーションを追加し、カスタムアノテーションのスコープとターゲット*を指定することです:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface JsonSerializable {
}
ご覧のとおり、最初の注釈は*ランタイムの可視性を持ち、型(クラス)*に適用できます。 さらに、メソッドがないため、JSONにシリアル化できるクラスをマークする単純なマーカーとして機能します。

2.2. フィールドレベルの注釈の例

同様に、2番目の注釈を作成して、生成されたJSONに含めるフィールドをマークします。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonElement {
    public String key() default "";
}
注釈は、名前が「key」で空の文字列をデフォルト値として1つのStringパラメータを宣言します。
メソッドを使用してカスタムアノテーションを作成する場合、これらの*メソッドにはパラメーターがなく、例外をスローできない*ことに注意する必要があります。 また、*戻り値の型は、プリミティブ、文字列、クラス、列挙、注釈、およびこれらの型の配列に制限されています***デフォルト値はnull *にできません。

2.3. メソッドレベルの注釈の例

オブジェクトをJSON文字列にシリアル化する前に、何らかのメソッドを実行してオブジェクトを初期化することを想像してみましょう。 そのため、このメソッドをマークするための注釈を作成します。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Init {
}
クラスのメソッドに適用できる、実行時の可視​​性を備えたパブリックアノテーションを宣言しました。

2.4. 注釈を適用する

それでは、カスタムアノテーションの使用方法を見てみましょう。 たとえば、JSON文字列にシリアル化する_Person_型のオブジェクトがあるとします。 このタイプには、姓と名の最初の文字を大文字にするメソッドがあります。 オブジェクトをシリアル化する前に、このメソッドを呼び出します。
@JsonSerializable
public class Person {

    @JsonElement
    private String firstName;

    @JsonElement
    private String lastName;

    @JsonElement(key = "personAge")
    private String age;

    private String address;

    @Init
    private void initNames() {
        this.firstName = this.firstName.substring(0, 1).toUpperCase()
          + this.firstName.substring(1);
        this.lastName = this.lastName.substring(0, 1).toUpperCase()
          + this.lastName.substring(1);
    }

    // Standard getters and setters
}
カスタムアノテーションを使用することで、_Person_オブジェクトをJSON文字列にシリアル化できることを示しています。 さらに、出力には、そのオブジェクトの_firstName _、_ lastName_、および_age_フィールドのみが含まれている必要があります。 さらに、シリアル化の前に_initNames()_メソッドを呼び出す必要があります。
_ @ JsonElement_アノテーションの_key_パラメーターを「personAge」に設定することにより、この名前をJSON出力のフィールドの識別子として使用することを示しています。
デモンストレーションのために、_initNames()_をプライベートにしたため、オブジェクトを手動で呼び出して初期化することはできず、コンストラクターもそれを使用していません。

3. 注釈の処理

これまで、カスタムアノテーションを作成する方法と、それらを使用して_Person_クラスを装飾する方法を見てきました。 次に、* JavaのReflection APIを使用してそれらを活用する方法を見ていきます。*
最初のステップは、オブジェクトが_null_であるかどうか、およびそのタイプに_ @ JsonSerializable_注釈があるかどうかをチェックすることです。
private void checkIfSerializable(Object object) {
    if (Objects.isNull(object)) {
        throw new JsonSerializationException("The object to serialize is null");
    }

    Class<?> clazz = object.getClass();
    if (!clazz.isAnnotationPresent(JsonSerializable.class)) {
        throw new JsonSerializationException("The class "
          + clazz.getSimpleName()
          + " is not annotated with JsonSerializable");
    }
}
次に、@ Initアノテーションを持つメソッドを探し、それを実行してオブジェクトのフィールドを初期化します。
private void initializeObject(Object object) throws Exception {
    Class<?> clazz = object.getClass();
    for (Method method : clazz.getDeclaredMethods()) {
        if (method.isAnnotationPresent(Init.class)) {
            method.setAccessible(true);
            method.invoke(object);
        }
    }
 }
_method _._ setAccessible _(_ true)_の呼び出しにより、プライベート_initNames()_ method __.__を実行できます。
初期化後、オブジェクトのフィールドを反復処理し、JSON要素のキーと値を取得して、マップに配置します。 次に、マップからJSON文字列を作成します。
private String getJsonString(Object object) throws Exception {
    Class<?> clazz = object.getClass();
    Map<String, String> jsonElementsMap = new HashMap<>();
    for (Field field : clazz.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(JsonElement.class)) {
            jsonElementsMap.put(getKey(field), (String) field.get(object));
        }
    }

    String jsonString = jsonElementsMap.entrySet()
        .stream()
        .map(entry -> "\"" + entry.getKey() + "\":\""
          + entry.getValue() + "\"")
        .collect(Collectors.joining(","));
    return "{" + jsonString + "}";
}
ここでも、_Person_オブジェクトのフィールドはプライベートであるため、because_field _._ setAccessible _(_ true)_を使用しました。
JSONシリアライザークラスは、上記のすべての手順を組み合わせます。
public class ObjectToJsonConverter {
    public String convertToJson(Object object) throws JsonSerializationException {
        try {
            checkIfSerializable(object);
            initializeObject(object);
            return getJsonString(object);
        } catch (Exception e) {
            throw new JsonSerializationException(e.getMessage());
        }
    }
}
最後に、ユニットテストを実行して、カスタムアノテーションで定義されたとおりにオブジェクトがシリアル化されたことを検証します。
@Test
public void givenObjectSerializedThenTrueReturned() throws JsonSerializationException {
    Person person = new Person("soufiane", "cheouati", "34");
    JsonSerializer serializer = new JsonSerializer();
    String jsonString = serializer.serialize(person);
    assertEquals(
      "{\"personAge\":\"34\",\"firstName\":\"Soufiane\",\"lastName\":\"Cheouati\"}",
      jsonString);
}

4. 結論

この記事では、さまざまなタイプのカスタム注釈を作成する方法を説明しました。 次に、それらを使用してオブジェクトを装飾する方法について説明しました。 最後に、JavaのReflection APIを使用してそれらを処理する方法を検討しました。
いつものように、完全なコードはhttps://github.com/eugenp/tutorials/tree/master/algorithms-miscellaneous-3[GitHub]で入手できます。