Java

[JAVA] 어노테이션 (Annotation)

콩스프 2022. 12. 6. 22:31

Annotation 이란?

  • 어노테이션 (Annotation)메타데이터(metadata) 라고 볼 수 있음
메타데이터 (metadata)
애플리케이션이 처리해야 할 데이터가 아니라,
컴파일 과정실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지를 알려주는 정보

 

  • 어노테이션은 다음과 같은 형태로 작성됨 ( @+어노테이션 명)
@AnnotationName

 

어노테이션의 용도

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

+ ) 빌드시 자동으로 XML 설정 파일을 생성하는데 사용

+ ) 배포를 위한 JAR 압축 파일을 생성하는데 사용
+ ) 실행 시 클래스의 역할을 정의

 

어노테이션 EX)

  • 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공하는 대표적인 예 "@Override" 어노테이션
  • @Override는 메소드 선언시 사용
  • 메소드가 오버라이드 (재정의) 된 것임을 컴파일러에게 알려주어 컴파일러가 오버라이드 검사를 하도록 함
  • 정확히 오버라이드가 되지 않았다면 컴파일러는 에러를 발생시킴

 

 

어노테이션 타입 정의와 적용

  • 어노테이션 타입을 정의하는 방법은 인터페이스를 정의하는 것과 유사함
  • @interface를 사용하여 어노테이션을 정의하고, 그 뒤에 사용할 어노테이션 이름을 적음
public @interface AnnotationName {
}

 

  • 정의한 어노테이션은 코드에서 다음과 같이 사용함
@AnnotationName

 

  • 어노테이션은 엘리먼트 (element)를 멤버로 가질 수 있음
  • 각 엘리먼트는 타입과 이름으로 구성되며, 디폴트 값을 가질 수 있음
  • 엘리먼트의 타입 : 기본 데이터 타입 (EX int, double) 이나 String, 열거 타입, Class 타입, 이들의 배열 타입을 사용할 수 있음
  • 엘리먼트의 이름 뒤에는 메소드를 작성하는 것처럼 ()를 붙여야 함
public @interface AnnotationName {
    타입 elementName() [default 값];
    ...
}

 

어노테이션 엘리먼트 EX)

 

- Annotation 정의

public @interface AnnotationName {
    String elementName1();
    int elementName2() default 5;
}

 

- Annotation 사용

@AnnotationName(elementName="값", elementName2=3);
또는
@AnnotationName(elementName="값")
  • elementName1디폴드 값이 없기 때문반드시 값을 기술해야 함
  • elementName2 디폴트 값이 있기 때문 생략 가능

 

 

  • 어노테이션은 기본 엘리먼트 value를 가질 수 있음
public @interface AnnotationName {	
    String value(); //기본 엘리먼트 선언
    int elementName() defualt 5;
}

 

  • value 엘리먼트를 가진 어노테이션을 코드에서 적용할 때에는 값만 기술할 수 있음
  • 해당 값은 기본 엘리먼트인 value 값으로 자동 설정
@AnnotationName("값");

 

  • value 엘리먼트와 다른 엘리먼트의 값을 동시에 주고 싶다면 지정을 해주면 됨
@AnnotationName(value="값", elementName=3);

 

 

어노테이션 적용 대상

  • 어노테이션을 적용할 수 있는 대상은 java.lang.annotation.ElementType 열거 상수로 정의되어 있음
ElementType 열거 상수 적용 대상
TYPE 클래스, 인터페이스, 열거 타입
ANNOTATION_TYPE 어노테이션
FIELD 필드
CONSTRUCTOR 생성자
METHOD 메소드
LOCAL_VARIABLE 로컬 변수
PACKAGE 패키지

 

  • 어노테이션이 적용될 대상을 지정할 때는 @Target 어노테이션을 사용함
  • @Target기본 엘리먼트value ElementType 배열을 값으로 가짐
    : 어노테이션이 적용될 대상을 복수개로 지정하기 위함

Target Annotation 사용 EX)

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface AnntationName {
}
  • 이와 같이 어노테이션을 정의할 경우
@AnnotationName
public class ClassName {	
    @AnnotationName
    private String fieldName;
    
    //@AnnotationName (X) --- @Target에 CONSTRUCT가 없어 생성자에는 적용 못함
    public ClassName() { }
    
    @AnnotationName
    public void methodName() { }
}
  • 어노테이션을 정의할 때 적용대상에 생성자를 포함하지 않았기 때문에 생성자에서 Annotation을 적용하지 못함

 

 

어노테이션 유지 정책

  • 어노테이션 정의 시 사용 용도에 따라 @AnnotationName을 어느 범위까지 유지할 것인지 지정해야 함
    1) 소스상에만 유지
    2) 컴파일된 클래스까지 유지
    3) 런타임 시에도 유지

  • 어노테이션 유지 정책은 java.lang.annotation.RetentionPolicy 열거 상수로 정의되어 있음
RetentionPolicy 열거 상수 설명
SOURCE 소스상에서만 어노테이션 정보를 유지
소스 코드를 분석할 때만 의미 있으며, 바이트 코드 파일에는 정보가 남지 않음
CLASS 바이트 코드 파일까지 어노테이션 정보를 유지
리플렉션을 이용해서 어노테이션 정보를 얻을 수 없음
RUNTIME 바이트 코드 파일까지 어노테이션 정보를 유지
리플렉션을 이용해서 런타임 시에 어노테이션 정보를 얻을 수 있음

 

