코딩쿠의 코드 연대기

Java 애노테이션 본문

코딩스터디/JAVA스터디

Java 애노테이션

코딩쿠 2024. 11. 18. 20:45

학습목표

  • 애노테이션 정의하는 방법
  • @retention
  • @target
  • @documented
  • 애노테이션 프로세서

애노테이션이란?

  • 소스 코드에 추가적인 정보를 제공하는 메타데이터입니다.
  • 컴파일러에게 정보를 제공하거나, 런타임에 특정 동작을 수행하도록 지시할 수 있습니다.
  • @ 기호로 시작하며, 클래스, 메서드, 필드 등에 붙여서 사용합니다.

애노테이션 정의하는 방법

Java에서 애노테이션을 정의하는 방법은 다음과 같습니다.

1. @interface 키워드 사용:

애노테이션을 정의하려면 @interface 키워드를 사용합니다. 이 키워드는 컴파일러에게 새로운 애노테이션 타입을 정의한다는 것을 알려줍니다.

Java

public @interface MyAnnotation {
    // 애노테이션 요소들
}

2. 애노테이션 요소 정의 (선택 사항):

애노테이션은 속성과 유사한 요소를 가질 수 있습니다. 요소는 애노테이션에 정보를 추가하는 데 사용됩니다. 요소를 정의하려면 다음과 같은 규칙을 따라야 합니다.

  • 요소의 타입은 기본형, String, enum, 애노테이션, Class 또는 이러한 타입의 배열만 가능합니다.
  • 요소는 괄호 () 없이 선언해야 합니다.
  • 기본값을 지정할 수 있습니다.

Java

public @interface MyAnnotation {
    String value() default "";
    int number();
    String[] names();
}

3. 메타 애노테이션 사용 (선택 사항):

메타 애노테이션은 다른 애노테이션에 적용되는 애노테이션입니다. Java는 @Retention, @Target, @Documented, @Inherited와 같은 몇 가지 기본 메타 애노테이션을 제공합니다.

  • @Retention: 애노테이션이 유지되는 기간을 지정합니다. (SOURCE, CLASS, RUNTIME)
  • @Target: 애노테이션을 적용할 수 있는 요소 유형을 지정합니다. (TYPE, FIELD, METHOD, PARAMETER 등)
  • @Documented: 애노테이션을 javadoc에 포함할지 여부를 지정합니다.
  • @Inherited: 애노테이션을 하위 클래스에서 상속할지 여부를 지정합니다.

Java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "";
    int number();
    String[] names();
}

예제:

Java

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface MyAnnotation {
    String value() default "";
    int number();
}

public class MyClass {
    @MyAnnotation(value = "test", number = 10)
    public void myMethod() {
        // ...
    }
}

위 예제에서 @MyAnnotationRUNTIME까지 유지되고 메서드에 적용될 수 있는 애노테이션입니다. valuenumber라는 두 개의 요소를 가지고 있으며, MyClassmyMethod() 메서드에 적용되었습니다.

참고:

  • 애노테이션은 인터페이스와 유사하지만, interface 키워드 앞에 @ 기호가 붙습니다.
  • 애노테이션은 클래스, 메서드, 필드, 변수 등에 적용될 수 있습니다.
  • 애노테이션은 컴파일 시간이나 런타임에 정보를 제공하는 데 사용됩니다.

@retention, @target, @documented

Java 애노테이션에서 @Retention, @Target, @Documented는 애노테이션의 동작 방식을 지정하는 데 사용되는 중요한 메타 애노테이션입니다.

1. @Retention

  • 애노테이션이 유지되는 기간을 지정합니다. 즉, 애노테이션 정보가 소스 코드, 컴파일된 클래스 파일, 런타임 중 언제까지 유지될지를 결정합니다.
  • RetentionPolicy enum 값을 인자로 받습니다.
    • RetentionPolicy.SOURCE: 소스 코드에만 유지되고 컴파일 과정에서 제거됩니다. 컴파일러에게 정보를 제공하는 용도로 사용됩니다.
    • RetentionPolicy.CLASS: 컴파일된 클래스 파일에 유지되지만 런타임에는 사용할 수 없습니다.
    • RetentionPolicy.RUNTIME: 런타임까지 유지되어 리플렉션을 통해 애노테이션 정보에 접근할 수 있습니다.

