본문 바로가기
Old Posts/Java

[Java] 자바 어노테이션(Annotation) 사용법 및 예제 - 커스텀 어노테이션 만들기

by A6K 2022. 10. 7.

스프링 프레임워크 기반의 작업을 많이 하는 개발자라면 '어노테이션(Annotation)'을 자주 사용할 것이다. (어노테이션, 애너테이션 등으로 발음하지만 본 포스트에서는 어노테이션으로 명명하겠다) 하지만 스프링 프레임워크를 사용하는 개발자들도 그냥 관용적으로 어노테이션들을 사용하는 경우가 많고, 자바 어노테이션 자체에 대해서 크게 고민해보지 않은 경우가 많이 있다.

1. 자바 어노테이션(Annotation)

자바에서 어노테이션은 사전적의미로는 주석이라는 뜻을 가지고 있다. 자바의 어노테이션은 소스코드에 추가해서 사용할 수 있는 메타 데이터의 일종이다. 메타 데이터란 애플리케이션이 처리해야할 데이터가 아니라 컴파일 과정과 실행 과정에서 코드를 어떻게 처리해야하는지를 알려주기 위한 추가 정보다.

자바의 어노테이션은 보통 '골뱅이(@)' 기호를 앞에 붙여서 사용한다. JDK 1.5 버전 이상에서부터 사용가능하며, 자바 어노테이션은 클래스 파일에 임베드되어 컴파일러에 의해 생성된 이후 JVM에 포함되어 동작한다.

자바 어노테이션을 이용하면 다음을 할 수 있다.

  • 컴파일러에게 코드 작성 문법 에러를 체크하도록 정보 제공
  • 소프트웨어 개발 환경이 빌드나 배포시 코드를 자동으로 생성할 수 있도록 정보 제공
  • 런타임에 특정 기능을 실행하도록 정보를 제공

자바 시스템에서 기본으로 제공하는 어노테이션들이 많이 있다. 필요에 따라서 사용자가 직접 커스텀 어노테이션들을 만들어서 사용할 수도 있다.

2. 자바 빌트인 어노테이션

자바에서 제공하는 어노테이션 중 개발시 자주 이용하는 것들을 나열해보자면,

@Override

메소드를 오버라이드하겠다는 의미로 메소드의 선언 앞에 붙여준다. 만약 상속받은 부모 클래스 또는 구현해야할 인터페이스에서 해당 메소드가 없다면 컴파일 오류가 발생한다.

@Override
public void getInfo() {
  System.out.println("test");
}

@Deprecated

메소드를 Deprecated 시킨다. 이 메소드를 사용하는 애플리케이션을 컴파일 할 경우 컴파일 경고가 발생한다. 하위호환을 위해서 메소드 자체를 없애지는 못하지만 사용하지 말 것을 사용자에게 알리고 싶을 때 붙여준다.

@Deprecated
public void deprecatedMethod() {
  System.out.println("test");
}

@SuppressWarnings

컴파일러 경고를 출력하지 않도록 설정한다. SuppressWarnings 어노테이션은 인자를 받는데 인자에 따른 의미는 다음과 같다.

  • @SuppressWarnings("all") : 모든 경고를 억제
  • @SuppressWarnings("cast") : 타입 캐스트관련 경고 억제
  • @SuppressWarnings("dep-ann") : 사용하지 말아야할 주석 관련 경고 억제
  • @SuppressWarnings("deprecation") : Deprecated 메소드를 사용한 경우 발생하는 경고 억제
  • @SuppressWarnings("fallthrough") : switch문에서 break 구문 누락 관련 경고 억제
  • @SuppressWarnings("finally") : finally 블럭 관련 경고 억제
  • @SuppressWarnings("null") : null 관련 경고 억제
  • @SuppressWarnings("rawtypes") : 제너릭을 사용하는 클래스 매개 변수가 특정되지 않았을 때의 경고 억제
  • @SuppressWarnings("unchecked") : 검증되지 않은 연산자 관련 경고 억제
  • @SuppressWarnings("unused") : 사용하지 않는 코드 관련 경고 억제

