어노테이션

2023. 11. 26. 17:05java/java

어노테이션이란?

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

 

용도

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

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

 3. 런타임 시 특정 기능을 실행하도록 정보를 제공

 

 

 예를들어 @Override 어노테이션은 메소드가 오버라이드된 것임을 컴파일러에게 알려주어 컴파일러가 오버라이드 검사를 하도록 해준다.

 어노테이션은 빌드 시 XML 설정 파일을 생성하거나, 배포를 위해 JAR 압축 파일을 생성하는데에도 사용된다.

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

 

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

 어노테이션 타입을 정의하는 방법은 인터페스를 정의하는 것과 유사하다. @interface를 사용해서 어

@AnnotationName(elementName1 = "값1", elementNmae = "값2")
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

노테이션을 정의하며, 그 뒤에 어노테이션 이름이 온다.

public @interface AnnotationName {
}

 

이렇게 정의한 어노테이션은 코드에서 다음과 같이 사용한다.

@AnnotationName

 

어노테이션은 element를 멤버로 가질 수 있다. 각 엘리먼트는 타입과 이름으로 구성되며, 디폴트 값을 가질 수 있다.

public @interface AnnotationName {
    String elementName1();
    String elementName2() default "디폴트 값";
}

 

이렇게 정의한 어노테이션을 코드에서 적용할 때에는 다음과 같이 한다.

@AnnotationName(elementName1 = "값1", elementNmae = "값2")
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

 

elementName1은 디폴트 값이 없기 때문에 반드시 기술해야 한다. 또한 어노테이셔은 기본 엘리먼트인 value를 가질 수 있다.

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

===============================================

@AnnotationName("기본")
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

 기본 엘리먼트만 어노테이션의 원소로 있을 때는 원소 이름을 생략하고 바로 사용할 수 있으나 다른 원소와 함께있을 때는 value=" " 을  명시적으로 기술해줘야 한다.

 

 

어노테이션 적용대상

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

 

어노테이션이 적용될 대상을 지정할 때에는 @Target 어노테이션을 사용한다. @Target의 기본 엘리먼트인 value는 ElementType 배열을 값으로 가진다

  => (적용될 대상을 복수개로 지정하기 위함)

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE}) // => 어노테이션에만 적용 가능
public @interface Target {
    ElementType[] value();             // => 요소 복수개를 배열로 받음
}

 

사용법은 아래와 같다.

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface AnnotationName {
    String elementName1() default "디폴트 값1";
    String elementName2() default "디폴트 값2";
}

 

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

@AnnotationName(elementName1 = "값1")
public class Main {

    //@AnnotationName => 안됨
    public Main(String filedName) {
        FiledName = filedName;
    }

    @AnnotationName
    private String FiledName;
    
    @AnnotationName
    public void methodName() {
        
    }
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

 

 

어노테이션 유지 정책

어노테이션 정의 시 한 가지 더 추가해야 할 내용은 사용 용도에 따라 @AnnotationName을 어느 범위까지 유지할 것인지 지정해야 한다. 소스상에만 유지할 건지, 컴파일된 클래스까지 유지할 건지, 런타임 시에도 유지할 건지를 지정해야 한다.

RetentionPolicy 열거 상수 설명
SOURCE 소스상에서만 어노테이션 정보를 유지한다. 소스 코드를 분석할 때만 의미가 있으며, 바이트 코드 파일에는 정보가 없다.
CLASS 바이트 코드 파일까지 어노테이션 정보를 유지한다. 하지만 리플렉션을 이용해서 어노테이션 정보를 얻을 수 없다.
RUNTIME 바이트 코드 파일까지 정보를 유지하면서 리플렉션을 이용해서 런타임시에 어노테이션 정보를 얻을 수 있다.

 

어노테이션 유지정책을 지정할 때에는 @Retention 어노테이션을 사용한다. 코드 자동 생성 툴을 개발하지 않는 이상 우리가 작성하는 어노테이션은 대부분 런타임 시점에 사용하기 위한 용도로 만들어진다.

 

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationName {
    String elementName1() default "디폴트 값1";
    String elementName2() default "디폴트 값2";
}

 

 

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

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

 

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface PrintAnnotation {
    String value() default "-";
    int number() default 15;
}

 

메소드메만 적용가능하고 런타임 시까지 어노테이션 정보를 유지하도록 했다. 기본 엘리먼트는 구분선에 사용될 문자이고, number는 반복 출력 횟수이다.

 

public class PrintAnnotationExample {

    public static void main(String[] args) {
        //Service 클래스로부터 메소드 정보를 얻음
        Method[] declaredMethods = Service.class.getDeclaredMethods();


        for(Method method : declaredMethods) {
            //어노테이션 적용 확인
            if(method.isAnnotationPresent(PrintAnnotation.class)) {
                PrintAnnotation printAnnotation = method.getAnnotation(PrintAnnotation.class);

                System.out.println("[" + method.getName() + "]");

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

                System.out.println();

                try {
                    method.invoke(new Service()); // 서비스 객체 만든 후 메소드 실행
                } catch (Exception e) {
                    System.out.println();
                }
            }
        }
    }
}

'java > java' 카테고리의 다른 글

Generic  (1) 2023.12.04
중첩 클래스와 중첩 인터페이스란?  (0) 2023.11.29
UDP 네트워킹  (0) 2022.06.04
스레드 풀(1)  (0) 2022.05.07
TCP 네트워킹  (0) 2022.05.07