apt とは何か
apt
はコマンド行ユーティリティで、注釈処理ツールです。検証が行われている一連の指定されたソースファイルに存在する注釈に基づいて、「注釈プロセッサ」 を検出し実行します。注釈プロセッサは一連の関連する API とサポートするインフラストラクチャにより、注釈 (JSR 175) プログラムを処理します。aptに関連する API により、プログラム構造を、ビルド時に、ソースベースまた読み取り専用で見ることが可能になります。これらの関連する API は、汎用 (JSR 14) プログラムの追加後に、JavaTM プログラム言語の型システムを正常にモデル化するために設計されました。apt
は、最初に新しいソースコードと他のファイルを作成する注釈プロセッサを実行します。次に、元のファイルと作成されたソースファイルのコンパイルを行います。これにより開発サイクルは簡素化されます。
apt を使用する理由
注釈の使用が必要となるのは、新しい派生ファイル (ソースファイル、クラスファイル、配備記述子など) の作成に使用される情報を保持するベースファイルに注釈があり、ベースファイルとその注釈に論理的に一貫性がある場合です。つまり、一連のファイルすべての一貫性を手動で維持するのではなく、派生ファイルはベースファイルから作成されるので、ベースファイルだけを維持する必要があるということです。apt ツールは派生ファイルを作成することを目的として設計されました。
注釈に基づいて派生ファイルを生成する場合、ドックレットと比較すると apt には次の利点があります。
- 整理された宣言のモデルとプログラムの現在の型の構造を持つ
- 最新の API 設計を使用して、配列の代わりに一般的なコレクションを返したり、宣言と型を操作するためのビジターを提供する
- 新しく生成されたファイルの再帰的な処理をサポートし、元となるソースファイルと生成されたソースファイルのコンパイルを自動的に実行できる
注釈処理の目的以外に、apt は他の関連するプログラミングタスクに対しても使用できます。
apt の使用方法
概要
まず、apt は、操作されているソースコードに存在する注釈を決定します。次に、記述した「注釈プロセッサファクトリ」を検索します。ファクトリで処理されている注釈が確認されます。次に、ファクトリが操作されているソースファイルに存在する注釈を処理する場合、「注釈プロセッサ」を渡すようにファクトリに求めます。次に、注釈プロセッサが実行されます。プロセッサに生成された新しいソースファイルがある場合、apt は新しいソースファイルが生成されないようになるまで、この処理を繰り返します。
注釈プロセッサの書き込みは、次の 4 つのパッケージに依存します。
各プロセッサは、com.sun.mirror.apt パッケージの AnnotationProcessor インタフェースを実装します。このインタフェースにはプロセッサを呼び出すために、apt ツールにより使用される、1 つのメソッド process があります。プロセッサはひとつまたは複数の注釈型を処理します。
プロセッサのインスタンスが、関連するファクトリ AnnotationProcessorFactory により返されます。apt ツールはファクトリの getProcessorFor メソッドを呼び出しプロセッサを保持します。この呼び出しの間に、ツールは AnnotationProcessorEnvironment を提供します。この環境では、プロセッサは起動するために必要なすべての情報を検出します。この情報には、操作しているプログラム構造の参照、新しいファイルの作成および警告とエラーメッセージを渡すことにより、apt ツールと通信および連携する方法が含まれます。
ファクトリを見るには 2 つの方法があります。使用するファクトリは「-factory」コマンド行オプションを使用して指定できます。またはファクトリは、apt discovery プロシージャ中に格納することが可能です。「-factory」オプションを使用すると、既知の単一のファクトリをもっとも簡単に実行できます。このオプションはファクトリが実行する方法をより制御する必要がある場合にも使用されます。特定のパスにおいてファクトリを検出するには、jar ファイル META-INF/services 情報により取得される検出プロシージャは、次に示すフォーマットに従います。
「-factory」オプションを使用する注釈プロセッサを作成し使用するには、次の手順で行います。
- AnnotationProcessorFactory を記述します。これにより、対象の注釈タイプに対する AnnotationProcessor を作成できます。
- クラスパス tools.jar とともに javac を使用するプロセッサおよびファクトリをコンパイルします。tools.jar には、com.sun.mirror.* インタフェースが含まれます。
- コンパイルされたクラスファイル、またはクラスファイルを含む jar ファイルを、apt を呼び出す場合に適切なパスに置きます。
デフォルトの検出プロシージャとともに注釈プロセッサを作成し使用するには、最初の 2 つのステップを使用します。
- META-INF/services で、com.sun.mirror.apt.AnnotationProcessorFactory という名前の UTF-8 で符号化されたテキストファイルを作成します。その内容は、各行が固定ファクトリクラスの完全指定名のリストです (sun.misc.Service により使用されるリストと同じ形式です)。
- ファクトリ、プロセッサ、および META-INF/services 情報を jar ファイルにパッケージします。
- jar ファイルを apt を呼び出す場合に適切な場所に置きます。適切なパスについては、「検出」 で説明します。
注釈プロセッサの簡単な例
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.*;
import com.sun.mirror.util.*;
import java.util.Collection;
import java.util.Set;
import java.util.Arrays;
import static java.util.Collections.*;
import static com.sun.mirror.util.DeclarationVisitors.*;
/*
* This class is used to run an annotation processor that lists class
* names. The functionality of the processor is analogous to the
* ListClass doclet in the Doclet Overview.
*/
public class ListClassApf implements AnnotationProcessorFactory {
// Process any set of annotations
private static final Collection<String> supportedAnnotations
= unmodifiableCollection(Arrays.asList("*"));
// No supported options
private static final Collection<String> supportedOptions = emptySet();
public Collection<String> supportedAnnotationTypes() {
return supportedAnnotations;
}
public Collection<String> supportedOptions() {
return supportedOptions;
}
public AnnotationProcessor getProcessorFor(
Set<AnnotationTypeDeclaration> atds,
AnnotationProcessorEnvironment env) {
return new ListClassAp(env);
}
private static class ListClassAp implements AnnotationProcessor {
private final AnnotationProcessorEnvironment env;
ListClassAp(AnnotationProcessorEnvironment env) {
this.env = env;
}
public void process() {
for (TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations())
typeDecl.accept(getDeclarationScanner(new ListClassVisitor(),
NO_OP));
}
private static class ListClassVisitor extends SimpleDeclarationVisitor {
public void visitClassDeclaration(ClassDeclaration d) {
System.out.println(d.getQualifiedName());
}
}
}
}
このプロセッサの例では、数多くの新しい言語とライブラリの特徴が使用されています。まず、static インポートが使用され、さまざまなユーティリティメソッドで単純名が使用できます。たとえば
「unmodifiableCollection」
が
「Collections.unmodifiableCollection」の代わりに使用されています。
次に、一般的なコレクションが全体で使用されています。Arrays.asList メソッドは、var-args メソッドであるために、コンマ区切りの文字列のリストを受け取り、目的の要素を使用するリストを作成できます。Collections.emptySet メソッドは一般的なメソッドであり、型保証された空のセットを作成するために使用できます。プロセスメソッドの for ループは、コレクションに対する繰り返し処理を行う、拡張された for ループです。
注釈をプロセスに指定する
処理している注釈をツールに指示するために、この例に示されるように、ファクトリは import 型の文字列のコレクションを返します。特定の文字列のエントリには次の 3 つのうち 1 つの形式があります。
- *
- 「すべて」の注釈を処理する。これは注釈のリストが空の場合にも処理する。つまり、* を処理するファクトリは、注釈が存在しない場合でも重要なプロセッサを要求される可能性がある。この機能により com.sun.mirror API は、一般的なソースコード処理ツールを記述するために使用できる
- foo.bar.Baz
- 標準的な名前が「foo.bar.Baz」という注釈を処理する
- foo.bar.*
- 標準的な名前が「foo.bar.」で始まる注釈を処理する
apt ツールは処理するファクトリに対して一連の注釈を示します。一連の注釈と注釈プロセッサ環境に基づいて、ファクトリは単一の注釈プロセッサを返します。ファクトリが複数の注釈プロセッサを返すように指定する場合はどうなるのでしょうか。ファクトリは com.sun.mirror.apt.AnnotationProcessors.getCompositeAnnotationProcessor を使用して、複数の注釈プロセッサの操作を組み合わせて配列できます。
認識するコマンド行オプションを指定する
supportedOptions メソッドにより、ファクトリは認識するコマンド行オプションの apt と通信します。「-A」で始まるコマンド行オプションは、注釈プロセッサと通信するために予約されています。たとえば、このファクトリが -Adebug および -Aloglevel=3 などのオプションを認識する場合、「-Adebug」および「-Aloglevel」の文字列を返します。後に、ファクトリが認識しない -A オプションが指定された場合、apt はその通知を行います。
apt コマンド行
独自のオプションに加えて、apt ツールは javac が受け入れるすべてのコマンド行オプションを受け入れます。javac オプションが存在する場合は、最後の javac の呼び出しに渡されます。
apt に固有のオプションを次に示します。
- -s dir
- プロセッサが生成したソースファイルが配置されるディレクトリのルートを指定する。ファイルはパッケージ名前空間に基づいてサブディレクトリに配置される
- -nocompile
- ソースファイルをクラスファイルに対してコンパイルしない
- -print
- 指定した型のテキスト表現を出力し、注釈プロセスまたはコンパイルを行わない
- -A[key[=val]]
- 注釈プロセッサに渡されるオプション。これらは直接 apt により解釈されないが、個々のプロセッサにより使用可能にされる
- -factorypath path
- 注釈プロセッサファクトリの検索先を指定する。このオプションが使用されると、クラスパスはファクトリを検索「しない」
- -factory classname
- 使用する AnnotationProcessorFactory 名。デフォルトの検出プロセスをバイパスする
apt が、javac のオプションのいくつかを共有する方法を次に示します。
- -d dir
- プロセッサと javac が生成するクラスファイルを配置する場所を指定する
- -cp path or -classpath path
- ユーザクラスファイルおよび注釈プロセッサファクトリを検出する場所を指定する。-factorypath が与えられた場合、クラスパスはファクトリを検索しない
デバッグに有用な、apt の隠しオプションを次に示します。
- -XListAnnotationTypes
- 注釈の型に検出されるリスト
- -XListDeclarations
- 指定および宣言がインクルードされるリスト
- -XPrintAptRounds
- 初期および再帰的な apt ラウンドに関する情報を出力する
- -XPrintFactoryInfo
- 処理を要求するファクトリの注釈に関する情報を出力する
apt ツールの動作方法
コマンド行に存在する注釈を決定するために、ソースファイルをスキャンした後に、デフォルトでは、apt ツールは適切なパスに存在する注釈プロセッサファクトリを検索します。-factorypath オプションが使用された場合、そのパスがファクトリを検索するための適切なパスになります。それ以外では、クラスパスが適切なパスになります。ファクトリが処理する注釈を決定するために照会されます。ファクトリが存在する注釈の 1 つに処理する場合、その注釈が要求済みであるとみなされます。すべての注釈が要求されると、ツールは追加のファクトリを検索しません。注釈がすべて要求された後、またはさらなるファクトリが検索できない場合、apt はファクトリの getProcessorFor メソッドを呼び出し、そのファクトリが要求した一連の注釈を渡します。各ファクトリは単一のプロセッサを返し、該当する一連の注釈に対する適切な処理を行います。すべてのプロセッサが返された後、apt は順番に各プロセッサを呼び出します。プロセッサが新しいソースファイルを生成した場合、apt の再帰的なラウンドが発生します。再帰的な apt ラウンドでは、ファクトリが現在の注釈を処理していなくても、検出プロシージャは以前のラウンドでプロセッサを供給したファクトリで getProcessorFor を呼び出します。これにより、ファクトリが後続の apt ラウンドでリスナーを登録できるようになります。ただしほとんどのファクトリは、この場合単純に AnnotationProcessors.NO_OP を返します。新しいソースファイルが生成されなかったラウンドの後に、apt は元のおよび生成されたソースファイル上で、javac を呼び出します。プロセッサが検出されない、またはプロセッサが既存の注釈を処理できない場合、apt を呼び出すことは、ソースファイル上で直接 javac を呼び出すことと本質的には同じです。
ファクトリクラスが注釈処理の複数のラウンドで使用される場合、ファクトリクラスは 1 回ロードされ、ファクトリの getProcessorFor メソッドがラウンドごとに 1 回呼び出されます。これによりファクトリはラウンドを越えて静的状態を格納できます。
-factory オプションが使用されている場合、指定されたファクトリだけが照会されます。
apt 処理のラウンド
apt の最初のラウンドは、入力ソースファイルを分析し、検出プロシージャを実行し、出力される注釈プロセッサを呼び出します。apt の 2 番目のラウンドでは、最初のラウンド (存在する場合) で生成された新しいソースファイルを分析し、それらの新しいファイルの検出プロシージャを実行し、出力される注釈プロセッサを呼び出します。同様に、2 番目のラウンドが新しいソースファイルを生成した場合、3 番目のラウンドは新しいソースを分析し、検出プロシージャを実行します。apt ラウンドは新しいソースファイルが生成されなくなるまで続きます。デフォルトでは、最後のラウンドの後に、apt ツールが元のソースファイルおよび生成されたソースファイル上で javac を実行します。
注釈プロセッサまたはファクトリは、その環境において addListener メソッドを使用して、ラウンドの最後にリスナーを登録できます。ツールは登録されたリスナーを、そのラウンドに対するすべての注釈プロセッサが完了した時点で呼び出します。リスナーはラウンドのステータスについての情報を渡します。その情報には、新しいソースファイルが記述された場合、エラーが発生した場合、完成したラウンドが最後のラウンドである場合などがあります。リスナーはすべての注釈プロセスが完了した場合に、最後のファイルの末尾を書き出すために使用されることがあります。同じクラスは AnnotationProcessor および RoundCompleteListener の両インタフェースに実装できるために、同じオブジェクトが両コンテキスト内で機能します。
リターンコード
最後の apt ラウンドの後に javac が呼び出された場合、apt のリターンコードは、それらのファイルをコンパイルする javac のリターンコードになります。javac が呼び出されなかった場合、apt は、ツール自体またはプロセッサによりエラーが報告されなかった場合には 0 の終了状態になります。不正なソースファイルまたは不完全なソースファイル自体の操作では 0 以外の終了状態にはなりません。
宣言と型
API のミラーは、主に Declaration インタフェースと、com.sun.mirror.declaration パッケージのサブインタフェースの階層を介して、ソースコードを構築することを表しています。Declaration は、パッケージ、クラス、メソッドなどのプログラムの要素を表し、通常ソースコードの特定の部分に、1 対 1 で対応します。Declarations は注釈される構造です。
型は TypeMirror インタフェース、および com.sun.mirror.type パッケージ内のサブインタフェースの階層により表されます。型にはプリミティブ型、クラスおよびインタフェース型、配列型、型変数、およびワイルドカード型が含まれます。
API は宣言と型を慎重に区別します。これは 1 つの宣言がすべてのファミリの型を定義する、汎用データ型においてもっとも重要です。たとえば、java.util.Set クラスの宣言は以下に対応します。
- パラメータ化された型 java.util.Set<String>
- パラメータ化された型 java.util.Set<Number>
- パラメータ化された型 java.util.Set<T>。型によっては、String または Number 以外の「T」
- raw 型 java.util.Set
宣言には、ドキュメントコメント、ソースの位置、修飾子、および注釈が含まれます。宣言には異なる種類の名前 (単純、指定) が含まれる可能性があります。より特定した宣言サブクラスにより、その構造に適切な追加の情報が与えられます。たとえば、クラス宣言により、コンストラクタおよびスーパークラスへのアクセスが可能になります。enum に対する宣言には、enum 定数に提供するメソッドが含まれています。
TypeMirror はソースコードの戻り値の型、パラメータ型などをモデル化するために使用されます。参照型に対する TypeMirror は、型から対応する宣言へのマッピングを行います。たとえば、java.util.Set<String> に対するミラー型から java.util.Set に対する宣言へのマッピングを行います。
FAQ
- 「Debian Advanced Packaging Tool」について知っていますか。
はい。
- 注釈プロセッサはドックレットとどのように比較できますか。
2 つのエンティティには確かにいくつかの類似点があります。たとえば、両方ともビルド時にリフレクト API を使用してソースコードに処理します。ただし、注釈プロセッサで使用されたミラー API は、Java プログラミング言語の現状の型システムをよりモデル化します。またデフォルトでは、実行する注釈プロセッサは、-doclet オプションと類似した機構ではなくソースコードに存在する注釈により決定されます。つまり、単一の固定ドックレットを実行する代わりに、apt は動的に実行する可能性がある複数のプロセッサを選択します。
- ローカル変数の注釈を処理するにはどうすればいいですか。
ローカル変数の注釈は、宣言の階層がメソッドまたはコンストラクタ内部のプログラム構造をモデル化しないので、表示されません。
- com.sun.mirror.apt 内のファクトリとプロセッサインタフェースが分かれているのはなぜですか。
「どのように」処理するか、および「いつ」処理するかを制御することを組み合わせて決定することは可能です。しかし、弊社はこの 2 つのステップをファクトリとプロセッサインタフェースに区別し分離する選択をしました。
- apt ant task はありますか。
現状ではありません。
- ミラーとは何ですか。
リフレクションを検出する場所を示します。リフレクションプログラミングでは、ミラーは表示されたオブジェクトと表示を行っているオブジェクト間の一貫性のある分割を維持します。
- ファクトリの注釈のサポートとファクトリのオプションのサポートが、ファクトリのクラスの注釈として符号化する代わりにメソッドにより返されるのはなぜですか。
インタフェースの実装は、必要な機能を持つクラスを示すための通常の機構です。たとえばどの注釈に処理しているかを示すことができます。特定の注釈型を使用して注釈されるクラスを必要とする言語機構はありません。したがって、注釈にあるそのような情報を符号化することは技術的には可能ですが、それはインタフェース内のメソッドが持つ型の安全性を欠くことになります。
- ビジターには慣れていませんが、使用する必要がありますか。
ビジターパターンは 「Gang of Four」の『Design Patterns』という本の中で紹介されている標準的なパターンの 1 つです。このパターンは型に対する明示的なテストを行わずに、型依存の操作を呼び出すことができる強力な機構を提供します。しかしビジターの使用は必須ではありません。
関連項目