예시:

Java

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    // ...
}

2. @Target

  • 애노테이션을 적용할 수 있는 요소의 유형을 지정합니다.
  • ElementType enum 값을 인자로 받습니다.
    • ElementType.TYPE: 클래스, 인터페이스, enum
    • ElementType.FIELD: 필드
    • ElementType.METHOD: 메서드
    • ElementType.PARAMETER: 메서드의 파라미터
    • ElementType.CONSTRUCTOR: 생성자
    • ElementType.LOCAL_VARIABLE: 지역 변수
    • ElementType.ANNOTATION_TYPE: 애노테이션
    • ElementType.PACKAGE: 패키지
    • ElementType.TYPE_PARAMETER: 타입 매개변수 (Java 8 이상)
    • ElementType.TYPE_USE: 타입 사용 (Java 8 이상)

예시:

Java

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    // ...
}

3. @Documented

  • 애노테이션 정보를 JavaDoc 문서에 포함시킬지 여부를 지정합니다.
  • 이 애노테이션이 있는 경우 JavaDoc을 생성할 때 애노테이션 정보가 포함됩니다.

예시:

Java

@Documented
public @interface MyAnnotation {
    // ...
}

요약:

  • @Retention: 애노테이션 유지 기간 (SOURCE, CLASS, RUNTIME)
  • @Target: 애노테이션 적용 대상 (TYPE, FIELD, METHOD 등)
  • @Documented: JavaDoc에 포함 여부

이러한 메타 애노테이션을 사용하여 애노테이션의 동작 방식을 세밀하게 제어할 수 있습니다.

애노테이션 프로세서

Java 애노테이션 프로세서는 컴파일 과정에서 애노테이션을 처리하는 데 사용되는 강력한 도구입니다. 컴파일러에 플러그인처럼 동작하여 소스 코드에서 특정 애노테이션을 찾아내고, 그에 따라 코드를 생성하거나 수정, 검증하는 등 다양한 작업을 수행할 수 있습니다.

애노테이션 프로세서의 주요 기능:

  • 코드 생성: 애노테이션 정보를 기반으로 새로운 소스 파일을 생성합니다. 예를 들어, Lombok은 @Getter와 같은 애노테이션을 통해 getter 메서드를 생성합니다.
  • 코드 검증: 잘못된 애노테이션 사용이나 규칙 위반을 검출하여 컴파일 오류를 발생시킴으로써 코드를 검증합니다.
  • 컴파일 타임 지원: 반복적인 작업을 자동화하여 개발자의 생산성을 높이고, 컴파일 타임에 타입 검증 및 코드 생성을 지원합니다.

애노테이션 프로세서의 장점:

  • 컴파일 타임 처리: 런타임이 아닌 컴파일 타임에 동작하므로 성능 오버헤드가 없습니다.
  • 코드 간결화: 보일러플레이트 코드를 줄이고 가독성을 높입니다.
  • 자동화된 검증: 애노테이션 사용 규칙을 자동으로 검증하여 런타임 오류를 방지합니다.
  • 유지보수성 향상: 코드 생성 및 검증을 자동화함으로써 유지보수가 쉬워집니다.

애노테이션 프로세서 사용 방법:

  1. javax.annotation.processing.Processor 인터페이스 구현: process() 메서드를 오버라이드하여 애노테이션 처리 로직을 구현합니다.
  2. javax.annotation.processing.SupportedAnnotationTypes 애노테이션 사용: 처리할 애노테이션 타입을 지정합니다.
  3. javax.annotation.processing.SupportedSourceVersion 애노테이션 사용: 지원하는 Java 소스 코드 버전을 지정합니다.
  4. META-INF/services/javax.annotation.processing.Processor 파일 생성: 프로세서 클래스의 정규화된 이름을 파일에 명시합니다.

애노테이션 프로세서 예시 (Lombok):

Lombok은 애노테이션 프로세서를 활용하여 개발 생산성을 향상시키는 대표적인 라이브러리입니다.

Java

@Getter
@Setter
public class User {
    private String name;
    private int age;
}

@Getter@Setter 애노테이션을 사용하면 Lombok이 컴파일 과정에서 getName(), setName(), getAge(), setAge() 메서드를 자동으로 생성해줍니다.

