의존성 설정
어노테이션 기반 코드 생성을 구현하기 위해선 두 가지 주요 라이브러리가 필요하다. 하나는 프로세서 등록을 자동화하는 auto-service, 다른 하나는 소스 코드를 동적으로 생성하는 javapoet다. Maven 기준으로 다음과 같이 의존성을 추가한다.
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc2</version>
</dependency>
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>
사용자 정의 어노테이션 정의
먼저, 어노테이션 프로세서가 인식할 수 있는 커스텀 어노테이션을 작성한다. 이 어노테이션은 클래스 선언에만 적용되며, 컴파일 타임에서만 유지되도록 설정한다.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface GenerateEntity {
}
어노테이션 프로세서 구현
다음으로 javax.annotation.processing.Processor 인터페이스를 상속받는 프로세서 클래스를 작성한다. 이 클래스는 특정 어노테이션이 붙은 요소를 찾아 자동으로 Java 클래스를 생성한다.
import com.google.auto.service.AutoService;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.Set;
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.example.GenerateEntity")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EntityGeneratorProcessor extends AbstractProcessor {
private ProcessingEnvironment processingEnv;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
this.processingEnv = processingEnvironment;
this.messager = processingEnvironment.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.isEmpty()) return false;
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
generateClass(element);
}
}
return true;
}
private void generateClass(Element element) {
String pkgName = processingEnv.getElementUtils()
.getPackageOf(element).getQualifiedName().toString();
String className = "AutoGeneratedEntity";
FieldSpec idField = FieldSpec.builder(long.class, "id")
.addModifiers(Modifier.PRIVATE)
.addJavadoc("고유 식별자")
.build();
FieldSpec nameField = FieldSpec.builder(String.class, "name")
.addModifiers(Modifier.PRIVATE)
.addJavadoc("엔티티 이름")
.build();
TypeSpec generatedClass = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.addField(idField)
.addField(nameField)
.build();
JavaFile javaFile = JavaFile.builder(pkgName, generatedClass).build();
try {
javaFile.writeTo(processingEnv.getFiler());
messager.printMessage(Diagnostic.Kind.NOTE, "자동 생성 완료: " + pkgName + "." + className);
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "코드 생성 실패: " + e.getMessage());
}
}
}
프로세서 사용 및 검증
새로운 모듈에서 위에서 만든 프로세서를 포함한 JAR을 의존성으로 추가하고, 다음처럼 어노테이션을 사용한다.
@GenerateEntity
public class AppConfiguration {
}
프로젝트를 빌드하면, 컴파일 과정에서 자동으로 AutoGeneratedEntity.java와 해당 클래스 파일이 생성된다. 생성된 코드는 다음과 같다.
package com.example;
public class AutoGeneratedEntity {
/**
* 고유 식별자
*/
private long id;
/**
* 엔티티 이름
*/
private String name;
// 기본 생성자 포함
}
생성된 소스는 target/generated-sources/annotations 경로에 위치하며, IDE에서도 오류 없이 참조할 수 있다.
Gradle 환경에서의 주의사항
Gradle을 사용하는 경우, 애노테이션 프로세서가 제대로 작동하지 않을 수 있다. 이를 해결하려면 apt 플러그인을 적용해야 한다.
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'net.ltgt.gradle:gradle-apt-plugin:0.21'
}
}
apply plugin: 'net.ltgt.apt'
apply plugin: 'net.ltgt.apt-idea'
또는 Gradle 4.6 이상에서는 내장된 annotationProcessor configuration을 사용할 수 있다.
dependencies {
annotationProcessor project(':processor-module')
}