예제를 통해 어노테이션의 큰 그림을 이해하기

L-cloud·2023년 9월 5일
0

스터디

목록 보기
2/5
post-thumbnail

Java를 학습하다 보면 @Bean , @ComponentScan, @Override 등 이런 어노테이션을 꽤 자주 마주칩니다. 대체 저 @가 무엇이기에 @Bean, @Component 과 같은 어노테이션이 붙은 클래스 혹은 매서드를 찾아낼 수 있고 컴파일 단계에서 오류를 검출할 수 있을까요? 어노테이션을 공부하면서 원리를 간략하게 알아봅시다! (파이썬의 데코레이터가 생각나셨다면 정상입니다! 데코레이터에 관련된 글은 여기서 확인해 보세요)

본 글은 깊은 내용보다는 추상적으로 어노테이션이 무엇이고 어떻게 사용하고 동작하는지 이해하는데 초점을 두고 있습니다. 글을 다 읽고 무엇을 공부할지 대강 감을 잡을 수 있으면 좋겠습니다.

본문에서 알아볼 내용

  1. 어노테이션 이란?
  2. 어노테이션의 종류
  3. 예시를 통해 이해하기
  4. 어노테이션과 리플랙션 및 어노테이션 프로세서

어노테이션과 처리 과정

어노테이션이 무엇인지 우선 공식 문서를 확인해 봅시다.

Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

코드에 영향을 주지 않는 메타 정보라고 합니다.

  • Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
  • Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
  • Runtime processing — Some annotations are available to be examined at runtime.

또한 컴파일러에 정보를 주거나 컴파일 및 배포 혹은 런타임 처리에 사용할 수 있다고 합니다.

어노테이션이 처리되는 과정을 간략히 살펴 봅시다. 자세한 내용

어노테이션 프로세서는 기존 소스 파일을 수정하지 않고 새로운 파일을 생성 하기에 기존 코드에 영향을 주지 않습니다!

어노테이션 종류

우선 어노테이션은 크게 세 가지로 구분이 됩니다.

  • 표준 어노테이션 → 자바에서 기본적으로 제공하는 어노테이션입니다. @Override, @Functionalinterface 등이 있습니다.
  • 메타 어노테이션 → 어노테이션을 위한 어노테이션으로 어노테이션의 적용 대상이나 유지 기간 등을 지정하는 데 사용합니다.
  • 사용자 어노테이션 → 사용자가 만든 어노테이션 입니다.

사용자 어노테이션을 만들기 전에 메타 어노테이션 두 개만 알아봅시다.

@Reteantion : 어노테이션이 어느 시점까지 유지되어야 하는지를 지정합니다. 앞서 이야기한 컴파일 및 배포 혹은 런타임 중 어느 시점까지인지 정한다고 생각하면 됩니다.

  • @Retention(RetentionPolicy.SOURCE) -> 소스 파일에만 존재. 클래스 파일에는 존재하지 않음 즉,컴파일 때 사라짐
  • @Retention(RetentionPolicy.CLASS) -> 클래스 파일에 존재. 런타임에 사용불가. 기본값.
  • @Retention(RetentionPolicy.RUNTIME) -> 클래스 파일에 존재. 실행시에 사용 가능

@Target : 어디에 적용할 수 있는지 (예: 메소드, 패키지, 필드, 생성자, 애너테이션 등)를 지정합니다.

더 자세한 정보는 여기를 확인해 보세요. 이와 관련된 내용은 다른 블로그에서 쉽게 찾을 수 있기에 넘어가도록 하겠습니다.

어노테이션 만들어 보기

그럼 이제 사용자 어노테이션을 만들어 봅시다. 문법은 인터페이스를 만드는 것과 유사합니다. @interface를 대신 작성해주면 됩니다.

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

@Retention(RetentionPolicy.RUNTIME) // 런타임까지 어노테이션 유지
@Target(ElementType.TYPE) // Class, interface (including annotation interface), enum, or record
public @interface MyRuntimeAnnotation {
    String value() default "default value";
}

// Retention, Target을 작성해주지 않으면 Default 값이 됩니다. 

참고 : 너무 복잡하게 생각하지 마세요! 저는 한 번에 이해하려고 하다가 시간만 날리고 말았습니다. 우선 어노테이션을 코드에 영향을 주지 않는 메타 정보를 주입하는 문법으로 생각해 보세요. 처음부터 for 문을 배웠을 때를 떠올려 봅시다. 종료 조건을 컴퓨터가 어떻게 판별하는지, 어떻게 반복하는지를 공부하는 것이 아니라 그냥 for 문의 문법을 이해하는 데 초점을 맞추었습니다. 어노테이션도 이와 마찬가지입니다.

이제 이를 적용해볼까요?

public class Main {
	
	public static void main(String[] args) {
		A a = new A();
		System.out.println(a.value()); // 여기!
	}
}

@MyRuntimeAnnotation
class A{}

