티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.

 


어노테이션은 메타데이터(metedata)라고 볼 수 있습니다. 메타데이터란 애플리케이션이 처리해야 할 데이터가 아니라, 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지를 알려주는 정보입니다. 어노테이션은 다음과 같은 형태로 작성됩니다.

@AnnotationName

어노테이션은 다음 세 가지 용도로 사용됩니다.

  • 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공
  • 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보를 제공
  • 실행 시(런타임 시) 특정 기능을 실행하도록 정보를 제공

(즉, 어떤 클래스 혹은 메서드 혹은 필드를 처리할 때 특정한 매뉴얼을 제시하고 싶을 때 사용한다고 이해하면 쉬울 것 같습니다. '이 클래스는 여느 클래스들과는 다르게 이런 식으로 처리해라, 뭘 더 해라'와 같은 매뉴얼입니다.)

 

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

어노테이션 타입을 정의하는 방법은 인터페이스를 정의하는 것과 유사합니다. 다음과 같이 @interface를 사용해서 어노테이션을 정의하며, 그 뒤에 사용할 어노테이션 이름이 옵니다.

public @interface AnnotationName
{
	...
}

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

@AnnotationName

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

public @interface Annotation
{
	타입 elementName1();               //엘리먼트 선언
	타입 elementName2() default 값;    //디폴트 값을 가지는 엘리먼트 선언
}

엘리먼트의 이름 뒤에는 메서드를 작성하는 것처럼 ()를 붙여야 합니다. 예를 들어 String 타입과 int 타입의 엘리먼트를 다음과 같이 선언할 수 있습니다.

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

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

@AnnotationName(elementName1 = "문자열데이터", elementName2 = 12);
@AnnotationName(elementName1 = "문자열데이터");    //elementName2는 디폴트 값이 있으므로 생략가능

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

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

value 엘리먼트를 가진 어노테이션을 코드에서 적용할 때에는 다음과 같이 값만 기술할 수 있습니다. 이 값은 기본 엘리먼트인 value 값으로 자동 설정됩니다.

@AnnotationName("문자열데이터");    //elementName은 디폴트 값이 있으므로 생략가능

만약 value 엘리먼트와 다른 엘리먼트의 값을 동시에 주고 싶다면 다음과 같이 정상적인 방법으로 지정하면 됩니다.

@AnnotationName(value = "문자열데이터", elementName = 12);

 

어노테이션 적용 대상

어노테이션을 적용할 수 있는 대상은 java.lang.annotation.ElementType 에 열거 상수로 다음과 같이 정의되어 있습니다.

 

(어노테이션이 어떤 매뉴얼이라고 이해했다면, 이 매뉴얼을 어디에, 누구에게 적용해야 하는지도 바르게 명시해 줘야겠죠. 이 매뉴얼은 클래스에만 적용해라, 혹은 필드, 혹은 메서드에만 적용해라와 같이 말해줄 수 있어야 합니다. 이렇게 매뉴얼 적용 대상을 지정할 수 있게 열거형으로 정리해 둔 것이 아래와 같습니다.)

 

ElementType 열거 상수 적용 대상
ANNOTATION_TYPE 어노테이션
CONSTRUCTOR 생성자
FIELD 필드, 열거, 상수
LOCAL_VARIABLE 지역 변수
METHOD 메서드
MODULE 모듈
PACKAGE 패키지
PARAMETER 메서드 파라미더(매개 변수)
RECORD_COMPONENT record 컴포넌트
TYPE 클래스, 인터페이스(어노테이션 타입 포함), 열거, record
TYPE_PARAMETER 타입 매개변수
TYPE_USE 타입 사용

 

어노테이션이 적용될 대상을 지정할 때는 @Target 어노테이션을 사용합니다. @Target도 어노테이션이므로 기본 엘리먼트인 value를 가집니다. @Target의 기본 엘리먼트인 value는 ElementType 배열을 값으로 가집니다. 이것은 어노테이션이 적용될 대상을 복수 개로 지정하기 위함입니다. 다음은 @Target 어노테이션을 사용하는 예입니다.