리플렉션 (Reflection) : 런타임 시에 클래스의 메타 정보를 얻는 기능
  • 클래스가 가지고 있는 필드가 무엇인지
  • 어떤 생성자를 갖고 있는지
  • 어떤 메소드를 가지고 있는지
  • 적용된 어노테이션이 무엇인지
  • 리플렉션을 이용해서 런타임 시에 어노테이션 정보를 얻으려면 어노테이션 유지 정책을 RUNTIME으로 설정해야 함
  • 어노테이션 유지 정책을 지정할 때에는 @Retention 어노테이션을 사용
  • @Retention의 기본 엘리먼트인 value는 RetentionPolicy 타입 이므로 세 가지 열거상수 중 하나를 지정하면 됨
  • 우리가 작성하는 어노테이션은 대부분 런타임 시점에 사용하기 위한 용도로 만들어 짐

 

런타임 유지 정책을 적용한 어노테이션 EX)

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationName {
}

 

 

어노테이션 정보 사용하기

  • 어노테이션 자체는 아무런 동작을 가지지 않는 단지 표식일 뿐
  • 리플렉션을 이용해서 어노테이션의 적용 여부엘리먼트 값을 읽고 적절히 처리 할 수 있음

  • 클래스에 적용된 어노테이션 정보를 얻으려면 java.lang.Class를 이용하면 됨
  • 필드, 생성자, 메소드에 적용된 어노테이션 정보를 얻으려면 Class의 메소드들을 통해
    java.lang.reflect 패키지의 Field, Constructor, Method 타입의 배열을 얻어야 함
리턴 타입 메소드명(매개 변수) 설명
Field[ ] getFields() 필드 정보를 Field 배열로 리턴
Constructor[] getConstructors() 생성자 정보를 Constructor 배열로 리턴
Method[] getDeclaredMethods() 메소드 정보를 Method 배열로 리턴

 

  • Class, Field, Constructor, Method가 가지고 있는 메소드를 호출해서 적용된 어노테이션 정보를 얻을 수 있음
리턴 타입 메소드명(매개 변수)
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
지정한 어노테이션이 적용되었는지 여부
Class에서 호출했을 때 상위 클래스에 적용된 경우에도 true를 리턴
Annotation getAnnotation(Class<T> annotationClass)
지정한 어노테이션이 적용되어 있으면 어노테이션을 리턴 그렇지 않다면 null을 리턴
Class에서 호출했을 때 상위 클래스에 적용된 경우에도 어노테이션을 리턴
Annotation[ ] getAnnotations()
적용된 모든 어노테이션을 리턴
Class에서 호출했을 때 상위 클래스에 적용된 어노테이션도 모두 포함
적용된 어노테이션이 없을 경우 길이가 0인 배열을 리턴
Annotation[ ] getDeclaredAnnotations()
직접 적용된 모든 어노테이션을 리턴
Class에서 호출햇을 때 상위 클래스에 적용된 어노테이션은 포함되지 않음

 

리플렉션 EX)

 

@Target({ElementType.METHOD)}
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintAnnotation {
    String value() default "-";
    int number() default 15;
}
  • 각 메소드의 실행 내용을 구분선으로 분리해서 콘솔에 출력하도록 하는 PrintAnnotation
  • @Target은 메소드에만 적용하도록 했음
  • @Retention은 런타임 시까지 어노테이션 정보를 유지하도록 함
  • 기본 엘리먼트 value는 구분선에 사용될 문자
  • number는 반복 출력 횟수

 

public class Service {
    @PrintAnnotation
    public void method1() {
        System.out.println("실행 내용1");
    }
    
    @PrintAnnotation("*")
    public void method2() {
        System.out.println("실행 내용2");
    }
    
    @PrintAnnotation(value="#", number=20)
    public void method3() {  	
        System.out.println("실행 내용3");
    }
}
  • PrintAnnotation을 적용한 Service 클래스

 

// Service 클래스로부터 메소드 정보를 얻음
// == Service 클래스에 선언된 메소드 얻기 (리플렉션)
Method[] declaredMethods = Service.class.getDeclaredMethods();

// Method 객체를 하나씩 처리
for (Method method : declaredMethods) {
    // PrintAnnotation이 적용되었는지 확인
    if (method.isAnnotationPresent(PrintAnnotation.class)) {
        // PrintAnnotation 객체 얻기
        PrintAnnotation printAnnotation = method.getAnnotation(PrintAnnotation.class);

        // 메소드 이름 출력
        System.out.println("[" + method.getName() + "]");

        // 구분선 출력
        for (int i = 0; i < printAnnotation.number(); i++) {
            System.out.println(printAnnotation.value());
        }
        System.out.println();

        try {
            // 메소드 호출
            method.invoke(new Service());
        } catch (Exception e) {}
        System.out.println();
    }
}
  • 리플랙션을 이용해서 Service 클래스에 적용된 어노테이션 정보를 읽음
  • 엘리먼트 값에 따라 출력할 문자와 출력 횟수를 콘솔에 출력한 후, 해당 메소드를 호출
  • method.invoke(new Service())는 Service 객체를 생성하고 생성된 Service 객체의 메소드를 호출

 

 

실행 결과)