Java で自前のアノテーションを処理する

## 実行時に自前のアノテーションを処理する

### サンプルコード

自前のアノテーションクラス。

```java
package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 実行時にアノテーションを使う
@Retention(RetentionPolicy.RUNTIME)
// アノテーションを型(クラスなど)、フィールド、メソッドに適用する
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
// 自前のアノテーションを定義する
public @interface MyAnnotation {
  String value() default "";
  String myvalue() default "";
}
```

アノテーションを使うクラス。

```java
package com.example;

@MyAnnotation("サンプルクラス")
public class Sample {

  @MyAnnotation("サンプルフィールド")
  public String sampleField = "サンプルフィールドの値";

  @MyAnnotation(value = "サンプルメソッド", myvalue = "サンプルメソッドのmyvalue")
  public void sampleMethod() {
    System.out.println("サンプルメソッドを実行");
  }
}
```

メインクラス。

```
package com.example;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {

  public static void main(String[] args) {

    // サンプルクラスを普通に使う
    Sample sample = new Sample();
    System.out.println(sample.sampleField);
    sample.sampleMethod();

    // サンプルクラスのアノテーション情報を出力する
    print(Sample.class);
  }

  private static void print(Class cls) {
    print(cls.getAnnotations());
    Field[] fields = cls.getFields();
    for (Field f : fields) {
      print(f.getAnnotations());
    }
    Method[] methods = cls.getMethods();
    for (Method m : methods) {
      print(m.getAnnotations());
    }
  }

  private static void print(Annotation[] annotations) {
    for (Annotation a : annotations) {
      // 今回取得したいアノテーションは com.example パッケージに属するものだけ
      if (a.annotationType().getPackageName().equals("com.example")) {
        System.out.println(a);
      }
    }
  }
}
```

実行結果。

```
サンプルフィールドの値
サンプルメソッドを実行
@com.example.MyAnnotation(value="サンプルクラス", myvalue="")
@com.example.MyAnnotation(value="サンプルフィールド", myvalue="")
@com.example.MyAnnotation(value="サンプルメソッド", myvalue="サンプルメソッドのmyvalue")
```

今回は Sample.class というクラスを指定してアノテーションを取得したが、 ClassLoader 等を利用して .class や .jar ファイルの場所を取得しすべてのクラスを探索することも可能 (Spring Framework のコンポーネントスキャンのように)。

### 参考資料

- [The Java® Language Specification - Chapter 9\. Interfaces 9\.6\. Annotation Types](https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.6)
- [ClassLoader \(Java SE 11 & JDK 11 \)](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/ClassLoader.html)
- [Java 独自のアノテーションを作成して値を取得するサンプル \| ITSakura](https://itsakura.com/java-annotation-make)
- [Javaでアノテーションを作ってみた。 \- Qiita](https://qiita.com/aky100200/items/b536c8d1f4b31377c8f9)
- [Javaで超簡易Webフレームワークを作ってみよう \- Qiita](https://qiita.com/hatimiti/items/79b6bd1003fe6bfec2f3)

## コンパイル時に自前のアノテーションを処理する

### Lombok の例

Lombok は javac などでコンパイルする際に annotation processor を実行し、クラスにメソッドなどを追加している。

[Lombok Execution Path](https://projectlombok.org/contributing/lombok-execution-path)

> Lombok is on the classpath, and javac will load every META-INF/services/javax.annotation.processing.Processor file on the classpath it can find, reading each line and loading that class, then executing it as an annotation processor. lombok.jar has this file, it lists lombok.launch.AnnotationProcessorHider$AnnotationProcessor as entry.

[Project Lombok: Creating Custom Transformations](http://notatube.blogspot.com/2010/12/project-lombok-creating-custom.html)

> Project Lombok runs as an annotation processor. The annotation processor acts as a dispatcher that delegates to Lombok annotation handlers (this is what we're going to create).

[誰も教えてくれなかった Lombok のこと \(ja\) \- notepad](https://www.coppermine.jp/docs/notepad/2014/01/lombok.html)

> コンパイラがソースファイルを読み込んでAST（Abstract Sintax Tree; 抽象構文木）という内部形式に変換して、本来ならそのままコンパイルを行うべきところを、LombokがASTを横取りして定型コードを付け加えた上で、変更後のASTでコンパイルを行います。

[java \- How does lombok work? \- Stack Overflow](https://stackoverflow.com/questions/6107197/how-does-lombok-work)

> Note that lombok.jar contains a file named /META-INF/services/javax.annotation.processing.Processor. When javac sees this file in a compilation classpath, it runs annotation processors defined there during compilation.

Lombok の Ant 用ビルド設定ファイルを見ると META-INF/services/javax.annotation.processing.Processor を出力しているのがわかる。

[lombok/build\.xml at master · rzwitserloot/lombok · GitHub](https://github.com/rzwitserloot/lombok/blob/master/build.xml)

> \<echo file="build/lombok/META-INF/services/javax.annotation.processing.Processor"\>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
lombok.launch.AnnotationProcessorHider$ClaimingProcessor\</echo\>

Lombok には AnnotationProcessor クラスが用意されている。

[lombok/AnnotationProcessor\.java at master · rzwitserloot/lombok · GitHub](https://github.com/rzwitserloot/lombok/blob/master/src/core/lombok/core/AnnotationProcessor.java)

[Project Lombok によるカスタム AST 変換](https://www.ibm.com/developerworks/jp/java/library/j-lombok/)

> Lombok は、よく使用されている 2 つの Java コンパイラー、javac と ECJ (Eclipse Compiler for Java) をサポートします。この 2 つのコンパイラーは同じような出力を生成しますが、それぞれの実装はまったく異なります。そのため、Lombok にはアノテーション・ハンドラー (Lombok に組み込まれる、コード生成ロジックが含まれるコード) のセットが 2 つ付属しています (それぞれのコンパイラー用に 1 つのセット)。

Lombok の java 用のハンドラーパッケージは lombok.javac.handlers となっている。

[lombok/src/core/lombok/javac/handlers at master · rzwitserloot/lombok · GitHub](https://github.com/rzwitserloot/lombok/tree/master/src/core/lombok/javac/handlers)

### 参考資料

- [The Java Community Process\(SM\) Program \- JSRs: Java Specification Requests \- detail JSR 269: Pluggable Annotation Processing API](https://www.jcp.org/en/jsr/detail?id=269)
- [Pluggable Annotation Processing API 使い方メモ \- Qiita](https://qiita.com/opengl-8080/items/beda51fe4f23750c33e9)