//@Target 어노테이션의 value는 배열의 형태이므로 하나를 지정해도 중괄호로 묶어줘야 함
@Target({ElementType.CONSTUCTOR})    //생성자를 대상으로 지정
@Target({ElementType.FIELD})         //필드, 열거, 상수를 대상으로 지정

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

 

 

어노테이션 유지 정책

어노테이션 정의 시 사용 용도에 따라 @AnnotationName을 어느 범위까지 유지할 것인지 지정해야 합니다. 쉽게 설명하면 컴파일 전까지만 유지할 것인지, 클래스를 로딩할 때까지만 유지할 것인지, 런타임 시에도 계속 유지하고 있을 것인지를 지정해야 합니다. 

 

(어노테이션을 매뉴얼이라고 이해했고, 또 이 매뉴얼을 누구에게 적용해야 하는지도 이해했다면, 이제는 이 매뉴얼을 언제까지 적용하고 있어야 하는지도 명확히 해야 할 것입니다. 바로 이 기간을 정해주는 것이 유지 정책이라고 볼 수 있습니다. 컴파일 하지 전까지만 유지할 것인지, 컴파일 후 클래스가 로딩될 때까지만 유지할 것인지, 컴파일 후 런타임 시에도 계속 유지하고 있을 것인지 선택할 수 있습니다.)

 

어노테이션 유지 정책은 java.lang.annotation.RetentionPolicy 에 열거 상수로 다음과 같이 정의되어 있습니다. 

 

RetentionPolicy 열거 상수 설명
CLASS 바이트 코드 파일까지 어노어노테이션 정보를 유지합니다. 하지만 리플렉션을 이용해서 어노테이션 정보를 얻을 수는 없습니다. 즉, 클래스를 로딩할 때까지만 적용합니다.
RUNTIME 바이트 코드 파일까지 어노테이션 정보를 유지하면서 리플렉션을 이용해서 런타임 시에 어노테이션 정보를 얻을 수 있습니다. 즉, 런타임 중에도 계속 유지합니다.
SOURCE 소스상에서만 어노테이션 정보를 유지합니다. 소스 코드를 분석할 때만 의미가 있으며, 바이트 코드 파일에는 정보가 남지 않습니다. 즉, 컴파일 전까지만 유지합니다.

 

리플렉션이란 런타임 시에 클래스의 메타 정보를 얻는 기능을 말합니다. 예를 들어 클래스가 가지고 있는 필드가 무엇인지, 어떤 생성자를 갖고 있는지, 어떤 메서드를 가지고 있는지, 적용된 어노테이션이 무엇인지 알아내는 것이 리플렉션입니다.

 

어노테이션 유지 정책을 지정할 때는 @Retention 어노테이션을 사용합니다. @Retention도 어노테이션이므로 기본 엘리먼트인 value를 가지고 있습니다. @Retention의 기본 엘리먼트인 value는 RetentionPolicy 타입이므로 위 세 가지 상수 중 하나를 지정하면 됩니다. 다음은 어노테이션 유지 정책을 적용한 예입니다.

@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.SOURCE)

 

기본적으로 제공되는 어노테이션

자바에서 기본적으로 제공되는 어노테이션은 다음과 같습니다.

 

어노테이션 설명
@Override 선언한 메서드가 상속 받는 상위 클래스클래스의 메서드를 Override 했다는 것을 의미합니다. 만약 상위 클래스에서 해당 메서드를 찾을 수 없다면 에러를 발생합니다.
@ Deprecated 해당 메서드가 더 이상 사용되지 않음을 의미합니다. 만약 해당 메서드가 사용될 경우 에러를 발생합니다.
@SuppressWarnings 선언한 곳의 컴파일 에러를 의도적으로 무시합니다.
@SafeVarargs java7부터 지원하며, 제네릭 같은 가변 인자의 매개 변수를 사용할 때의 경고를 무시합니다.
@FunctionalInterface java8부터 지원하며, 함수형 인터페이스를 지정합니다. 인터페이스의 추상 메서드 개수를 세어 1개가 아닌 경우에 에러를 발생합니다.

 

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