애노테이션 프로세서 사용 방법:

  1. AbstractProcessor 클래스 상속: process() 메서드를 오버라이드하여 애노테이션 처리 로직을 구현합니다.
  2. 애노테이션 정의: @SupportedAnnotationTypes로 처리할 애노테이션을 지정하고, @SupportedSourceVersion으로 지원할 소스 버전을 명시합니다.
  3. 빌드 설정: Gradle 또는 Maven의 annotationProcessor를 사용하여 애노테이션 프로세서를 등록합니다. (기존 방식인 META-INF/services 파일 작성도 가능)

코드 예시 (커스텀 애노테이션 프로세서):

@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing: " + element);
        }
        return true;
    }
}

Lombok과 같은 애노테이션 프로세서의 동작 예시:

@Getter
@Setter
public class User {
    private String name;
    private int age;
}

이 코드는 Lombok 애노테이션 프로세서를 통해 컴파일 시 자동으로 다음과 같은 메서드를 생성합니다:

public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }

자주 사용되는 애노테이션 프로세서:

  • Lombok: @Getter, @Builder 등으로 보일러플레이트 코드 제거.
  • Dagger: 의존성 주입을 위한 코드 생성.
  • Room: SQLite 데이터베이스와의 상호작용을 위한 코드 생성.
  • AutoValue: 불변 객체를 자동 생성.

애노테이션 프로세서는 코드 생성, 분석, 검증, 수정 등 다양한 작업을 자동화하여 개발 생산성을 높이는 데 기여합니다. Java 개발에서 매우 유용한 도구이므로, 적극적으로 활용해 보시기 바랍니다.

참고사항:

  • 런타임 애노테이션과의 차이: 애노테이션 프로세서는 컴파일 타임에 동작하며, 런타임 리플렉션 기반 애노테이션(@Override 등)과는 다른 방식으로 사용됩니다.
  • 프로세서 등록 방법: Gradle에서는 annotationProcessor 설정을 활용하여 프로세서를 쉽게 등록할 수 있습니다.

예시 (Gradle 설정):

dependencies {
    annotationProcessor 'org.projectlombok:lombok:1.18.20'
}

보완 내용:

  1. javax.annotation.processing.Processor 대신 AbstractProcessor 사용 권장:
    • 대부분의 경우 javax.annotation.processing.AbstractProcessor를 상속받아 사용하는 것이 더 편리합니다. AbstractProcessor는 기본적인 메서드와 필드를 제공하여 코드 작성량을 줄여줍니다.
    • 따라서 "javax.annotation.processing.Processor 인터페이스 구현" 대신 "javax.annotation.processing.AbstractProcessor 클래스 상속"을 권장합니다.
  2. 코드 수정 기능에 대한 부정확한 설명:
    • 애노테이션 프로세서는 기존 소스를 직접 수정하는 방식으로 동작하지 않습니다. 대신 새로운 소스 파일을 생성하거나 컴파일 과정에서만 영향을 미치는 방식으로 작동합니다.
    • 따라서 "기존 코드를 수정하거나 새로운 코드를 삽입할 수 있습니다"는 "새로운 코드를 생성하거나 컴파일 오류를 발생시켜 개발자를 돕는 데 사용됩니다"로 수정하는 것이 적절합니다.
  3. 애노테이션 프로세서와 런타임 애노테이션의 구분 필요:
    • 애노테이션 프로세서는 컴파일 타임에 동작하며, 런타임에 동작하는 리플렉션 기반 애노테이션과는 다릅니다.
    • 따라서 "컴파일 타임 처리"라는 표현을 명확히 하고, 런타임 애노테이션과의 차이점을 추가로 언급할 필요가 있습니다.
  4. META-INF 파일 생성 관련 최신 방식 추가:
    • 최근에는 annotationProcessor로 빌드 시스템(예: Gradle, Maven)에서 애노테이션 프로세서를 등록하는 것이 일반적입니다. META-INF/services 방식은 여전히 사용되지만, 빌드 시스템에서 더 쉽게 설정할 수 있음을 언급해야 합니다.

'코딩스터디 > JAVA스터디' 카테고리의 다른 글

Java 제네릭  (1) 2024.11.20
Java) I/O  (0) 2024.11.19
Java) Enum  (3) 2024.11.17
Java 멀티쓰레드 프로그래밍  (2) 2024.11.16
Java 예외 처리  (4) 2024.11.15