본 포스팅은 Pluggable Annotation Processing API 使い方メモ 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할수 있습니다.
Java 1.6에서 추가된, 컴파일 시 Annotation을 처리하기 위한 구조.
Lombok이나 JPA의 Metamodel 에서 이용되고 있다.
즉, 컴파일 시에 Annotation을 읽어 소스 코드를 자동생성할 수 있다.
Java 1.5에서 Annotation이 추가될 때에, Annotation을 컴파일시에 처리하는 방법으로 Annotation Processing Tool (apt)가 추가되었지만, 그것과는 별개인 것 같다.
MyAnnotationProcessor.java
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
import javax.lang.model.SourceVersion;
import java.util.Set;
import java.util.HashSet;
public class MyAnnotationProcessor extends AbstractProcessor {
private int round = 1;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
System.out.println("Round : " + (this.round++));
annotations.forEach(System.out::println);
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add("*");
return supportedAnnotationTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
}
Hoge.java
public class Hoge {
@Deprecated
public void deprecatedMethod() {}
@Override
public String toString() {
return "hoge";
}
}
> javac MyAnnotationProcessor.java
> dir /b
MyAnnotationProcessor.class
MyAnnotationProcessor.java
Hoge.java
> javac -processor MyAnnotationProcessor Hoge.java
Round : 1
java.lang.Deprecated
java.lang.Override
Round : 2
> dir /b
Hoge.class
Hoge.java
MyAnnotationProcessor.class
MyAnnotationProcessor.java
MyAnnotationProcessor.java
import javax.annotation.processing.AbstractProcessor;
public class MyAnnotationProcessor extends AbstractProcessor {
Processor
를 구현한 추상 클래스 AbstractProcessor 가 준비되어 있기 때문에, 보통은 그것을 상속해서 만든다.MyAnnotationProcessor.java
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add("*");
return supportedAnnotationTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
getSupportedAnnotationTypes()
로 처리하는 Annotation을 정의한다.Set<String>
은, 처리 대상이 되는 Annotation 패턴을 반환하도록 한다.*
을 사용한 와일드카드 표현도 가능*
만 사용한 경우는, 전체 Annotation이 처리 대상이 된다.getSupportedSourceVersion()
는 지원 범위가 되는 버전을 정의SourceVersion.RELEASE_7를 반환하도록 한 경우의 경고 메시지
경고: Annotation Processor 'MyAnnotationProcessor'에서 -source '1.8'보다 낮은 소스 버전 'RELEASE_7'가 지원되고 있다.
> javac -processor MyAnnotationProcessor Hoge.java
-processor
옵션으로 사용하는 프로세서를 지정한다.실행경과
Round : 1
java.lang.Deprecated
java.lang.Override
Round : 2
MyAnnotationProcessor.java
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.SourceVersion;
import java.util.Set;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
public class MyAnnotationProcessor extends AbstractProcessor {
private int round = 1;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
System.out.println("Round : " + (this.round++));
annotations.forEach(System.out::println);
return true;
}
}
@SupportedSourceVersion
과 @SupportedAnnotationTypes
Annotation으로 정의할 수 있다.MyAnnotationProcessor.java
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.Element;
import javax.lang.model.SourceVersion;
import java.util.Set;
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("*")
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment env) {
for (TypeElement typeElement : typeElements) {
for (Element element : env.getElementsAnnotatedWith(typeElement)) {
Override override = element.getAnnotation(Override.class);
if (override != null) {
System.out.println("@Override at " + element);
}
}
}
return true;
}
}
동작 확인
> javac -processor MyAnnotationProcessor Hoge.java
@Override at toString()
RoundEnvironment#getElementsAnnotatedWith(TypeElement)
함수에서, 실제로 Annotate 되는 장소를 나타내는 Element 의 Set 을 얻을 수 있다.Element#getAnnotation(Class)
함수에서, 실제 Annotation을 얻을 수 있다.MyAnnotationProcessor.java
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
messager.printMessage(Kind.OTHER, "Other");
messager.printMessage(Kind.NOTE, "Note");
messager.printMessage(Kind.WARNING, "Warning");
messager.printMessage(Kind.MANDATORY_WARNING, "Mandatory Warning");
messager.printMessage(Kind.ERROR, "Error");
return true;
}
}
컴파일 실행 결과
Note:Other
Note:Note
Warning: Warning
Warning: Mandatory Warning
Error: Error
Note:Other
Note:Note
Warning: Warning
Warning: Mandatory Warning
Error: Error
Error 2개
Warning 4개
AbstractProcessor
클래스가 가지는 processingEnv
라는 필드로부터, getMessager()
함수를 사용해서 Messager 의 인스턴스를 취득한다.Messager#printMessage()
함수를 사용해서, 컴파일 시의 메시지 출력을 할 수 있다.Kind.ERROR
로 해서 메시지 출력하면 컴파일 결과로 에러가 된다.MyAnnotationProcessor.java
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
messager.printMessage(Kind.NOTE, "hogehoge", element);
}
}
return true;
}
}
컴파일 실행 결과
Hoge.java:5: 注意:hogehoge
public String toString() {
^
Messager#printMessage()
함수의 세 번째 인수로 Element
를 전달하면, 그 요소의 코드상 위치 정보가 같이 출력된다.MyAnnotationProcessor.java
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("java.lang.Override")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
AnnotationMirror override = element.getAnnotationMirrors().get(0);
messager.printMessage(Kind.NOTE, "hogehoge", element, override);
}
}
return true;
}
}
컴파일 실행 결과
Hoge.java:4: 注意:hogehoge
@Override
^
Messager#printMessage()
함수의 네 번째 인수로 AnnotationMirror를 전달하면, 그 Annotation의 코드상 위치정보가 같이 출력된다.AnnotationValue
를 전달하면, Annotation 인수의 위치 정보도 출력 가능할 듯하다 (테스트해보지 않음).MyAnnotation.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({
ElementType.TYPE, ElementType.METHOD, ElementType.LOCAL_VARIABLE,
ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE_PARAMETER
})
public @interface MyAnnotation {
String value();
}
MyAnnotationProcessor.java
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes("MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
MyAnnotation anno = element.getAnnotation(MyAnnotation.class);
String msg =
"<<@MyAnnotation(\"" + anno.value() + "\")>>\n"
+ " Kind : " + element.getKind() + "\n"
+ " SimpleName : " + element.getSimpleName() + "\n"
+ " Modifiers : " + element.getModifiers() + "\n"
+ " asType : " + element.asType() + "\n"
+ " EnclosedElements : " + element.getEnclosedElements() + "\n"
+ " EnclosingElement : " + element.getEnclosingElement() + "\n"
+ " AnnotationMirrors : " + element.getAnnotationMirrors() + "\n"
;
System.out.println(msg);
}
}
return true;
}
}
Hoge.java
@MyAnnotation("클래스")
public class Hoge<@MyAnnotation("Generic 인수") T> {
@MyAnnotation("필드")
private double d;
@MyAnnotation("함수")
public void method(@MyAnnotation("인수") int i) {
@MyAnnotation("로컬 변수")
String str;
}
}
컴파일 실행 결과
<<@MyAnnotation("클래스")>>
Kind : CLASS
SimpleName : Hoge
Modifiers : [public]
asType : Hoge<T>
EnclosedElements : Hoge(),d,method(int)
EnclosingElement : 이름이 없는 Package
AnnotationMirrors : @MyAnnotation("\u30af\u30e9\u30b9")
<<@MyAnnotation("Generic 인수")>>
Kind : TYPE_PARAMETER
SimpleName : T
Modifiers : []
asType : T
EnclosedElements :
EnclosingElement : Hoge
AnnotationMirrors : @MyAnnotation("\u578b\u5f15\u6570")
<<@MyAnnotation("필드")>>
Kind : FIELD
SimpleName : d
Modifiers : [private]
asType : double
EnclosedElements :
EnclosingElement : Hoge
AnnotationMirrors : @MyAnnotation("\u30d5\u30a3\u30fc\u30eb\u30c9")
<<@MyAnnotation("함수")>>
Kind : METHOD
SimpleName : method
Modifiers : [public]
asType : (int)void
EnclosedElements :
EnclosingElement : Hoge
AnnotationMirrors : @MyAnnotation("\u30e1\u30bd\u30c3\u30c9")
<<@MyAnnotation("인수")>>
Kind : PARAMETER
SimpleName : i
Modifiers : []
asType : int
EnclosedElements :
EnclosingElement : method(int)
AnnotationMirrors : @MyAnnotation("\u5f15\u6570")
매번 javac 옵션으로 -processor를 지정하는게 피곤합니다.
Annotation Processor를 소정의 방법으로 jar에 패키징하면, 컴파일 시에 jar을 class path에 지정하는 것으로 프로세서 처리를 할 수 있습니다.
MyOverrideProcessor.java
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes("java.lang.Override")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyOverrideProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("Override!!");
return true;
}
}
@Override
Annotation 을 처리하는 프로세서.
이것을 아래의 구성과 같이 jar에 패키징한다.
|-sample/processor/
| `-MyOverrideProcessor.class
`-META-INF/services/
`-javax.annotation.processing.Processor
META-INF/services/javax.annotation.processing.Processor
는 단순한 테스트 파일로, 내용은 아래와 같이 사용하는 프로세서의 FQCN을 기술합니다.
javax.annotation.processing.Processor
sample.processor.MyOverrideProcessor
이 jar 을 processor.jar
로 합니다.
Hoge.java
public class Hoge {
@Override
public String toString() {
return super.toString();
}
}
> javac -cp processor.jar Hoge.java
Override!!
Override!!
META-INF/services/javax.annotation.processing.Processor
라는 형식으로 텍스트 파일을 배치한다.javax.annotation.processing.Processor
안에는, 사용하는 프로세서의 FQCN을 기술한다.javax.annotation.processing.Processor
로 지정한 프로세서가 컴파일 시에 사용된다.javax.annotation.processing.Processor
에는, 실행구역에 복수 프로세서를 기술할 수가 있다.META-INF/services
의 아래에 사용하는 Class 정보를 배치하는 방법은 Java의 ServiceLoader라는 구조를 이용한 것으로 보인다.MyAnnotationProcessor.java
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
messager.printMessage(Kind.NOTE, "Hello Annotation Processor!!");
return true;
}
}
javax.annotation.processing.Processor
sample.processor.MyAnnotationProcessor
javax.annotation.processing.Processor
).jar 파일을 출력
Hoge.java
public class Hoge {
@Override
public String toString() {
return super.toString();
}
}
평소같이 java 프로젝트를 만든다.
Annotation Processing를 유효하게 한다
MyAnnotationProcessor
프로젝트의 jar 파일을 선택한다.이걸로 Annotation Processing이 유효하게 되었다.
출력 결과를 확인한다
MyAnnotationProcessor.java
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes("java.lang.Override")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
messager.printMessage(Kind.ERROR, "オーバーライドさせぬ!", element);
}
}
return true;
}
}
이용 측의 모습
Editor 상에 에러가 표시된다
폴더 구성
|-settings.gradle
|-build.gradle
|-processor/
| `-src/main/
| |-java/sample/processor/
| | `-MyAnnotationProcessor.java
| `-resources/META-INF/services/
| `-javax.annotation.processing.Processor
|
`-client/
|-build.gradle
`-src/main/java/sample/processor/
`-Hoge.java
processor
와 client
라는 프로젝트가 있다.processor
프로젝트에서는, Annotation Processor를 구현한다.client
프로젝트에서는, processor
프로젝트에서 작성한 Annotation Processor를 이용한다.settings.gradle
include 'processor', 'client'
build.gradle
subprojects {
apply plugin: 'java'
}
client/build.gradle
dependencies {
compile project(':processor')
}
MyAnnotationProcessor.java
package sample.processor;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("Hello!!");
return true;
}
}
Hoge.java
package sample.processor;
public class Hoge {
@Override
public String toString() {
return "hoge";
}
}
> gradle compileJava
:processor:compileJava
:processor:processResources
:processor:classes
:processor:jar
:client:compileJava
Hello!!
Hello!!
BUILD SUCCESSFUL
Total time: 3.423 secs
gradle eclipse
로 했다면, 자동으로 Annotation Processing 설정이 활성화되도록 한다
폴더 구성
|-build.gradle
|-lib/
| `-processor.jar
`-src/main/java/sample/processor/
`-Hoge.java
프로세서를 패키징한 jar 파일은, lib
폴더의 아래에 배치해둡니다
client/build.gradle
apply plugin: 'java'
apply plugin: 'eclipse'
ext {
eclipseAptPrefsFile = '.settings/org.eclipse.jdt.apt.core.prefs'
eclipseFactoryPathFile = '.factorypath'
processorJarPath = 'lib/processor.jar'
}
dependencies {
compile files(processorJarPath)
}
eclipse {
project.name = 'MyAnnotationProcessorClient'
classpath.file.withXml {
it.asNode().appendNode('classpathentry', [kind: 'src', path: '.apt_generated'])
}
jdt.file.withProperties { properties ->
properties.put 'org.eclipse.jdt.core.compiler.processAnnotations', 'enabled'
}
}
eclipseJdt << {
file(eclipseAptPrefsFile).write """\
|eclipse.preferences.version=1
|org.eclipse.jdt.apt.aptEnabled=true
|org.eclipse.jdt.apt.genSrcDir=.apt_generated
|org.eclipse.jdt.apt.reconcileEnabled=true
|""".stripMargin()
file(eclipseFactoryPathFile).write """\
|<factorypath>
| <factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
| <factorypathentry kind="EXTJAR" id="${file(processorJarPath).absolutePath}" enabled="true" runInBatchMode="false"/>
|</factorypath>
|""".stripMargin()
}
cleanEclipse << {
file(eclipseAptPrefsFile).delete()
file(eclipseFactoryPathFile).delete()
}
하고 있는것은 단순해서 eclipse
Task를 실행하면 Annotation Processing을 활성화하기위해 필요한 설정 파일을 추가하고 있습니다.
org.eclipse.jdt.apt.core.prefs 작성
ext {
eclipseAptPrefsFile = '.settings/org.eclipse.jdt.apt.core.prefs'
...
}
...
eclipseJdt << {
file(eclipseAptPrefsFile).write """\
|eclipse.preferences.version=1
|org.eclipse.jdt.apt.aptEnabled=true
|org.eclipse.jdt.apt.genSrcDir=.apt_generated
|org.eclipse.jdt.apt.reconcileEnabled=true
|""".stripMargin()
...
.factorypath 작성
ext {
eclipseFactoryPathFile = '.factorypath'
...
}
...
eclipseJdt << {
...
file(eclipseFactoryPathFile).write """\
|<factorypath>
| <factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
| <factorypathentry kind="EXTJAR" id="${file(processorJarPath).absolutePath}" enabled="true" runInBatchMode="false"/>
|</factorypath>
|""".stripMargin()
}
**.classpath vuswlwq **
eclipse {
...
classpath.file.withXml {
it.asNode().appendNode('classpathentry', [kind: 'src', path: '.apt_generated'])
}
...
}
org.eclipse.jdt.core.prefs 편집
eclipse {
...
jdt.file.withProperties { properties ->
properties.put 'org.eclipse.jdt.core.compiler.processAnnotations', 'enabled'
}
}
cleanEclipse 에 삭제 대상을 추가
ext {
eclipseAptPrefsFile = '.settings/org.eclipse.jdt.apt.core.prefs'
eclipseFactoryPathFile = '.factorypath'
...
}
...
cleanEclipse << {
file(eclipseAptPrefsFile).delete()
file(eclipseFactoryPathFile).delete()
}
Annotation Processor 처리 중, 소스 코드를 동적으로 만들어 보자.
MyAnnotation.java
package sample.processor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface MyAnnotation {
boolean value() default false;
}
Hoge.java
package sample.processor;
@MyAnnotation(true)
public class Hoge {
}
MyAnnotationProcessor.java
package sample.processor;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
@SupportedAnnotationTypes("sample.processor.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.size() == 0) {
return true;
}
Messager messager = super.processingEnv.getMessager();
for (TypeElement typeElement : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {
MyAnnotation anno = element.getAnnotation(MyAnnotation.class);
messager.printMessage(Kind.NOTE, element.getSimpleName() + " class is annotated by @MyAnnotation.");
if (!anno.value()) {
messager.printMessage(Kind.NOTE, "@MyAnnotation value is false. no generate.");
break;
}
String src =
"package sample.processor.generated;\r\n"
+ "import sample.processor.MyAnnotation;\r\n"
+ "@MyAnnotation\r\n"
+ "public class Fuga {\r\n"
+ " public void hello() {\r\n"
+ " System.out.println(\"Hello World!!\");\r\n"
+ " }\r\n"
+ "}\r\n"
;
try {
Filer filer = super.processingEnv.getFiler();
JavaFileObject javaFile = filer.createSourceFile("Fuga");
try (Writer writer = javaFile.openWriter()) {
writer.write(src);
}
messager.printMessage(Kind.NOTE, "generate source code!!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
}
컴파일 실행 결과
Warring:Hoge class is annotated by @MyAnnotation.
Warring:generate source code!!
Warring:Fuga class is annotated by @MyAnnotation.
Warring:@MyAnnotation value is false. no generate.
자동 생성된 Fuga Class
package sample.processor.generated;
import sample.processor.MyAnnotation;
@MyAnnotation
public class Fuga {
public void hello() {
System.out.println("Hello World!!");
}
}
Fuga.java
는 .class
파일이 출력된 폴더의 Root에 출력됩니다.
- Fuga.class
는 동일하게 .class
파일의 출력 저장소에 package
선언에서 정의한 위치에 출력된다.출력된 파일 상태
|-Fuga.java
`-sample/processor/
|-Hoge.class
`-generated/
`-Fuga.class
MyAnnotationProcessor.java(일부)
try {
Filer filer = super.processingEnv.getFiler();
JavaFileObject javaFile = filer.createSourceFile("Fuga");
try (Writer writer = javaFile.openWriter()) {
writer.write(src);
}
messager.printMessage(Kind.NOTE, "generate source code!!");
} catch (IOException e) {
e.printStackTrace();
}
AbstractProcessor#processingEnv
의 getFiler()
함수에서 Filer 의 인스턴스를 취득한다Filer#createSourceFile(String)
함수에서 JavaFileObject
의 인스턴스를 취득한다(인수는 생성된 Class 명).JavaFileObject
를 취득하면 openWriter()
이나 openOutputStream()
를 사용해서 Writer이나 Stream을 취득해서 소스 코드를 작성한다.process()
함수가 실행된다.process()
함수의 첫 번째 인수의 Size를 이용한다).처리해야 하는 Annotation이 남아 있지 않다면, 바로 처리를 종료시키는 부분
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.size() == 0) {
return true;
}
[vvakame / build.gradle | GitHub Gist](https://gist.github.com/vvakame/5176993#file-build-gradle-L196) |
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024