0. 학습할 것
- 애노테이션 정의하는 방법
- @retention
- @target
- @documented
- 애노테이션 프로세서
1. 어노테이션 이란?
- 자바를 개발한 사람들은 소스코드에 대한 문서를 따로 만들기보다 소스코드와 문서를 하나의 파일로 관리하는 것이 낫다고 생각했다.
- 그래서 소스코드의 주석 '/** ~ */' 에 소스코드에 대한 정보를 저장하고, 소스코드의 주석으로부터 HTML 문서를 생성해내는 프로그램(javadoc.exe)를 만들어 사용했다.
- 위와 같이 소스코드 안에 다른 프로그램(ex: javadoc.exe)을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애노테이션이다.
- 어노테이션은 JEE5부터 추가된 요소로 사전적 의미로는 주석을 의미한다.
- 하지만 자바에서는 단순 주석이 아닌 클래스에 특수한 의미를 부여하거나 기능을 주입하기 위한 메타데이터라고 볼 수 있다.
- annotation은 그 자체로는 어떤한 조작도 이루어지지 않으며 프로그램에 대한 영향도 없다.
- 이런 어노테이션은 인터페이스 일종으로 @ 를 사용하여 선언한다.
- 예를 들어, @Test 애노테이션 같은 경우 테스트 프로그램인 JUnit에게 테스트를 해야한다! 라고 알리기만 할 뿐 메서드가 포함된 프로그램 자체에는 아무런 영향을 미치지 않는다.
- 즉, 주석과 동일하다. 하지만 다른 프로그램에게 어떤 역할을 한다고 알려주는 주석인 것이다.
- JDK에서 기본적으로 제공하는 어노테이션이 아닌 JUnit과 같은 다른 프로그램에서 제공하는 약속된 형식으로 정보를 애노테이션으로 제공하기만 하면 된다.
@Test
public void testMethod() { ... }
개념정리 (참고)
메타 데이터(metadata) : 데이터에 대한 데이터. 즉, 다른 데이터를 설명해주는 데이터.
예를들어, 카메라로 사진을 찍었을 때 사진에 대한 [촬영한 시간, 장소, 카메라 모델명, 플래쉬 사용 유무, 등등]이 메타데이터라고 할 수 있습니다.애노테이션(annotation) : 사전적 의미로 주석을 의미하며 프로그램에 대한 데이터를 제공하는 메타데이터의 한 형태 입니다.
애노테이션의 용도: 컴파일러에 제공하는 정보 -> 컴파일러는 애노테이션을 사용하여 에러를 체크하거나 에러메시지를 억제할 수 있습니다.
ex) @Override, @SuppressWarnings컴파일 시간 및 배포 시간 처리 -> 소프트웨어 개발툴이 애노테이션 정보를 처리하여 코드, XML 파일등을 생성할 수 있습니다. (애노테이션을 사용한 곳에 특정 코드를 추가할 수 있습니다.
ex) Lombok의 @Getter, @Setter)런타임 처리 -> 일부 애너테이션은 런타임에 특정 기능을 실행하도록 정보를 제공합니다. (Java Reflection)자바 리플렉션(Java Reflection) : 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API 입니다. 런타임 시에 클래스 이름만 알고있다면 클래스에 대한 정보를 가져오고 활용할 수 있게 해줍니다.
출처: https://parkadd.tistory.com/54
2. 어노테이션 목적과 종류
- 데이터에 대한 유효성 검사 조건을 보다 쉽게 파악할 수 있고, 코드가 깔끔해지게 된다.
- 어노테이션과 리플랙션을 같이 이용하면 원하는 클래스를 주입하는 것도 가능해진다.
- 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보를 제공
- 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공
- 실행 시(런타임 시) 특정 기능을 실행하도록 정보를 제공
3. 어노테이션 종류
어노테이션은 총 2가지로 나뉜다.
- Built-in Annotation : 자바에서 기본적으로 제공하는 어노테이션
- Meta Annotation : 커스텀 어노테이션을 만들수 있게 제공된 어노테이션.
Built-in Annotation
종류설명
@Override | 선언한 메서드가 오버라이드 되었다는 것을 나타냄. * 상위(부모)클래스(또는 인터페이스)에서 해당 메서드를 찾지 못하면 컴파일 에러 발생. |
@Deprecated | 해당 메서드가 더이상 사용되지 않음을 표시합니다. * 해당 메서드를 사용할 경우 컴파일 경고를 발생. |
@SuppressWarnings | 선언한 곳의 컴파일 경고를 무시. |
@SafeVarargs | 제네릭 가은 가변인자 매개변수를 사용할 때의 경고를 무시한다.(Java 7 이상) |
@FunctionalInterface | 람다 함수등을 위한 인터페이스 지정.(Java 8 이상) * 메소드가 없거나 두개이상 되면 컴파일 오류 발생 |
@Native | native 메서드에서 참조되는 상수 앞에 붙인다. (JDK 1.8) |
Meta Annotation
종류설명
@Retention | 어노테이션이 유지되는 기간을 지정하는데 사용한다.(세가지 유지정책 사용) |
@Documented | 해당 어노테이션을 javadoc에 포함시킵니다. |
@Target | 어노테이션이 적용할 위치를 지정합니다. 여러 값일 경우 {} 사용 |
@Inherited | 어노테이션의 상속을 가능하게 합니다. |
@Repeatable | 연속적으로 어노테이션을 사용할 수 있게 해줍니다. |
3-1. built-in Annotation
@Override
- 메서드 앞에만 붙일 수 있는 어노테이션
- 조상의 메서드를 오버라이딩하는 것이라는 걸 컴파일러에게 알려주는 역할
- 어노테이션이 없다면 아래와 같이 오버라이드 메서드명을 오타 내도 컴파일러는 알지 못함.
- 하지만 어노테이션이 있다면 이를 컴파일러가 감지할 수 있다.
- 필수는 아니지만 어노테이션을 붙이는 것이 좋다.
public class AnnotationTestClass {
// 에러없음, 단지 새로운 메서드라고 감지
public void testMethod() { ... }
// 에러감지
// java: method does not override or implement a method from a supertype
@Override
public void testMethod() { ... }
}
@Deprecated
- 개선된 버전의 기능이 새로 생겼으니 사용하지 말기를 권고하는 어노테이션 (사용해도 프로그램에 전혀 이상은 없다.)
- 개선된 버전이 나왔다고 해도 이미 레거시에서 많이 구 버전을 사용하고 있을 것이므로 삭제가 불가능하여 해당 어노테이션을 사용한다.
- 아래는 Date 클래스에서 사용하는 예제
@Deprecated
public int getDate() {
return normalize().getDayOfMonth();
}
@FunctionalInterface
- 함수형 인터페이스를 선언할 때, 이 어노테이션을 붙이면 컴파일러가 함수형 인터페이스를 올바르게 선언 했는데 확인하고, 에러를 발생 시킨다.
- 즉, 함수형 인터페이스를 선언할 때는 반드시 사용하자
- 아래는 Runnable 인터페이스의 일부.
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
@SuppressWarnings
- 컴파일러가 보여주는 경고 메시지를 나타나지 않게 해준다.
- 경우에 따라 경고 메시지를 묵인해야 하는 경우가 있는데 이 때 많은 경고 메시지들 때문에 중요한 경고 메시지를 놓칠 수도 있기 때문에 묵인해도 되는 경고 메시지는 없애기 위해 사용
- 묵인해도 되는 경고 메세지
- @Deprecated 가 붙은 대상을 사용해서 발생하는 경고
- @unchecked : 제네릭스로 타입을 지정하지 않았을 때 경고
- @rawtypes : 지네릭스를 사용하지 않아서 발생하는 경고
- @varargs : 가변인자의 타입이 지네릭 타입일 때 발생하는 경고
import java.util.ArrayList;
class TestClass {
int newField;
int getNewField() {
return newField;
}
@Deprecated
int oldField;
@Deprecated
int getOldField() {
return oldField;
}
}
class AnnotationClass {
@SuppressWarnings("deprecation") // deprecation 관련 경고를 억제
public static void main(String args[]) {
TestClass tc = new TestClass();
tc.oldField = 10; // @Deprecated가 붙은 대상을 사용
System.out.println(tc.getOldField()); // @Deprecated가 붙은 대상을 사용
@SuppressWarnings("unchecked") // 지네릭스 관련 경고를 억제
ArrayList<TestClass> list = new ArrayList(); // 타입을 지정하지 않음
list.add(tc);
}
}
/* 출력
10
*/
@SafeVarargs
- non-reifiable 타입
- 컴파일 후에 제거되는 타입
- 컴파일 후에 타입 정보가 유지되지 않는다는 의미
- reifiable 타입
- 컴파일 후에도 제거되지 않는 타입
- 컴파일 후에도 타입 정보가 유지된다는 의미
메서드에 선언된 가변인자의 타입이 non-reifiable 타입일 경우 해당 메서드를 선언하는 부분과 호출하는 부분에서 "unchecked"경고가 발생한다.이 어노테이션은 생성자와 static, final에만 사용이 가능하다.
즉, 오버라이드 될 수 있는 메서드에는 사용 불가능 하다.
예를들어 Arrays의 asList를 살펴보자
이 메서드는 매개변수로 넘겨받은 값들로 배열을 만들어 새로운 ArrayList객체를 만들어서 반환하는데 이 과정에서 경고가 발생한다.
public static <T> List<T> asList(T... a) {
return new ArrayList<T>(a); // ArrayList (E[] array)를 호출. 경고발생!
}
- 매개변수가 가변인자인 동시에 제네릭 타입.
- 메서드에 선언된 타입 T는 컴파일 과정에서 Object로 변경됨. 즉, Object[]가 되는 것이다.
- Object[]에는 모든 타입의 객체가 들어있을 수 있으므로, 이 배열로 ArrayList를 생성하는 것이 위험하다고 경고하는 것이다.
- 그러나 사용하는 쪽에서 asList()가 호출되는 부분을 타입 T가 아니면 들어가지 못하게 하도록 만들어 놨다면 전혀 문제가 없기 때문에 경고를 없애기 위해 해당 애노테이션을 사용해야 한다.
- 메서드 선언 시 @SafeVarargs를 붙이면, 이 메서드를 호출하는 곳에서 발생하는 경고도 무시된다.
- 반면 @SafeVarargs대신, @Suppresswarnings("unchecked")로 경고를 억제하려면, 메서드 선언뿐 아니라 메서드가 호출되는 곳에도 애노테이션을 붙여야 한다.
- @SafeVarargs로 'unchecked'는 억제가 가능하지만, 'varargs' 경고는 억제가 불가능 하므로 습관적으로 @SafeVarargs와 @SuppressWarnings("varargs")를 같이 붙이는 것을 권장한다.
@SafeVarargs // unchecked 경고 억제
@SuppressWarnings("varargs") // vargars 경고 억제
public static <T> List<T> asList(T... var0) {
return new Arrays.ArrayList(var0);
}
3-2. Meta Annotation
- 애노테이션을 위한 애노테이션
- 애노테이션을 정의할 때 애노테이션의 적용대상(target), 유지기간(retention)등을 지정하는데 사용
@Target
- 적용가능한 대상을 지정하는데 사용(ElementType의 Enum 요소를 사용하여 대상 결정)
- 아래는 @SuppressWarnings이다. 이 애노테이션에 적용할 수 있는 대상을 @Target으로 지정하였다.
- 여러개의 값을 지정할 때는 배열에서처럼 괄호 {} 를 사용해야 한다.
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
요소 타입이 ElementType[] 배열이기 때문에 적용가능한 대상을 여러 개의 값을 지정할 수 있다.
@Target 으로 지정할 수 있는 애노테이션은 적용대상의 종류는 아래와 같다.
대상타입적용 대상
ANNOTATION_TYPE | 애노테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
RARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수 (JDK 1.8) |
TYPE_USE | 타입이 사용되는 모든 곳(JDK 1.8) |
TYPE 은 타입을 선언할 때, 애노테이션을 붙일 수 있다는 뜻이고 TYPE_USE 는 해당 타입의 변수를 선언할 때 붙일 수 있다는 뜻이다.
아래와 같이 static import 문을 쓰면 ElementType.TYPE -> TYPE 으로 간단히 할 수 있다.
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
@Target({FIELD, TYPE, TYPE_USE})
public @interface MyAnnotation {}
@MyAnnotation
class AnnoTargetTest {
@MyAnnotation
int num = 0;
@MyAnnotation
AnnoTargetTest nc;
}
FILED는 기본형에, TYPE_USE는 참조형에 사용된다는 점에 주의하자.
@Retention
- 애노테이션의 유지시간을 지정한다.
RetentionPolicy의 값을 넘겨주는 것으로 애노테이션의 유지 정책이 결정된다고 설명되어 있다.
애노테이션의 유지 정책(retention policy)의 종류
유지 정책의미
SOURCE | 소스 파일에만 존재. 클래스 파일에는 존재하지 않음. 컴파일 전까지만 유효 |
CLASS | 클래스 파일에 존재. 실행시에 사용 불가. Default 값, 컴파일러가 클래스를 참조할 때 까지 유효 |
RUNTIME | 클래스 파일에 존재. 실행시에 사용가능. 컴파일 이후에도 JVM에 의해 계속 참조 가능 |
SOURCE는 애노테이션을 사실상 주석처럼 사용한다고 보면 됩니다 컴파일러가 컴파일 할 때 해당 애노테이션의 메모리는 버립니다.'@Override'나 '@SuppressWarnings' 처럼 컴파일러가 사용하는 애노테이션은 유지 정책이 'SOURCE' 입니다. 컴파일러를 직접 만드는게 아니라면 주석처럼 사용됩니다.
CLASS(디폴트 값)는 컴파일러가 컴파일 시에는 애노테이션의 메모리를 가져가지만, 실질적으로 런타임시에는 사라지게 됩니다. 런타임시에 사라진다는 것은 리플렉션으로 선언된 애노테이션 데이터를 가져올 수 없게 됩니다.
즉, 실행 타임에서 애노테이션에 대한 데이터를 활용할 수 없다는것을 의미합니다. 예를들어 해당 클래스에 있는 애노테이션의 정보를 가져오고 싶을 때 유지정책이 SOURCE나 CLASS 라면 가져오는것이 불가능합니다.
(런타임시에 이미 애노테이션에 대한 메모리가 없기 때문에)RUNTIME은 애노테이션을 런타임시에까지 사용할 수 있습니다. JVM이 자바 바이트코드가 담긴 class 파일에서 런타임 환경을 구성하고 런타임을 종료할 때까지 메모리는 살아있습니다.
- 인용: https://parkadd.tistory.com/54 -
@Documented
- 애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
- 자바에서 제공하는 기본 애노테이션 중 @Override, @SuppressWarnings를 제외하고는 모두 이 메타 애노테이션이 붙어 있다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
@Ingerited
- 애노테이션이 자손 클래스에 상속되도록 한다.
- @Ingerited가 붙은 애너테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애너테이션이 붙은 것과 같이 인식된다.
@Inherited //@SupperAnno가 자손까지 영향 미치게
@interface SupperAnno { ... }
@SuperAnno
class Parent { ... }
class Child extends Parent { ... } // Child에 애너테이션이 붙은 것으로 인식
위 코드에서 Child클래스는 애너테이션이 붙지 않았지만, 조상인 parent 클래스에 붙은 @SuperAnno가 상속되어 Child클래스에도 @SuperAnno가 붙은 것으로 인식된다.
@Repeatable
- 보통은 하나의 대상에 한 종류의 에너테이션을 붙이는데 @Repeatable이 붙은 애너테이션은 여러 번 붙일 수 있다.
@Repeatable(ToDos.class)
@interface ToDo {
String Value();
}
예를들어 @ToDo 라는 애너테이션이 위와 같이 정의되어 있을 때, 다음과 같이 MyClass클래스에 @ToDo를 여러번 붙이는 것이 가능하다.
@ToDo("delete test codes.")
@ToDo("Override inherited methods")
class MyClass { ... }
일반적인 애너테이션과 달리 같은 애너테이션이 여러개가 하나의 대상에 적용될 수 있기 때문에, 이 애너테이션들을 하나로 묶어서 다룰 수 있는 애너테이션도 추가로 정의해야 한다.
@interface ToDos { // 여러개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
ToDo[] value(); // ToDo애너테이션 배열타입의 요소를 선언. 이름이 반드시 value이여야 함
}
@Repeatable(ToDos.class) // 괄호 안에 컨테이너 애너테이션을 지정해 줘야 한다.
@interface ToDo {
String value();
}
@Native
- 네이티브 메서드에 의해 참조되는 상수 필드에 붙이는 애노테이션
- 네이티브 메서드란 JVM이 설치된 OS의 메서드를 의미한다.
- 네이티브 메서드는 보통 C언어로 작성되어 있고, 자바에서는 선언부만 정의하고 구현은 하지 않는다.
- 그래서 추상 메서드 처럼 선언부만 있고 몸통이 없다.
class Object{
private static native void registerNatives(); // 네이티브 메서드
static {
registerNatives(); //네이티브 메서드 호출
}
protected native Object clone() throws CloneNotSupportedException;
public final native Class<?> getClass();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public native int hashCode();
...
}
- Object 클래스의 메서드는 대부분 네이티브 메서드이다.
- 호출하는 방법은 일반 자바 메서드와 동일 하지만 실제로는 OS의 메서드가 호출되는 것이다.
- 이게 가능한 이유는 JNI(Java Native Interface)가 한다.
4. 애노테이션을 정의하는 방법
애너테이션 타입 정의
- 커스텀 애노테이션이라고 부르며 이와 관련된 소스는 java.lang.annotation 패키지에 속해있다.
- 커스텀 애노테이션을 정의하려면 interface라는 키워드를 쓰고 그 앞에 @를 붙여주면 애노테이션이 정의된다.
- 공백을 넣어 @ interface도 가능하다고는 하지만, 표준 스타일을 맞추기 위해서 공백없이 붙여주는게 좋은 방법이다.
@interface [애노테이션이름] {
타입 요소이름(); // 애노테이션의 요소를 선언한다.
...
}
애너테이션 요소
애너테이션 내에 선언된 메서드를 '애너테이션의 요소(element)' 라고 한다.
@interface TestInfo{
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType {FIRST, FINAL} 과 같이 열거형 포함 가능
DateTime testDate(); // 자신이 아닌 다른 애너테이션(@DateTime) 포함 가능
}
@interface DateTime{
String yymmdd();
String hhmmss();
}
- 애노테이션 요소는 반환값이 있고 매개변수가 없는 추상 메서드의 형태이다.
- 상속을 통해 구현하지 않아도 된다.
- 다만, 애너테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해주어야 한다.
@TestInfo(
count = 3, testedBy = "han",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testDate = @DateTime(yymmdd = '220814', hhmmss = '001241')
)
public class newClass { ... }
각 요소들은 기본값을 가질 수 있으며, 기본값이 있는 요소들은 애너테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용된다.
@interface TestInfo{
int count() default 1; // 기본값을 1로 지정
}
// use...
@TestInfo // TestInfo(count=1) 과 동일함.
public class NewClass{ ... }
애너테이션의 요소가 오직 하나 뿐이고 이름이 value 인 경우, 애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 된다
@interface TestInfo{
String value();
}
// use...
@TestInfo("passed") // TestInfo(value="passed") 와 동일
public class newClass{ ... }
- 요소 타입이 배열인 경우, 괄호{} 를 사용해 여러 개의 값을 지정할 수 있다.
- 하나인 경우는 괄호{} 를 생략할 수 있다.
마커 애너테이션 Marker Annotation
값을 지정할 필요가 없는 경우, 애너테이션의 요소를 하나도 정의하지 않을 수 있다.
Serializable 이나 Cloneable 인터페이스처럼, 요소가 하나도 정의되지 않은 애너테이션을 마커 애너테이션이라 한다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
애너테이션 요소의 규칙
애너테이션의 요소를 선언할 때 반드시 지켜야 하는 규칙
- 요소 타입은 기본형, String, Enum, 애너테이션, Class 만 허용
- () 안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
@interface AnnotationTest{
int id = 100; // 상수 선언은 가능하다. -> static final int id = 100;
String major(int i, int j); // 에러. 매개변수를 선언할 수 없다.
String minor() throws Exception; // 에러. 예외를 선언할 수 없다.
ArrayList<T> list(); // 에러. 요소의 타입에 타입 매개변수 사용 불가!
}
(추가할 것.. java.lang.annotation.Annotation, 애노테이션 프로세서)
참고 및 인용
https://www.notion.so/12-386f4cd47d37448fa0252d3ed22b45b7#5332f6a6fe214b9a817e6da11c7bce76
https://www.notion.so/37d183f38389426d9700453f00253532
https://parkadd.tistory.com/54
https://blog.naver.com/swoh1227/222229853664
https://pej4303.tistory.com/62
https://github.com/ByungJun25/study/tree/main/java/whiteship-study/12week
'Language > Java-Weekly-study' 카테고리의 다른 글
[Java] W11: 자바 Enum (0) | 2022.08.13 |
---|---|
[Java] W10: 자바 멀티쓰레드 프로그래밍 (0) | 2022.07.31 |
[Java] W09: 자바 예외 처리 (0) | 2022.07.13 |
[Java] W08: 자바 인터페이스 (0) | 2022.07.12 |
[Java] W07: 자바 패키지 (0) | 2022.07.10 |