본문 바로가기

Java

자바 어노테이션

스프링 프로젝트를 생성하면 @Component와 같은 키워드가 달려있는걸 볼 수 있다. 이걸 자바에선 어노테이션이라 부르는데, 클래스, 메서드, 변수에 달 수 있다고 한다.

어노테이션은 무엇인가?

어노테이션이란 프로그램에 추가적인 정보를 제공해주는 메타데이터라고 볼 수 있다. 여기서 메타데이터란 어플리케이션이 처리해야 할 데이터가 아니라 컴파일 과정과 런타임에서 코드를 어떻게 컴파일하고 처리할 것인지에 대한 정보를 말한다.

  • 이 메타데이터를 잘 이용하면 비즈니스 로직과 분리하여 대상의 유효성 검증, 값 주입, 역할 부여(기능 주입) 등을 수행할 수 있어 코드를 좀 더 깔끔하게 작성할 수 있게 된다.
  • 어노테이션은 옵션에 따라 컴파일 전까지만 유효하도록 처리될 수 있고, 컴파일 시기에 처리될 수도 있고, 런타임 시기에 처리될 수 있다.
  • Java의 리플렉션(실행 중인 자바 클래스의 정보를 가져오는 기능)을 사용하여 런타임에 어노테이션의 정보를 바탕으로 다양한 기능을 수행할 수 있기 때문에, AOP(관점지향 프로그래밍)을 구성하는데 도움을 줄 수 있다.

어노테이션은 빌트인 어노테이션과 메타 어노테이션으로 나뉘는데, 빌트인은 말 그대로 자바에서 선언해놓은 어노테이션들이고, 메타 어노테이션이 우리가 수정할 수 있는 영역이다.

어노테이션은 어떻게 만드는가?

@Target({ElementType.[적용대상]})
@Retention(RetentionPolicy.[정보유지되는 대상])
public @interface [어노테이션명]{
	public 타입 elementName() [default 값]
    ...
}

 

 

어노테이션의 필드 타입은 enum, String이나 기본 자료형, 기본 자료형의 배열만 사용할 수 있다.

 

메타 어노테이션의 종류는 다음과 같다.

@Retention : 컴파일러가 어노테이션을 다루는 방법을 기술, 어느 시점까지 영향을 미치는지를 결정
RetentionPolicy.SOURCE : 컴파일 전까지만 유효
RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효
RetentionPolicy.RUNTIME : 컴파일 이후 런타임 시기에도 JVM에 의해 참조가 가능(리플렉션)

@Target : 어노테이션 적용할 위치 선택
ElementType.PACKAGE : 패키지 선언
ElementType.TYPE : 타입 선언
ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언
ElementType.CONSTRUCTOR : 생성자 선언
ElementType.FIELD : 멤버 변수 선언
ElementType.LOCAL_VARIABLE : 지역 변수 선언
ElementType.METHOD : 메서드 선언
ElementType.PARAMETER : 전달인자 선언
ElementType.TYPE_PARAMETER : 전달인자 타입 선언
ElementType.TYPE_USE : 타입 선언

@Documented : 해당 어노테이션을 Javadoc에 포함시킴
@Inherited : 어노테이션의 상속을 가능하게 함
@Repeatable : Java8 부터 지원하며, 연속적으로 어노테이션을 선언할 수 있게 함

 

어노테이션은 어떻게 동작하는가?

어노테이션은 특별한 종류의 인터페이스로 취급한다. 일반적인 인터페이스와 구분하기 위해 @기호와 interface 키워드를 붙여 선언한다.

어노테이션은 그 자체로는 아무것도 해주는게 없지만, 메타데이터이기 때문에 이 정보를 활용할 수 있는 다른 매개체가 필요하다.

정보를 이용하기 위해선 어노테이션에 대한 접근이 필요하고, 클래스 메소드와 필드에 대한 정보를 얻고 싶으면, 리플렉션을 이용하여 얻어야 한다.

1. 내 어노테이션 만들기

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String nickname() default "no nickname";
}
@MyAnnotation(nickname = "lion")
public class MyId {
    String name;
    int age;

    public MyId(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }
}
public class AnnotationStudy {
    public static void main(String[] args) {
        MyId myId = new MyId("John", 10);
        AnnotationStudy as = new AnnotationStudy();
        as.printNickname(myId);
    }
    public void printNickname(MyId myId) {
    //MyId 클래스의 인스턴스들 중에서 Annotation이 붙은 것들을 모두 가져옴
        Annotation[] annotations = MyId.class.getDeclaredAnnotations();
        for(Annotation annotation : annotations){
            if(annotation instanceof MyAnnotation){
                MyAnnotation m = (MyAnnotation) annotation;
                System.out.println(myId.getName() + "'s nickname is " + m.nickname() + ".");
            }
        }
    }
}
/*
Output:
John's nickname is lion
*/