본문 바로가기

Java 기본 문법 - 참조 서적 [이것이 자바다 - 한빛미디어]/4. 객체지향 프로그래밍

12. Java 자바 - 어노테이션

어노테이션(Annotation)

 

= 메타데이터(metadata)라고 볼 수 있다.

 

메타데이터 : 어플리케이션이 처리할 데이터가 아니라, 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고

                    처리할 것인지 알려주는 정보

 

 

어노테이션 작성 형태

 

@AnnotationName

 

어노테이션의 용도

 

- 컴파일러에게 코드 문법 에러를 체크하도록 정보 제공

 

- 소프트웨어 개발 툴이 빌드 또는 배치 시 코드를 자동으로 생성할 수 있도록 정보를 제공

 

- 실행 시(런타임 시) 특정 기능을 실행하도록 정보 제공

 

 

@Override 어노테이션

 

- 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공한다.

 

- 메소드 선언 시 사용되며, 메소드가 오버라이트(재정의)된 것임을 컴파일러에게 알려주어

  컴파일러가 오버라이트 검사를 하도록 한다.

 

- 오버라이드가 되지 않았다면, 컴파일러는 에러를 발생시킨다.

 

 

어노테이션은 자동으로 XML 설정 파일을 생성하거나, 배포를 위해 JAR 압축 파일을 생성하는데 사용된다.

 

실행 시 클래스의 역할을 정의 하기도 한다.

 


 

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

 

인터페이스를 정의하는 것과 유사하다.

 

@interface를 사용해서 어노테이션을 정의하며, 그 뒤에 사용할 어노테이션 이름이 온다.

 

public @interface AnnotationName {
}

 

이렇게 정의된 어노테이션은 아래와 같이 사용한다.

 

@AnnotationName

 

어노테이션은 엘리먼트(element) 를 멤버로 가질 수 있다.

 

각 엘리먼트는 타입과 이름으로 구성되며, 디폴트 값을 가질 수 있다.

 

public @interface AnnotationName {
    타입 elementName() [default 값];    // 엘리먼트 선언
    . . . 
}

 

엘리먼트의 타입으로

int, double 같은 기본타입

String, 열거 타입, Class 타입

그리고 이들의 배열 타입을 사용할 수 있다.

 

엘리먼트의 이름 뒤에는 메소드를 작성하는 것처럼 ( ) 를 붙여야 한다.

 

ex) String 타입 엘리먼트, int 타입 엘리먼트

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

 

이렇게 정의한 어노테이션을 코드에 적용할 때는 아래와 같이 기술한다.

 

@AnnotationName(elementName1 = "값", elementName2 = 3);

또는

@AnnotationName(elementName1 = "값");

 

elementName1 은 디폴트 값이 없기 때문에 반드시 값을 기술해야 하고,

elementName2 는 디폴트 값이 있기 때문에 생략 가능하다.

 

어노테이션은 기본 엘리먼트인 value를 가질 수 있다.

 

public @interface AnnotationName {
    String value();    // 기본 엘리먼트 선언
    int elementName() default 5;
}

 

value 엘리먼트를 가진 어노테이션을 코드에서 적용할 때는 아래와 같이 값만 기술할 수 있다.

이 값은 기본 엘리먼트인 value 값으로 자동 설정된다.

 

@AnnotationName("값");

 

만약 value 엘리먼트와 다른 엘리먼트의 값을 동시에 주고 싶다면, 아래와 같이 정상적 방법으로 지정한다.

 

@AnnotationName(value = "값", elementName = 3);

 


 

2. 어노테이션 적용 대상

 

적용 대상은 java.lang.annotation.ElementType 열거 상수로 아래와 같이 정의되어 있다.

 

ElementType 열거 상수

적용 대상

TYPE

클래스, 인터페이스, 열거 타입

ANNOTATION_TYPE

어노테이션

FIELD

필드

CONSTRUCTOR

생성자

METHOD

메소드

LOCAL_VARIABLE

지역 변수

PACKAGE

패키지

 

어노테이션을 적용할 대상을 지정할 때는 @Target 어노테이션을 사용한다.

 

@Target의 기본 엘리먼트인 value는 ElementType 배열을 값으로 가진다.

어노테이션이 적용될 대상을 복수 개로 지정하기 위함

 

ex) 아래와 같은 어노테이션을 정의할 경우

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})

public @interface AnnotationName {
}

 

다음과 같이 클래스, 필드, 메소드만 어노테이션을 적용할 수 있고, 생성자는 적용할 수 없다. (정의 안됨)

 

@AnnotationName
public class ClassName {
    @AnnotationName
    private String fieldName;
    
    //@AnnotationName  @Target에 CONSTRUCTOR 가 없어 생성자는 적용할 수 없다. (컴파일 에러)
    public ClassName() { }
    
    @AnnotationName
    public void methodName() { }
}

 


 

3. 어노테이션 유지 정책

 

어노테이션 정의 시 한 가지 더 추가할 내용은

 

사용 용도에 따라 @AnnotationName 을 어느 범위까지 유지할 것인지 지정해야 한다.

 

소스상에서만 유지할 것인지, 컴파일된 클래스까지 유지할 것인지, 런타임 시에도 유지할 것인지

결정해야 한다.

 

어노테이션 유지 정책은 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 {
}

 


 

4. 런타임 시 어노테이션 정보 사용하기

 

런타임 시에 어노테이션이 적용되었는지 확인하고, 엘리먼트 값을 이용해서 특정 작업을 수행하는 방법

 

어노테이션은 단지 표식일 뿐이라 아무런 동작을 하지 않지만,

 

리플렉션을 이용해서 어노테이션의 적용 여부와 엘리먼트 값을 읽고 처리할 수 있다.

 

클래스에 적용된 어노테이션 정보를 얻으려면 java.lang.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) 어노테이션 / 리플렉션 이용, 각 메소드의 실행 내용을 구분선으로 분리해서 콘솔에 출력하는

PrintAnnotation

 

PrintAnnotation.java : 어노테이션 정의

@Target( {ElementType METHOD} )
@Retention(RetentionPolicy.RUNTIME)

public @interface PrintAnnotation {
    String value() default "-";
    int number() default 15;
}

 

@Target : 메소드만 적용

@Retention : 런타임 시까지 어노테이션 정보 유지

value : 구분선에 사용될 문자, 디폴트 "-"

number : 반복 출력 횟수, 디폴트 15

 

Service.java : PrintAnnotation 을 적용한 Service 클래스

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");
    }
}    

 

2 번 라인 @PrintAnnotation : 엘리먼트의 기본값으로 적용

7 번 라인 @PrintAnnotation("*") : 기본 엘리먼트 value 값을 "*" 로 설정

12 번 라인 @PrintAnnotation("#") : 기본 엘리먼트 value 값을 "#" 으로 설정, number 값을 20 으로 설정

 

ServiceExample.java : Service 클래스에 적용된 어노테이션 정보 읽고, 엘리먼트 값에 따라

출력할 문자와 출력 횟수를 콘솔에 출력한 후, 해당 메소드를 호출한다.

 

method.invoke( new Service( ) ) : Service 객체를 생성, 생성된 Service 객체의 메소드 호출

PrintAnnotationExample.java

public class PrintAnnotationExample {
    public static void main(String[] args) {
        //Service 클래스로 부터 메소드 정보를 얻음
        Method[] declaredMethods = Service.class.getDeclaredMehtods();  //Service 클래스에 선언된 메소드 얻기(리플렉션)
        
        //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.print();
            }
        }
    }
}