위 코드는 어떻게 될까요? default value 가 출력이 될까요? 아니요 컴파일 오류가 발생합니다!

왜요? 런타임에 사용할 수 있고 컴파일 시점에 남아 있다면서요? 다시 한번 더 이야기하지만 어노테이션은 코드에 영향을 주지 않는 메타 정보 입니다! A 클래스에서 value() 를 호출할 수 있다면 A 코드에 영향을 주는 것이겠죠? 그럼 메타 정보를 어디서 어떻게 확인할 수 있을까요?
A.class 파일을 조금 자세히 봅시다. RuntimeVisibleAnnotations 이 따로 추가된 것을 확인할 수 있습니다. (RetentionPolicy 에 따라 다를 수 있겠죠?)

javap -v A
...
  Compiled from "Main.java"
class A
 ...
  #10 = Utf8               RuntimeVisibleAnnotations
...
SourceFile: "Main.java"
RuntimeVisibleAnnotations:
  0: #11()
    MyRuntimeAnnotation

그럼 어떻게 사용하라는 거야?! 하는 의문이 들 수 있습니다. 이때 나오는 것이 바로 리플렉션 입니다!

리플랙션

리플랙션이란 런타임에 동적으로 특정 클래스의 정보를 추출할 수 있는 기법입니다. 이제 리플랙션을 이용해서 어노테이션 정보를 가져와 봅시다!

public class Main {

    public static void main(String[] args) {
        A a = new A();

        Annotation[] annotations = a.getClass().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof MyRuntimeAnnotation) {
                MyRuntimeAnnotation myAnnotation = (MyRuntimeAnnotation) annotation;
                System.out.println("Annotation: " + annotation);  // Annotation: @MyRuntimeAnnotation(value=default value)
                System.out.println("Value: " + myAnnotation.value()); // Value: default value
            }
        }
    }
}

객체 생성 없이 클래스 정보를 가져오거나 특정 패키지 내의 클래스 모두 가져오는 것도 가능합니다. 물론 해당 클래스 정보가 JVM에 없으면 클래스 로더에 의해서 로딩이 됩니다.

그럼 스프링에서 어떻게 bean을 등록하는지 큰 그림이 그려지시죠? 구체적인 것은 코드를 봐야겠지만 패키지 내의 클래스 파일들을 읽어보고 @Component , @Bean 어노테이션이 붙은 클래스를 식별할 것이라 봅니다. ClassPathScanningCandidateComponentProvider 키워드로 검색하면 뭔가 나올 것 같네요.

어노테이션 프로세서

그럼 @Retention(RetentionPolicy.RUNTIME) 이 아닌 경우는 어떻게 될까요? 리플랙션을 사용하지 못하는데? 이때는 어노테이션 프로세서를 생성 해서 사용합니다. 어노테이션 프로세서는 컴파일 단계에서 Annotation에 정의된 로직을 동작하게 해줍니다. 개발자는 이 로직을 직접 작성 할 수 있습니다.

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("com.package.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)) {
            // 여기에서 규칙을 확인합니다. 예를 들어, 메소드 이름이 "myMethod"인지 확인합니다.
            if (!element.getSimpleName().toString().equals("myMethod")) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Method should be named 'myMethod'", element);
            }
        }
        return true;
    }
}

추가로 META-INF/services/javax.annotation.processing.Processor 파일을 생성하고 어노테이션 프로세서를 등록해야합니다. 그럼 특정 조건을 만족하지 않으면 컴파일 타임에 오류가 발생하는 어노테이션을 만들 수 있겠죠.

마무리

개인적으로 어노테이션을 공부할 때 마법처럼 느껴져 이해하기 어려웠습니다. 대부분의 글과 책에서는 어노테이션의 정의와 미리 정의된 어노테이션에 대한 설명, 그리고 어노테이션을 만드는 방법에 대해서만 다루고 있었기 때문이었습니다. 너무 구체적인 내용만 있고 도대체 저 어노테이션이 부여한 메타 정보를 어떻게 사용하는지는 찾기 어려웠습니다. 우선 자세한 내용보다는 큰 틀에서 어노테이션을 살펴보니 이후 어떤 것을 공부하면 좋을지에 대한 방향이 잡히기 시작했고 이전보다 빠르게 이해가 갔습니다.

지적은 언제나 환영입니다. 긴 글 읽어 주셔서 감사합니다!

자세한 내용을 공부하기 좋은 곳들

리플렉션

어노테이션

어노테이션 프로세서

더 공부하면 좋을 것들

  • Retension의 CLASS 정책은 왜 필요한가?
  • 리플랙션
  • 어노테이션 프로세서
  • 어노테이션 처리 과정
profile
내가 배운 것 정리

1개의 댓글

comment-user-thumbnail
2023년 9월 6일

어노테이션은 깊게 생각 안해봤었는데 읽어보니 내부 구조가 대략적으로 이해가 되네요. 포스팅 감사합니다!

답글 달기