어노테이션 자체는 아무런 동작을 가지지 않는 단지 표식일 뿐이지만, 리플렉션을 이용해서 어노테이션의 적용 여부와 엘리먼트 값을 읽고 적절히 처리할 수 있습니다. 클래스에 적용된 어노테이션 정보를 얻으려면 java.lang.Class를 이용하면 되지만, 필드, 생성자, 메서드에 적용된 어노테이션 정보를 얻으려면 Class의 다음 메서드를 통해서 java.lang.reflect 패키지의 Field, Constructor, Method 타입의 배열을 얻어야 합니다.

 

리턴 타입 메서드명(매개 변수) 설명
Field[] getFields() 필드 정보를 Field 배열로 리턴
Constructor[] getConstructors() 생성자 정보를 Construtor 배열로 리턴
Method[] getDeclaredMethods() 메서드 정보를 Method 배열로 리턴

 

그런 다음 Class, Field, Construtor, Method가 가지고 있는 다음 메서드를 호출해서 적용된 어노테이션 정보를 얻을 수 있습니다.

 

리턴 타입 메서드명(매개 변수) / 설명
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
지정한 어노테이션이 적용되어 있으면 true를 리턴합니다. Class에서 호출했을 때 상위 클래스에 적용된 경우에도 true를 리턴합니다.
Annotation getAnnotation(Class<T> annotationClass)
지정한 어노테이션이 적용되어 있으면 어노테이션을 리턴하고, 그렇지 않다면 null을 리턴합니다. Class에서 호출했을 때 상위 클래스에 적용된 경우에도 어노테이션을 리턴합니다.
Annotation[] getAnnotations()
적용된 모든 어노테이션을 리턴합니다. Class에서 호출했을 때 상위 클래스에 적용된 어노테이션도 모두 포함합니다. 적용된 어노테이션이 없을 경우 길이가 0인 배열을 리턴합니다.
Annotation[] getDeclaredAnnotations()
직접 적용된 모든 어노테이션을 리턴합니다. Class에서 호출했을 때 상위 클래스에 적용된 어노테이션은 포함되지 않습니다.

 

어노테이션과 리플렉션을 이용해서 간단한 예제를 만들어 보겠습니다. 다음은 각 메서드의 실행 내용을 구분선으로 분리해서 콘솔에 출력하도록 하는 PrintAnnotation입니다.

//PrintAnnotation.java
package chapter00.exam00;

import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintAnnotation 
{
	String value() default "-";    //구분선에 사용될 문자
	int number() default 15;       //구분선에 사용될 문자를 반복 출력할 횟수
}

@Target은 메서드에만 적용하도록 했고, @Retention은 런타임 시까지 어노테이션 정보를 유지하도록 했습니다. 기본 엘리먼트 value는 구분선에 사용될 문자이고, number는 반복 출력 횟수입니다. 다음은 PrintAnnotation을 적용한 Service 클래스입니다.

//Service.java
package chapter00.exam00;

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

다음 클래스는 리플렉션을 이용해서 Service 클래스에 적용된 어노테이션 정보를 읽고 엘리먼트 값에 따라 출력할 문자와 출력 횟수를 콘솔에 출력한 후, 해당 메서드를 호출합니다.

//exam00.java
package chapter00.exam00;

import java.lang.reflect.Method;

public class exam00 
{
	public static void main(String[] args)
	{
		//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.print(printAnnotation.value());
				}
				System.out.println();
				
				try
				{
					//메서드 호출
					method.invoke(new Service());
				}
				catch (Exception e) {}
				System.out.println();
			}
		}
	}
}

/*
실행결과

[Method1]
---------------
실행 내용1

[Method2]
***************
실행 내용2

[Method3]
####################
실행 내용3

*/

실행결과는 꼭 저 순서대로 나오지는 않았습니다. 프로그램을 다시 실행시켜보면 순서가 무작위로 바뀌었습니다. 

method.invoke(new Service())는 Service 객체를 생성하고 생성된 Service 객체의 메서드를 호출하는 코드입니다.

'공부 일지 > JAVA 공부 일지' 카테고리의 다른 글

자바, 메서드 재정의  (0) 2021.04.08
자바, 상속  (0) 2021.04.08
자바, Getter와 Setter 메서드  (0) 2021.04.07
자바, 접근 제한자  (0) 2021.04.07
자바, import문  (0) 2021.04.07
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함