2023. 11. 26. 17:05ㆍjava/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();
}
}
}
}
}