💡 목표 : 자바의 애노테이션에 대해 학습해보자.
⚡️ 목차
- 애노테이션 정의하는 방법
- @retention
- @target
- @documented
- 애노테이션 프로세서
애노테이션이란 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공한다. 애노테이션은 프로그램에 대한 추가적인 정보를 제공하는데 사용한다. 쉽게 이해하기 위해 밑에 그림을 보자.
위 그림은 과거의 파일 관리 방법이었다. 자바 코드와 관련 설정 파일을 따로 저장하고 ver@.@
로 구분하여 관리했다. 위와 같이 관리를 하는데 두 가지의 어려움이 있었다.
그래서 다음과 같은 관리 방법을 사용하게 되었다.
/** ~ */
에 소스코드에 대한 정보를 저장하고, 소스코드의 주석으로부터 HTML 문서를 생성해내는 javadoc.exe라는 프로그램을 만들어서 사용했다.javadoc.exe
)을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애노테이션이다.javadoc ?
메타데이터의 한 형태인 주석은 프로그램 내에 일부가 아닌, 프로그램에 대한 데이터를 제공한다.
메타데이터 ?
데이터에 대한 데이터이며, 간단히 정리하자면 어떤 목적을 가지고 만들어진 데이터라고 할 수 있다.
@Test // 해당 메서드는 Test 대상임을 프로그램에게 알리는 역할을 한다.
public void method() {
.....
}
애노테이션은 사용할 때 여러가지의 용도가 있다.
커스텀 애노테이션이라고 부르며 이와 관련된 소스는 java.lang.annotation 패키지에 속해있다. 커스텀 애노테이션을 정의하려면 interface
키워드를 쓰고 앞에 @
를 붙여주면 애노테이션이 정의된다.
@interface MyAnnotation {
// 애노테이션의 요소를 선언한다.
}
햄버거를 나타내고 두 개의 요소를 가지고 있는 애노테이션을 테스트로 만들어보면
@interface Hamburger {
String patty(); // 패티를 의미하는 요소
String source(); // 소스를 의미하는 요소
}
인터페이스 공부할 때 인터페이스에서 추상 메서드를 선언할 수 없는 것처럼, 애노테이션도 추상 메서드로 선언한다. 추상 메서드로 선언하지만 구현할 필요는 없다. 대신 사용하는 쪽에서 애노테이션에 있는 모든 요소들의 값을 다 넣어줘야 한다. 이때 순서는 상관없다.
@interface BicMacAnnotation {
int pickle();
String bread();
String[] pattys();
String source();
}
@BicMacAnnotation(pickle = 3, bread = "BicMacBurn",
source = "BicMacSource", pattys = {"10 : 1 patty", "10 : 1 patty"})
public class CustomAnnotationTest {
.....
}
@interface MyAnnotation {
int count() default 1;
}
@MyAnnotation // @MyAnnotation(count = 1)과 동일한 결과.
public class MyClass {
.....
}
@interface MyAnnotation {
String value();
}
@MyAnnotation("test") // @MyAnnotation(value = "test")과 동일한 결과.
public class MyClass {
.....
}
{}
중괄호를 사용해야 한다.@interface MyAnnotationArr {
String[] texts();
}
@MyAnnotation(texts={"test", "test1"})
@MyAnnotation(texts="test") // 값이 하나있을 땐 {} 괄호를 작성 X
@MyAnnotation({}) // 값이 없을 땐 {} 괄호가 반드시 있어야 한다.
public class MyClass {
......
}
java.lang.annotation.Annotation 인터페이스가 모든 애노테이션의 조상이다.
package java.lang.annotation;
/**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*
* More information about annotation types can be found in section 9.6 of
* <cite>The Java™ Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
/**
* Returns true if the specified object represents an annotation
* that is logically equivalent to this one. In other words,
* returns true if the specified object is an instance of the same
* annotation type as this instance, all of whose members are equal
* to the corresponding member of this annotation, as defined below:
* <ul>
* <li>Two corresponding primitive typed members whose values are
* <tt>x</tt> and <tt>y</tt> are considered equal if <tt>x == y</tt>,
* unless their type is <tt>float</tt> or <tt>double</tt>.
*
* <li>Two corresponding <tt>float</tt> members whose values
* are <tt>x</tt> and <tt>y</tt> are considered equal if
* <tt>Float.valueOf(x).equals(Float.valueOf(y))</tt>.
* (Unlike the <tt>==</tt> operator, NaN is considered equal
* to itself, and <tt>0.0f</tt> unequal to <tt>-0.0f</tt>.)
*
* <li>Two corresponding <tt>double</tt> members whose values
* are <tt>x</tt> and <tt>y</tt> are considered equal if
* <tt>Double.valueOf(x).equals(Double.valueOf(y))</tt>.
* (Unlike the <tt>==</tt> operator, NaN is considered equal
* to itself, and <tt>0.0</tt> unequal to <tt>-0.0</tt>.)
*
* <li>Two corresponding <tt>String</tt>, <tt>Class</tt>, enum, or
* annotation typed members whose values are <tt>x</tt> and <tt>y</tt>
* are considered equal if <tt>x.equals(y)</tt>. (Note that this
* definition is recursive for annotation typed members.)
*
* <li>Two corresponding array typed members <tt>x</tt> and <tt>y</tt>
* are considered equal if <tt>Arrays.equals(x, y)</tt>, for the
* appropriate overloading of {@link java.util.Arrays#equals}.
* </ul>
*
* @return true if the specified object represents an annotation
* that is logically equivalent to this one, otherwise false
*/
boolean equals(Object obj);
/**
* Returns the hash code of this annotation, as defined below:
*
* <p>The hash code of an annotation is the sum of the hash codes
* of its members (including those with default values), as defined
* below:
*
* The hash code of an annotation member is (127 times the hash code
* of the member-name as computed by {@link String#hashCode()}) XOR
* the hash code of the member-value, as defined below:
*
* <p>The hash code of a member-value depends on its type:
* <ul>
* <li>The hash code of a primitive value <tt><i>v</i></tt> is equal to
* <tt><i>WrapperType</i>.valueOf(<i>v</i>).hashCode()</tt>, where
* <tt><i>WrapperType</i></tt> is the wrapper type corresponding
* to the primitive type of <tt><i>v</i></tt> ({@link Byte},
* {@link Character}, {@link Double}, {@link Float}, {@link Integer},
* {@link Long}, {@link Short}, or {@link Boolean}).
*
* <li>The hash code of a string, enum, class, or annotation member-value
I <tt><i>v</i></tt> is computed as by calling
* <tt><i>v</i>.hashCode()</tt>. (In the case of annotation
* member values, this is a recursive definition.)
*
* <li>The hash code of an array member-value is computed by calling
* the appropriate overloading of
* {@link java.util.Arrays#hashCode(long[]) Arrays.hashCode}
* on the value. (There is one overloading for each primitive
* type, and one for object reference types.)
* </ul>
*
* @return the hash code of this annotation
*/
int hashCode();
/**
* Returns a string representation of this annotation. The details
* of the representation are implementation-dependent, but the following
* may be regarded as typical:
* <pre>
* @com.acme.util.Name(first=Alfred, middle=E., last=Neuman)
* </pre>
*
* @return a string representation of this annotation
*/
String toString();
/**
* Returns the annotation type of this annotation.
* @return the annotation type of this annotation
*/
Class<? extends Annotation> annotationType(); //
}
Annotation 인터페이스는 모두 추상메서드로 이루어져 있으며, 갯수도 4개 밖에 없다.
애노테이션의 요소와 관련되서 몇가지 규칙이 있다. (굳이 외울 필요는 없지만 알아두면 좋을 법 하다.)
@interface WrongAnnotation {
int id = 100; // 인터페이스와 동일하게 static final이 숨겨져있기 때문에 이는 상수로써 사용 가능
String method(int i, int j); // 매개변수 X
String method2() throws Exception; // 예외 선언 X
ArrayList<T> list(); // 요소의 타입을 매개변수로 정의할 수 없다.
}
애노테이션은 메타데이터의 저장을 위해 클래스처럼 멤버를 가질 수 있는데, 이때 멤버의 갯수에 따라 Marker Annotation, Single Value Annotaiton, Full Annotation으로 분류할 수 있다.
멤버 변수가 없으며, 단순히 표식으로써 사용되는 애노테이션이다. 컴파일러에게 어떤 의미를 전달한다. 대표적으로는 @Override
애노테이션이 Marker Annotation의 예시이다.
@Override
public void method() ....
멤버로 단일 변수만을 갖는 애노테이션이다. 단일 변수밖에 없기 때문에 (값)만을 명시하여 데이터를 전달할 수 있다.
ex : @Resource("mappedName = PERSON")
멤버를 둘 이상 갖는 애노테이션으로, 데이터를 (값 = 쌍)의 형태로 전달된다.
ex : @FullAnnotation(var1 = "data value1", var2 = "data value2", var3 = "data value3")
커스텀 애노테이션을 작성시 애노테이션을 설명하기 위한 메타 애노테이션이 있다. 메타 애노테이션은 5개가 존재한다.
@Documented
: javadoc 및 기타 문서툴에 의해 문서화될 때, 해당 애노테이션이 문서에 표기된다.@Target
: 애노테이션 적용 가능한 대상을 지정할 수 있다.@Retention
: 애노테이션 유지 범위를 지정할 수 있다.@Inherited
: 자식클래스에서 부모클래스에 선언된 애노테이션을 상속받을 수 있다.@Repeatable
: 동일한 위치에 같은 애노테이션을 여러 개 선언할 수 있다.javadoc으로 작성한 문서를 포함시키려면 @Documented
를 붙인다. 여기서 javadoc을 작성한 문서는 /**
로 시작해서 */
로 끝난다.
package java.lang.annotation;
/**
* Indicates that annotations with a type are to be documented by javadoc
* and similar tools by default. This type should be used to annotate the
* declarations of types whose annotations affect the use of annotated
* elements by their clients. If a type declaration is annotated with
* Documented, its annotations become part of the public API
* of the annotated elements.
*
* @author Joshua Bloch
* @since 1.5
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
애노테이션을 적용할 수 있는 대상(위치)을 나타내는 애노테이션이다. 만약 Target에 선언된 대상과 다른 대상에 이 애노테이션을 적용할 경우 컴파일 에러가 발생한다. 타입으로 enum인 ElementType[]을 받는다.
Type
: class, interface, annotation, enum에만 적용 가능.FIELD
: 필드, enum 상수에만 적용 가능.METHOD
: 메서드에만 적용 가능.PARAMETER
: 파라미터에만 적용 가능.CONSTRUCTOR
: 생성자에만 적용 가능.LOCAL_VARIABLE
: 지역변수에만 적용 가능.ANNOTATION_TYPE
: 애노테이션에만 적용 가능.PACKAGE
: 패키지에만 적용 가능.TYPE_PARAMETER
: 자바 8부터 추가되었으며, 타입 파라미터(T, E 와 같은)에만 적용 가능.TYPE_USE
: TYPE_PARAMETER와 동일하게 자바 8부터 추가되었으며, JLS의 15가지 타입과 타입 파라미터에 적용 가능.자바 언어 명세서 (Java Language Specification, JLS)
자바 언어의 명세서를 뜻하며, 자바 프로그래밍 언어를 위한 문법과 정상적인/비정상적인 규칙들을 보여준다. 그리고 정상적인 프로그램을 실행하기 위한 프로그램 방법들도 보여준다.
@Target(ElementType.FIELD) // 필드에만 @MyTarget Annotation이 적용 가능하게끔 선언
public @interface MyTarget {...}
애노테이션이 어느 시점까지 유지되는지를 나타낼 수 있다. enum RetentionPolicy에 3가지 정책이 있다.
SOURCE
: 컴파일 시점에 컴파일러에 의해 제거된다. 즉, java 파일 내에서만 적용되고, class파일 형태에선 적용되지 않는다.CLASS
: SOURCE의 범위뿐만 아니라, class파일까지 적용된다RUNTIME
: SOURCE, CLASS 범위뿐만 아니라 JVM에서 실행될 때도 적용돼 리플렉션으로 애노테이션을 조회할 수 있다.해당 애노테이션을 적용하면 부모클래스에 선언된 애노테이션이 자식클래스에 상속된다. getAnnotation()을 호출하여 각 클래스별로 적용된 애노테이션을 확인할 수 있다. 실제로 어떻게 작동하는지 보기 위해 @Inherited를 사용하지 않는 애노테이션과, 사용하는 애노테이션을 만든다.
@Retention(RetentionPolicy.RUNTIME)
@interface MyInherited {
....
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface ChildAnnotation {
.....
}
Parent, Child, GrandChild 클래스를 만든다.
@MyInherited
class Parant{}
@ChildAnnotation
class Child extends Parent{}
class GrandChild extends Child{}
실제 코드를 통해 해당 애노테이션들이 어디에 적용됐는지 확인해보면 @Inherited
애노테이션을 사용하지 않은 @MyInherited
애노테이션은 자기 자신의 정보를 출력하고, 클래스 상속관계에서 조차 @MyInherited
가 적용되지 않는다는 것을 확인할 수 있다. 하지만 @Inherited
를 사용한 @ChildAnnotation
을 조회해보면 Child, GrandChild에 애노테이션만 출력된다. GrandChild는 선언된 애노테이션은 없지만, 상속받은 Child 애노테이션이 있기 때문에 동일하게 적용된다.
public class InheritedAnnotationTest {
public static void main(String[]args) {
System.out.println("inherited 애노테이션을 사용 전");
System.out.println("Before inherited A : "
+ new Parent().getClass().getAnnotation(MyInherited.class));
System.out.println("Before inherited B : "
+ new Child().getClass().getAnnotation(MyInherited.class));
System.out.println("Before inherited C : "
+ new GrandChild().getClass().getAnnotation(MyInherited.class));
System.out.println("inherited 애노테이션 사용 후");
System.out.println("After inherited A : "
+ new Parent().getClass().getAnnotation(ChildAnnotation.class));
System.out.println("After inherited B : "
+ new Child().getClass().getAnnotation(ChildAnnotation.class));
System.out.println("After inherited C : "
+ new GrandChild().getClass().getAnnotation(ChildAnnotation.class));
}
}
Output
inherited 애노테이션 사용 전
Before inherited A : @annotationtest.MyInherited()
Before inherited B : null
Before inherited C : null
inherited 애노테이션 사용 후
After inherited A : null
After inherited B : @annotationtest.ChildAnnotation()
After inherited C : @annotationtest.ChildAnnotation()
동일한 애노테이션을 여러개 선언할 경우 에러가 발생하지만, @Repeatable
을 적용하면 여러개의 동일한 애노테이션을 선언할 수 있다.
@Repeatable(ToDos.class) // @ToDo 애노테이션을 여러번 반복해서 쓸 수 있게한다.
@interface ToDo {
String value();
}
일반적인 애노테이션과 달리 같은 이름의 애노테이션 여러개가 하나의 대상에 적용될 수 있기 때문에, 이 애노테이션들을 하나로 묶어서 다룰 수 있는 애노테이션도 추가로 정의해야한다 → 마치 애노테이션들을 담을 배열을 만든다고 생각하면 이해가 쉽다.
// @ToDo 애노테이션의 @Repeatable 애노테이션을 위한 ToDos 애노테이션
@interface ToDos {
ToDo[] value();
}
위와 같이 ToDo 애노테이션을 담을 컨테이너 ToDos를 만들어야 한다. 또한 ToDo 애노테이션 배열타입의 요소를 선언해줘야 하고 컨테이너 애노테이션 안에 요소의 이름이 반드시 value이어야 한다.
"Annotation Processing" is a hook into the compile process of the java compiler, to analyse the source code for user defined annotations and handle then (by producing compiler errors, compiler warning, emitting source code, byte code ...)
애노테이션 프로세싱은 자바 컴파일러의 컴파일 단계에서, 유저가 정의한 애노테이션의 소스코드를 분석하고 처리하기 위해 사용하는 훅이다. 컴파일 에러나 컴파일경고를 만들어 내거나, 소스코드(.java)와 바이트코드(.class)를 내보내기도 한다.
출처 : https://stackoverflow.com/questions/2146104/what-is-annotation-processing-in-java [stackoverflow]
여기서 나오는 훅 이란? 후킹(Hooking) 이라고 불리며 이미 작성되어 있는 코드의 특정 지점을 가로채서 동작 방식에 변화를 주는 일체의 기술이라고 의미를 지닌 용어이다.
애노테이션 프로세서의 장점
애노테이션 프로세서의 단점
한끼 5천원인 세상 지났다. . 밥 값은 해야지. . .
[참고]
https://parkadd.tistory.com/54
https://velog.io/@ljs0429777/12주차-과제-애노테이션
좋은 정보 감사합니다