컴파일러 경고는 경고일 뿐, 경고 상황을 개발자가 알고 있는 경우에는 컴파일 로그가 지저분해지고 진짜 잡아야하는 경고들이 잘 보이지 않을 수 있기 때문에 이 어노테이션을 쓰게 된다.

@SafeVarargs

제너릭 같은 가변인자 매개변수를 사용할 때 경고를 무시

@FunctionalInterface

자바에서 람다 함수를 위한 인터페이스를 지정한다. 함수형 인터페이스에 사용하기 적합하지 않은 경우(메소드가 없거나 두 개 이상이라면)에는 컴파일 오류가 발생한다.

3. 메타 어노테이션

커스텀 어노테이션을 만들 때 사용하는 메타 어노테이션들도 있다. 메타 어노테이션에 대해서 잘 알고 있어야 정확히 원하는 커스텀 어노테이션을 만들어 사용할 수 있다.

@Retention

어노테이션의 리텐션 기간을 명명한다.

  • RetentionPolicy.Class
    • 바이트 코드 파일까지 어노테이션 정보를 유지한다
    • 리플렉션을 이용해서 어노테이션 정보를 얻을 수는 없다
  • RetentionPolicy.Runtime
    • 바이트 코드 파일까지 어노테이션 정보를 유지하면서 리플렉션을 이용해 런타임에 어노테이션 정보를 가져올 수 있다.
  • RetentionPolicy.Source
    • Compile 이후에는 삭제된다.

@Documented

자바 문서에도 어노테이션 정보가 표현된다.

@Target

생성할 어노테이션이 적용될 수 있는 위치를 나열한다.

  • ElementType.TYPE
    • 클래스, 인터페이스, 열거 타입
  • ElementType.ANNOTATION_TYPE
    • 어노테이션
  • ElementType.FILED
    • 필드
  • ElementType.CONSTRUCTOR
    • 생성자
  • ElementType.METHOD
    • 메소드
  • ElementType.LOCAL_VARIABLE
    • 로컬변수
  • ElementType.PACKAGE
    • 패키지

@Inherited

자식 클래스가 어노테이션을 상속 받을 수 있다.

@Repeatable

반복적으로 어노테이션을 선언할 수 있다.

4. 커스텀 어노테이션 만들기

커스텀 어노테이션을 만들려면 @interface [어노테이션 이름] 이라는 형태로 어노테이션을 정의해야한다. 어노테이션을 정의할 때, 생성되는 어노테이션에 대한 메타 어노테이션은 어노테이션 정의 앞쪽에 붙여준다.

예를들어 MyAnnotation 이라는 어노테이션을 생성하기 위해서는 다음과 같이 선언하면 된다.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
        String value() default "MyAnnotation : default value"
}

이 어노테이션은 메소드에 붙일 수 있고, RUNTIME에 적용된다. 새로 만든 커스텀 어노테이션은 @어노테이션이름으로 메소드 앞에 붙여서 사용할 수 있다.

class MyObject {
    @MyAnnotation
    public void testMethod1() {
        System.out.println("This is testMethod1");
  }

    @MyAnnotation(value = "My new Annotation")
    public void testMethod1() {
        System.out.println("This is testMethod1");
  }
}

이렇게 만든 어노테이션은 자바의 '리플렉션(Reflection)'을 활용하여 특정 목적으로 사용할 수 있다. 여기까지 어노테이션은 그저 문자열 정보를 가지고 있을 뿐이다.

public class MyMain {
    public static void main(String[] args) {
        Method[] methodList = MyObject.class.getMethods();

        for (Method method : methodList) {
            if(method.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation annotation=method.getDeclaredAnnotation(MyAnnotation.class);
                String value=annotation.value();
                System.out.println(method.getName() + ":" + value);
            }
        }
    }
}

이런 식으로 리플렉션을 통해 어노테이션의 값을 가져올 수 있다.

롬복이나 스프링 등에서 사용하는 어노테이션의 경우에는 어노테이션 프로세서를 통해서 AST를 수정하는 경우도 있다. 이 경우 어노테이션을 붙여서 자동으로 Getter/Setter를 만들어주는 경우도 있다.

댓글