1. 序章

Javaアノテーションは、メタデータ情報をソースコードに追加するためのメカニズムです。 これらは、JDK5で追加されたJavaの強力な部分です。 注釈は、XML記述子とマーカーインターフェースの使用に代わるものを提供します。

それらをパッケージ、クラス、インターフェース、メソッド、およびフィールドにアタッチすることはできますが、アノテーション自体はプログラムの実行に影響を与えません。

このチュートリアルでは、カスタム注釈を作成して処理する方法に焦点を当てます。 アノテーションの詳細については、アノテーションの基本に関する記事をご覧ください。

2. カスタム注釈の作成

オブジェクトをJSON文字列にシリアル化することを目的として、3つのカスタムアノテーションを作成します。

クラスレベルで最初のものを使用して、オブジェクトをシリアル化できることをコンパイラに示します。 次に、2番目のフィールドをJSON文字列に含めるフィールドに適用します。

最後に、メソッドレベルで3番目のアノテーションを使用して、オブジェクトの初期化に使用するメソッドを指定します。

2.1. クラスレベルのアノテーションの例

カスタムアノテーションを作成するための最初のステップは、 @interfaceキーワードを使用してそれを宣言することです:

public @interface JsonSerializable {
}

次のステップは、メタアノテーションを追加して、カスタムアノテーションのスコープとターゲットを指定することです。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface JsonSerializable {
}

ご覧のとおり、最初のアノテーションには実行時の可視性があり、タイプ(クラス)に適用できます。 さらに、メソッドがないため、JSONにシリアル化できるクラスをマークするための単純なマーカーとして機能します。

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

同様に、生成されたJSONに含めるフィールドをマークする2番目のアノテーションを作成します。

@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のReflectionAPIを使用してそれらを利用する方法を見ていきます。

最初のステップは、オブジェクトが 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);
        }
    }
 }

methodsetAccessible 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 + "}";
}

ここでも、fieldsetAccessible tru e を使用しました。これは、 Person オブジェクトのフィールドはプライベートです。

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");
    ObjectToJsonConverter serializer = new ObjectToJsonConverter(); 
    String jsonString = serializer.convertToJson(person);
    assertEquals(
      "{\"personAge\":\"34\",\"firstName\":\"Soufiane\",\"lastName\":\"Cheouati\"}",
      jsonString);
}

4. 結論

この記事では、さまざまなタイプのカスタムアノテーションを作成する方法を学びました。 次に、それらを使用してオブジェクトを装飾する方法について説明しました。 最後に、JavaのReflectionAPIを使用してそれらを処理する方法を確認しました。

いつものように、完全なコードはGitHubで入手できます。