Java 어노테이션 프로세서를 활용한 코드 자동 생성

의존성 설정

어노테이션 기반 코드 생성을 구현하기 위해선 두 가지 주요 라이브러리가 필요하다. 하나는 프로세서 등록을 자동화하는 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')
}

태그: APT Annotation Processing JavaPoet AutoService Gradle APT Plugin

5월 25일 13:42에 게시됨