11장 애너테이션

Jasik·2021년 12월 12일
0

제어자처럼 사용.
애너테이션 자체로는 아무것도 하지 않으며, 도구가 있어야 유용하게 사용 가능

애너테이션 요소

키/값 쌍 요소
@Test(timeout=10000)

애너테이션 요소 다음 중 하나

  • 기본 타입 값
  • String
  • Class 객체
  • enum 인스턴스
  • 애너테이션
  • 위의 항목의 배열(배열의 배열은 안됨)

애너테이션 요소 기본값 가질 수 있음.

요소 이름이 value이고 이 요소만 지정할 때는 value= 생략 가능.
@SuppressWarnings("unchecked")

// 배열일 때 중괄호. 하나만 있으면 생략 가능
@BugReport(reportedBy={"Harry", "Fred"})

// 애너테이션 요소로 다른 애너테이션 사용 가능
@BugReport(ref=@Reference(id=112124), ...)

여러 애너테이션 가능
애너테이션이 반복가능으로 설정되었으면 같은 애너테이션 반복 가능.

선언부에 애너테이션

  • 클래스(enum 포함)와 인터페이스(애너테이션 인터페이스 포함)
  • 메서드
  • 생성자
  • 인스턴스 변수(enum 상수 포함)
  • 지역 변수(for와 try-with-resources 문 안에서 선언한 변수 포함)
  • 파라미터 변수와 catch 절의 파라미터
  • 타입 파라미터
  • 패키지
@Entity
public class User {...}

@SupressWarnings("unchecked")
List<User> users = ...;

public class Cache<@Immutable V> {...}

애너테이션 정의하기

@interface 문법을 사용해 애너테이션 인터페이스로 선언해야함.

@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    long timeout();
    ...
}

@interface 선언은 실제 자바 인터페이스를 만들어냄.

애너테이션을 처리하는 도구는 애너테이션 인터페이스를 구현하는 객체를 받음.

@Target, @Retention 메타애너테이션

@Target 의 값은 ElementType 객체 배열. 애너테이션에 적용할 수 있는 아이템을 나타냄.

@Target({ElementType.TYPE, ElementType.METHOD})
public @interface BugReport

@Retention : 애너테이션에 접근할 수 있는 위치를 지정.
RetentionPolicy.SOURCE: 애너테이션이 소스 핸들러에게는 보이지만 클래스 파일에는 포함되지 않는다
RetentionPolicy.CLASS: 애너테이션이 클래스 파일에 포함되지만 가상 머신은 해당 애너테이션을 로드하지 않는다. default
RetentionPolicy.RUNTIME: 애너테이션을 실행 시간에 이용할 수 있고 리플렉션 API를 통해 접근할 수 있다.

표준 애너테이션

컴파일용 애너테이션

@SuppressWarnings
컴파일러의 특정 유형의 경고를 억제하게함

@SafeVarargs
대상 메서드가 가변 인자 파라미터에 손상을 주지 않는다고 단정

@Generated
도구로 생성한 소스 코드와 개발자가 작성한 코드를 구별할 때 붙인다.

@FunctionalInterface
람다 표현식의 변환 대상을 나타내는 데 사용

리소스 관리용 애너테이션

@PostConstruct, @PreDestroy
웹 컨테이너와 애플리케이션 서버처럼 객체의 생명주기를 제어하는 환경에서 사용

@Resource
리소스 주입

메타 애너테이션

@Documented
자바독 같은 문서화 도구에 힌트를 제공.
문서화되는 애너테이션은 다른 문서화용 제어자(private, static 등)와 같은 식으로 취급해야 한다.

@Inherited
클래스용 애너테이션에만 적용. 클래스에 상속되는 애너테이션이 포함되면 서브클래스에서도 모두 자동으로 같은 애너테이션이 포함.

@Repeatable
같은 애너테이션을 여러 번 적용.

실행 시간에 애너테이션 처리하기

// inteface ToString
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ToString {
    boolean includeName() default true;
}
@ToString(includeName=false)
public class Point {
    @ToString(includeName=false)
    private int x;
    @ToString(includeName=false)
    private int y;
}

@ToString
public class Rectangle {
    @ToString(includeName=false) private Poion topLeft;
    @ToString private int width;
    @ToString private int height;
    ...
}

위 코드의 목적은 Rectangle 클래스 인스턴스를 Rectangle[[5,10],width=20,height=30] 와 같은 문자열로 표현하는 것이다.

핵심은 AnnotatedElement 인터페이스에 선언된 다음 메서드들이다.

T getAnnotation(Class<T>)
T getDeclaredAnnotation(Class<T>)
T[] getAnnotationsByType(Class<T>)
T[] getDeclaredAnnotationsByType(Class<T>)
Annotation[] getAnnotations()
Annotation[] getDeclaredAnnotations()

리플렉션 클래스인 Class, Field, Parameter, Method, Constructor, Package 가 AnnotatedElement 인터페이스를 구현한다.

public class ToStrings {
    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        
        Class<?> cl = obj.getClass();
        ToString ts = cl.getAnnotation(ToString.class);
        
        // 객체에 @ToString 이 없으면 객체의 toString() 결과를 반환
        if (ts == null) {
            return obj.toString();
        }
        
        StringBuilder = result = new StringBuilder();
        if (ts.includeName()) {
            result.append(cl.getName());
        }
        
        result.append("[");
        
        boolean isFirst = true;
        
        // 객체의 필드들을 본다
        for (Field f : cl.getDeclaredFields()) {
            ts = f.getAnnotation(ToString.class);
            if (ts != null) {
                if (isFirst) {
                    isFirst = false;
                } else {
                    result.append(",");
                }
                
                // private 필드도 접근 가능
                f.setAccessable(true);
                
                // includeName이 true이면 
                if (ts.includeName()) {
                    result.append(f.getName());
                    result.append("=");
                }
                
                try {
                    result.append(ToStrings.toString(f.get(obj)));
                } catch (ReflectiveOperationException ex) {
                    ex.printStackTrace();
                }
            }
        }
        
        result.append("]");
        return result.toString();
    }
}
profile
가자~

0개의 댓글