こんにちわ、みけです。
前回org.glassfish.grizzly.http.server.CLStaticHttpHandler
と
JAX-RS(Jersey)を組み合わせてアプリケーションを作って、
ルートにデプロイしたところ、
片方のハンドラーしか動作しないという残念な結果になってしまいました。
なんか、エロイ人からも特にアドバイス的な音沙汰もなかったので、
CLStaticHttpHandler
を使うのを諦めて、
全部JAX-RSに寄せようと思います。
問題点
さて、スタティックなコンテンツに対するハンドルをJAX-RSで
記述する場合、一つだけ困ったことが有ります。
それはコンテンツが増えるたびに、リソースクラスを作らなければいけないことです。
また、逆にコンテンツがなくなるたびに、リソースクラスを削除しなければいけないことにもなります。
こういった、本質的でないクラスの作成を人様がやるには、
人様の時間は限られすぎています。
というわけで、こういう作業はコンパイラーにさせるべきであるので、
アノテーションプロセッサーにやらせることにしました。
というわけで、本日のお題
Gradleでアノテーションプロセッサーを使ってソースコードを生成するようなプロジェクトを作ってみる
です。
アノテーションプロセッサーに関する基本的でかつ詳細な事柄については、
nikkei IT Proの記事が詳しいです。
- 「Java SE 6完全攻略」第94回 アノテーションを処理する その1
- 「Java SE 6完全攻略」第94回 アノテーションを処理する その2
- 「Java SE 6完全攻略」第94回 アノテーションを処理する その3
- 「Java SE 6完全攻略」第94回 アノテーションを処理する その4
- 「Java SE 6完全攻略」第94回 アノテーションを処理する その5
- 「Java SE 6完全攻略」第94回 アノテーションを処理する その6
- 「Java SE 6完全攻略」第94回 アノテーションを処理する その7
自作アノテーションプロセッサーを使うときのポイント
- アノテーションプロセッサーとアプリケーションの二つのプロジェクトを用意する
- アプリケーションはコンパイル時にはプロセッサーを参照できないようにする
- プロセッサーをコンパイルするときだけクラスパスに追加する
- アノテーションを付与できるようにするために、アノテーションクラスだけをさらに別のプロジェクトに切り出す
- プロセッサーでコードを生成する場合は、生成先のディレクトリーをコンパイル前に作っておく
- 生成されたコードがすでにある場合は、一度コードを破棄する
- 生成されたコードもコンパイル対象に加える
1.アノテーションプロセッサーとアプリケーションの二つのプロジェクトを用意する
アノテーションプロセッサーを自作する場合、
プロジェクトの構造が若干面倒になります。
というのも、アプリケーションプロジェクトのコンパイルの段階で、
既にアノテーションプロセッサーのプロジェクトはビルドされている
必要があるので、プロジェクト間で依存関係が発生します。
したがって、アプリケーションプロジェクト(application
)は、
アノテーションプロセッサーのプロジェクト(processor
)に依存するようにします。
これをgradleで記述する場合、次のようになります。
build.gradle
subprojects { apply plugin: 'java' } project(':application') { dependencies { compile project(':processor') } }
settings.gradle
include 'processor', 'application'
2.アプリケーションはコンパイル時にはプロセッサーを参照できないようにする
ところで、アノテーションプロセッサーで用いられるクラスは、
アプリケーションを作成するためのクラスには関係のないクラスばかりなので、
コンパイルするときに不要なものばかりです。
依存はするけど、コンパイル時には参照できないようにしておきたいです。
したがって、別のconfiguration
を作成して、
アプリケーションプロジェクトはそこからプロセッサープロジェクトに
依存するようにします。
したがって、gradleの記述は次のようになります。
build.gradle
project(':application') { configurations { annotationProcessor } dependencies { annotationProcessor project(':processor') } }
3.プロセッサーをコンパイルするときだけクラスパスに追加する
ただし、これだと、コンパイルするときにプロセッサーが素通りしてしまうので、
コンパイルクラスパスに含める必要があります。
したがって、gradleの記述は次のようになります。
build.gradle
project(':application') { configurations { annotationProcessor } dependencies { annotationProcessor project(':processor') } compileJava { classpath += configurations.annotationProcessor } }
4.アノテーションを付与できるようにするために、アノテーションクラスだけをさらに別のプロジェクトに切り出す
プロセッサーのトリガーとなるアノテーションは、
プロセッサーはもちろん、アプリケーションプロジェクトからも参照できないと、
アノテーションを付与することができません。
アノテーションクラスはプロセッサー、アプリケーションプロジェクト双方から参照できるように、
別のサブプロジェクトに切り出しておく必要があります。
したがって、サブプロジェクト(common)を作成して、
それにプロセッサー、アプリケーションの両方のプロジェクトが依存するようにします。
settings.gradle
include 'common', 'processor', 'application'
build.gradle
project(':application') { configurations { annotationProcessor } dependencies { compile project(':common') annotationProcessor project(':processor') } compileJava { classpath += configurations.annotationProcessor } } project(':processor') { dependencies { compile project(':common') } }
5.プロセッサーでコードを生成する場合は、生成先のディレクトリーをコンパイル前に作っておく
アノテーションプロセッサーの仕様により、ソースコードを生成する場合、
存在しないディレクトリーにソースコードを作成することができません。
したがって、コンパイル前に生成したソースコードを格納するディレクトリーを
作っておく必要があります。
また、出力先のディレクトリーの情報はコンパイラーに引数として渡しておかない場合、
コンパイラーはクラス出力先にソースコードを出力してしまいます。
その結果、出力したコードをさらに利用する場合などは不便になってしまいます。
そのため、-s
オプションによって出力ディレクトリーを指定します。
ここでは生成したソースコードをsrc/main/generated
に出力するものとします。
build.gradle
project(':application') { ext { generated = file("${projectDir}/src/main/generated") } configurations { annotationProcessor } dependencies { compile project(':common') annotationProcessor project(':processor') } compileJava { doFirst { mkdir generated } options.compilerArgs += ['-s', generated] classpath += configurations.annotationProcessor } }
6.生成されたコードがすでにある場合は、一度コードを破棄する
アノテーションプロセッサーの仕様により、生成しようとするファイルがある場合は、
エラーが発生してしまいます。
そのため、コンパイルするたびに、生成されたコードを破棄する必要があります。
そこで、コンパイルタスクは生成されたコードを破棄するタスクに依存するようにします。
build.gradle
project(':application') { ext { generated = file("${projectDir}/src/main/generated") } configurations { annotationProcessor } dependencies { compile project(':common') annotationProcessor project(':processor') } task cleanGenerated(type: Delete) { delete generated } compileJava { dependsOn cleanGenerated doFirst { mkdir generated } options.compilerArgs += ['-s', generated] classpath += configurations.annotationProcessor } }
7.生成されたコードもコンパイル対象に加える
生成されたコードもソースコードですから、
これをコンパイル対象に加えます。
具体的にはsourceSets
で指定します。
build.gradle
project(':application') { ext { generated = file("${projectDir}/src/main/generated") } configurations { annotationProcessor } sourceSets { main { java { srcDirs generated } } } dependencies { compile project(':common') annotationProcessor project(':processor') } task cleanGenerated(type: Delete) { delete generated } compileJava { dependsOn cleanGenerated doFirst { mkdir generated } options.compilerArgs += ['-s', generated] classpath += configurations.annotationProcessor } }
以上の要求をまとめたビルドスクリプトは次のようになります。
なお、その他の前提条件は次のとおりです。
settings.gradle
include 'common', 'processor', 'application'
build.gradle
apply plugin: 'idea' ext { jdk = 1.8 encoding = 'UTF-8' } allprojects { repositories { mavenCentral () } } subprojects { apply plugin: 'java' group = 'sample.processor' version = '1.0' compileJava { sourceCompatibility = rootProject.jdk targetCompatibility = rootProject.jdk options.encoding = rootProject.encoding } dependencies { testCompile 'junit:junit:4.11' } } project(':application') { ext { generated = file("${projectDir}/src/main/generated") } configurations { annotationProcessor } sourceSets { main { java { srcDirs generated } } } dependencies { compile project(':common') annotationProcessor project(':processor') } task cleanGenerated(type: Delete) { delete generated } compileJava { dependsOn cleanGenerated doFirst { mkdir generated } options.compilerArgs += ['-s', generated] classpath += configurations.annotationProcessor } clean.dependsOn cleanGenerated } project(':processor') { dependencies { compile project(':common') } }
その他のアノテーションプロセッサーの諸注意は次回以降…
おわり