과거에는 XML 파일 혹은 properties 라는 파일에 모든 자바의 설정을 했습니다. 어떤 설정이 어디에 쓰이려면 많은 시간이 소요되었고 한 프로젝트에 여러 담당자가 붙어 작업을 하는데에도 불편함이 많았습니다. 이러한 불편함을 해소한 것이 어노테이션입니다.
Oracle, The Java Tytorials 에서 정의하고 있는 어노테이션은 다음과 같습니다.
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
어노테이션은 메타 데이터의 한 형태로 프로그램에 대한 데이터를 제공하나 프로그램의 한 형태는 아니며, 코드의 동작에 직접적인 영향을 끼치지 않는다고 합니다.
이 어노테이션의 용도는 다음과 같습니다.
- Information for the compiler
- Compile-time and deployment-time processing
- Runtime processing
어노테이션은 컴파일러에게 정보를 제공하고 컴파일을 할 때와 설치할 때 작업을 지정해줍니다. 그리고 실행 시에 특정 기능을 실행하도록 정보를 제공해주는 역할을 합니다.
어노테이션은 JDK5 부터 등장을 하였으나, 활성화가 된 것은 JDK6 부터입니다. JDK6 기준 표준 어노테이션은 3가지로 @Override, @Deprecated, @SuppressWarnings 어노테이션이 있습니다.
JDK7 이후 등장한 어노테이션은 @SafeVarags, JDK8 에는 @FunctionalInterface 가 있습니다.
기본적인 어노테이션의 종류는 한정되어 있지만, 내가 원하는대로 커스텀이 가능한 어노테이션도 만들어 낼 수 있습니다. 때문에 어노테이션의 종류는 커스텀 어노테이션으로 인해 무궁무진합니다.
이 어노테이션은 리플렉션을 이용하면 어노테이션 지정만으로도 원하는 클래스를 주입할 수 있는 용도로도 사용이 가능하다고 합니다.
Override
- 컴파일러에게 오버라이딩 한다는 것을 알려 문법상 잘못된 부분이 있을 경우 문법 체크를 하여 컴파일 오류를 발생시킵니다. 또한 자식 클래스에 여러 개의 메서드가 정의 되어 있을 경우 어떤 메서드가 오버라이딩 된 메서드인지 확인할 수 있는 용도로도 유용하게 사용할 수 있습니다.
Deprecated
- 사용하지 않는 클래스, 메서드 혹은 필드 등임을 알립니다. IDE 에서는 취소선을 표시하여 더 이상 사용하지 않는 것임을 알립니다.
SuppressWarnings
- 컴파일러가 생성하는 오류 메세지를 억제합니다. 예로들어 Deprecated 어노테이션을 사용한 메서드를 호출할 일이 있을 때 해당 어노테이션을 사용하여 경고 메세지를 억제시켜 이후 발생하는 새로운 메세지와 혼동이 없도록 할 수 있습니다. 해당 어노테이션은 다음과 같이 사용하여 여러개의 경고 메세지를 한 번에 억제할 수 있습니다.
@SuppressWarnings({"unchecked", "deprecation"})
SafeVarargs
- JDK7부터 등장한 어노테이션, 정의한 메서드 혹은 생성자에서 안전하지 않을 수도 있다는 경고를 억제합니다. 즉 안전함을 보장하는 장치입니다.
FunctionalInterface
- JDK8부터 등장한 어노테이션, 해당 어노테이션은 인터페이스에서 단 하나의 추상 메서드만 가지는 인터페이스임을 알립니다. 해당 인터페이스를 사용하면 부적절한 메서드를 추가하거나 다른 인터페이스를 상속 받으면 컴파일 에러가 발생합니다.
해당 어노테이션의 사용 예시는 다음과 같습니다.
AnnotationOverrideParent.java
public class AnnotationOverrideParent {
public String annotations(String prac) {
return "annotations";
}
}
AnnotationOverrideChild.java
public class AnnotationOverrideChild extends AnnotationOverrideParent {
@Override
public String annotations(String name) {
return "annotations";
}
}
AnnotationOverrideChild 자식 클래스에서 AnnotationOverrideParent 클래스를 확장하였고, annotations 메서드를 재정의하고 매개변수를 부모 클래스와 다르게 정의하였습니다. 결과는 하단의 이미지와 같습니다.
이와 같이 컴파일러가 문법 체크를 하게 하여 잘못된 문법을 체크하는 용도로도 사용이 가능합니다.
@Deprecated
public class AnnotationDeprecation {
@Deprecated
String name;
@Deprecated
public String annotations() {
return "annotations";
}
}
@Deprecated
enum AnnotationsEnums {
}
@Deprecated
interface AnnotationInforface {
}
Deprecated 어노테이션은 위와 같이 class, enum, interface, field, method 와 같이 여러 곳에서 사용이 가능합니다. 해당 어노테이션을 사용하면 하기의 이미지와 같이 IDE 에서는 취소선을 표시하여 더이상 사용하지 않는 클래스임을 알립니다.
해당 어노테이션은 경고 메세지를 억제시킵니다. 위 이미지를 보면, deprecation 오류 메세지가 출력된 것을 확인해 볼 수 있습니다. 만일 해당 경고 메세지가 지속적으로 발생한다면, 해당 메서드 내에서 발생하는 새로운 추가 오류 메세지를 놓치게 될 수 있는데 이 때 SuppressWarnings 어노테이션을 사용하여 더이상 사용하지 않는다는 경고 메세지를 억제하여 새로운 경고 메세지를 바로 확인할 수 있도록 할 수 있습니다.
해당 어노테이션은 인터페이스에서 사용이 가능하며, 추상 메서드를 단 하나만 지정하게 합니다.
하기 이미지와 같이 추상 메서드를 두 개 이상 정의하려고 한다면 컴파일 오류가 발생하게 됩니다.
커스텀 어노테이션을 선언할 때 주로 사용되는 어노테이션으로 빌트인 어노테이션 외 나머지 어노테이션들입니다.
해당 어노테이션의 속성으로는 SOURCE, CLASS, RUNTIME 입니다.
어노테이션의 유효 시점을 설정하는 것으로, 순서는 다음과 같습니다.
SOURCE -> CLASS -> RUNTIME
SOURCE 은 어노테이션 정보가 컴파일을 했을 때 소멸됩니다.
CLASS 은 디폴트 값으로 컴파일러가 컴파일시에는 어노테이션의 메모리를 가져가나 실질적으로 런타임시에는 소멸되게 됩니다.
RUNTIME 은 어노테이션을 실행중에도 사용이 가능합니다. 즉 메모리에 적재된 상태입니다. JVM 이 자바 바이트코드가 담긴 클래스 파일에서 런타임 환경을 구성하고 런타임을 종료할 때까지 메모리가 살아있습니다.
해당 어노테이션은 어노테이션에 대한 정보가 Javadocs 문서에 포함된다는 것을 선언합니다.
import java.lang.annotation.Documented;
@Documented
@interface classPreamble {
}
해당 어노테이션은 어노테이션을 정의할 수 있는 위치를 지정합니다. 해당 어노테이션의 정의 가능 범위는 다음과 같습니다.
정말 다양합니다..
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
해당 어노테이션은 클래스를 선언할 때에만 적용이 되며, 부모 클래스에 정의된 어노테이션이 하위 어노테이션까지 적용이 됩니다. 즉 상위 클래스의 어노테이션이 자식 클래스에도 확장이 됩니다.
어노테이션의 반복 정의를 위한 어노테이션입니다.
반복할 어노테이션을 선언합니다.
import java.lang.annotation.*;
@Repeatable(AnnotationRepeatables.class)
public @interface AnnotationRepeatableTest {
String name();
}
어노테이션을 담을 어노테이션을 구현합니다.
@interface AnnotationRepeatables {
AnnotationRepeatableTest[] value();
}
테스트
@AnnotationRepeatableTest(name="1")
@AnnotationRepeatableTest(name="2")
public class AnnotationRepeatableResult {
}
어노테이션 프로세서는 컴파일 타임에서 사용자 정의 어노테이션의 소스 코드를 분석하고 처리하기 위해 사용됩니다.
대표적인 예로 Lombok과 JPA 가 있다고 합니다. Lombok 은 Getter 혹은 Setter, 생성자도 자동으로 생성해주는 역할을 합니다. 이 Lombok 은 어노테이션 프로세서를 사용해 표준적으로 작성해야 할 코드를 개발자 대신에 작성해주는 역할을 합니다.
다만, 컴파일된 자바의 클래스 파일을 수정할 수는 없습니다.
이 작동 원리는 다음과 같습니다.
- 어노테이션 클래스를 생성한다.
- 어노테이션 파서 클래스를 생성한다.
- 어노테이션을 사용한다.
- 컴파일하면, 어노테이션 파서가 어노테이션을 처리한다.
- 자동 생성된 클래스가 빌드 폴더에 추가된다.
생각보다 깊게 공부하지 못했던 영역이었기 때문에 아쉬움이 남습니다. 어노테이션을 공부하면서 생각보다 이해가 안되었던 부분이 많았는데 특히 왜, 어떨때 커스텀한 어노테이션을 만드는지 이에대한 필요성에 대해서 체감하지 못했기 때문에 다시 한 번 공부해 볼 필요성이 있을 것 같습니다.
The Java Tutorials, https://docs.oracle.com/javase/tutorial/java/annotations/index.html
자바의 신 17장, 어노테이션이라는 것도 알아야 한다.
자바 라이브 스터디 애노테이션 블로그 참고, https://github.com/whiteship/live-study/issues/12