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 객체의 메소드를 호출
실행 결과)