Annotation Processorの基本的な所
codemodelを使ってアノテーションプロセッサ利用によるJavaコードの生成とかでもやってるけど、色々思う所があったのでやってみた
package sample.annotation;
public @interface Bean {
}
んなアノテーションがあって
package sample;
import sample.annotation.Bean;
@Bean
public class Sample {
}
的な感じで使っていると
package sample;
public class SampleBean extends Sample {
}
的な感じでソースが生成される的な事をやるアノテーションプロセッサーを作ると
package sample.processor;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.SourceVersion;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JPackage;
import org.apache.commons.lang3.ClassUtils;
import sample.annotation.Bean;
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("sample.annotation.*")
public class BeanAnnotationProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
private JCodeModel codeModel;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
codeModel = new JCodeModel();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Bean.class)) {
String name = element.toString();
final JPackage pkg = codeModel._package(
ClassUtils.getPackageName(name)
);
try {
JDefinedClass clazz = pkg._class(
ClassUtils.getShortClassName(name) + "Bean"
);
clazz._extends(codeModel.directClass(name));
codeModel.build(new CodeWriter() {
@Override
public OutputStream openBinary(JPackage pkg, String fileName) throws IOException {
FileObject file = filer.createResource(
StandardLocation.SOURCE_OUTPUT,
pkg.name(),
fileName
);
return file.openOutputStream();
}
@Override
public void close() throws IOException {
}
});
} catch (JClassAlreadyExistsException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
}
的な感じになる訳だけど。問題はこれをどうやって実行するかっていう所かと。今までとかだとgradleで
apply plugin: "java"
configurations {
apt
}
sourceSets {
apt {
output.resourcesDir = "apt_generated"
}
}
repositories {
mavenCentral()
}
dependencies {
compile "org.apache.commons:commons-lang3:3.1"
compile "com.sun.codemodel:codemodel:2.6"
apt files("lib/apt.jar")
}
// ※Gradle2.0からCompileタスクタイプが無くなりJavaCompileを使えとの事らしいので
task compileAptJava(type: JavaCompile, group: "build", overwrite: true) {
source = sourceSets.main.java
classpath = configurations.compile + configurations.apt
dependencyCacheDir = compileJava.dependencyCacheDir
options.compilerArgs = [
"-proc:only",
"-processor",
"sample.processor.BeanAnnotationProcessor"
]
destinationDir = sourceSets.apt.output.resourcesDir
}
compileJava.dependsOn compileAptJava
sourceSets.main.java.srcDirs += sourceSets.apt.output.resourcesDir
っていう感じでアノテーションプロセッサーを指定してcompileJavaが行われる前にcompileAptJavaを処理する事が出来る訳だけど、これをしなくてもアノテーションプロセッサーなライブラリにMETA-INF/services/javax.annotation.processing.Processorを定義する事でコンパイルプロセス上で勝手にやってくれる模様。で中身には
sample.processor.BeanAnnotationProcessor
っていう感じでアノテーションプロセッサーなクラスを指定する。それを行なってjarでパッケージ化した場合には
apply plugin: "java"
apply plugin: "eclipse"
repositories {
mavenCentral()
}
dependencies {
compile files("lib/apt.jar")
}
compileJava {
options.compilerArgs = ["-s", "apt_generated"]
}
な感じで特にcompileAptJavaなあれをしなくても良い。又、この場合デフォルトではクラスをコンパイルした先の所にソースが出力されるようになるが、-sオプションで指定しておけば良い模様
んまぁこんな所かとは思うんだけど、Eclipseとかで利用する場合には注釈処理とファクトリーパスを設定するだけで良いんだが、アノテーションプロセッサーの処理段階だとクラスパスが何かと認識してくれないのでアノテーションプロセッサーなjarに関してはfat jar(依存性を組み込んだ?jar)を作れば良い