[Java] 어노테이션(Annotation) 정리
스프링으로 개발을 하다 보면 @GetMapping, @RequiredArgsConstructor 등의 다양한 어노테이션을 볼 수 있고, 사용을 하게 된다.
간단하게는 어노테이션을 쓰면 그 어노테이션의 로직(내용)을 컴파일/ 빌드 시 자동으로 추가해주는 것으로 알고 있다.
이렇게 자주 사용하는 어노테이션이지만 동작방식이나 정의되는 방법 등에 대해 정확하게 알고 있다고 생각하지 않아, 좀 더 알아보고 정리해보고자 한다.
1. 어노테이션(Annotation)이란?
- 자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종.
- 보통 @ 기호를 앞에 붙여서 사용.(컴파일러는 컴파일 시 @문자로 시작이 되면 어노테이션으로 판단하여 진행)
- JDK 1.5 버전 이상부터 사용 가능.
// Example
@RequiredArgsConstructor
@GetMapping
@Entity
...
- 클래스, 인터페이스, 메소드, 메소드 파라미터, 필드, 지역 변수 위에 작성하여 사용.
- 값을 저장할 수 있는 엘레먼트를 가지고, 어노테이션 이름 다음에 괄호 안에 엘레먼트를 정의.
@Entity(name = "MEMBER_TABLE")
1-1. 어노테이션의 용도
- 컴파일러에게 코드 작성 문법 에러를 체크하도록 정보를 제공.
- 소프트웨어 개발툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보 제공.
- 실행 시(런타임 시) 특정 기능을 실행하도록 정보를 제공.
2. 어노테이션 사용 순서
- 어노테이션 정의
- 어노테이션 배치
2-1. 어노테이션 정의
- 어노테이션을 적용할 때는 어노테이션이 어디(클래스, 메소드, 파라미터 등)에 적용되며, 언제까지 어노테이션 소스가 유지될 것인지를 설정해야 한다.
- 아래와 같이 어노테이션을 정의 할 수 있다.
@Target({ElementType.[적용대상]}) // 클래스, 파라미터, 메소드 등
@Retention(RetentionPolicy.[정보유지되는 대상]) // 런타임, 클래스, 소스
public @interface [어노테이션명]{
public 타입 elementName() [default 값]
...
}
- 예를 들어보자면, 우리가 자주 사용하는 룸북에서 제공하는 생성자 관련 어노테이션을 볼 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface RequiredArgsConstructor {
....
}
- @Target 에는 어느 위치(클래스, 파라미터, 메소드 등)에 어노테이션을 적용할지 작성한다.
- @Retention 에는 언제까지 어노테이션을 유지할 것인지 작성한다.
@Retention 어노테이션은 보통 Runtime 을 많이 사용하고 있어, 어노테이션을 확인하다 보면 Retention 값이 RUNTIME 으로 되어 있는 것을 확인할 수 있다.
2-2. 어노테이션 배치.
- 어노테이션의 사용은 클래스를 참고하는 소스의 흐름상에서 Reflection 을 사용하는 방법을 통해 활용한다.
- 해당 내용을 이해하기 위해 간단하게 예제를 작성해서 확인해보고자 한다.
- 추가로 코드가 실행되는 중 Reflection(리플렉션)을 이용하여 추가 정보를 통해 기능 동작도 확인한다.
Reflection(리플렉션)???
구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API.
자바의 리플렉션은 클래스, 인터페이스, 메소드들을 찾을 수 있고, 객체를 생성하거나 변수를 변경하거나 메소드를 호출할 수 있다.
- JaewonAnnotation.java (어노테이션)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JaewonAnnotation {
String value() default "-";
String str() default "Test Jaewon Annotation :)";
}
- TestAnnotationService.java (어노테이션을 사용하고자 하는 클래스)
public class TestAnnotationService {
@JaewonAnnotation
public void test1() {
System.out.println("테스트1");
}
@JaewonAnnotation("***")
public void test2() {
System.out.println("테스트2");
}
@JaewonAnnotation(value = "Test", str = "JaewonAnnotaion 테스트 입니다!!!")
public void test3() {
System.out.println("테스트3");
}
}
- TestMain.java (TestAnnotationService.java 를 사용하는 클래스)
import java.lang.reflect.Method;
public class TestMain {
public static void main(String[] args){
Method[] methodList = TestAnnotationService.class.getMethods();
for(Method method : methodList) {
// 어노테이션 적용 여부 확인
if(method.isAnnotationPresent(JaewonAnnotation.class)) {
System.out.println("Method Name : " + method.getName());
JaewonAnnotation annotation=method.getDeclaredAnnotation(JaewonAnnotation.class);
String value = annotation.value();
String str = annotation.str();
System.out.println("Value : " + value);
System.out.println("Str : " + str);
}
}
}
}
- TestMain 을 실행하게 되면 결과는 아래와 같습니다.
Method Name : test1
Value : -
Str : Test Jaewon Annotation :)
Method Name : test2
Value : ***
Str : Test Jaewon Annotation :)
Method Name : test3
Value : Test
Str : JaewonAnnotaion 테스트 입니다!!!
3. 어노테이션의 종류
- 어노테이션에도 종류가 존재한다.
- 표준(내장) 어노테이션 : 자바가 기본적으로 제공하는 어노테이션
- 메타 어노테이션 : 어노테이션을 위한 어노테이션
- 사용자 정의 어노테이션 : 사용자가 직접 정의(생성)하는 어노테이션
3-1. 표준(내장) 어노테이션
- 7개의 표준 어노테이션 중에 3개가 java.lang 의 일부이고, 나머지 4개는 java.lang.annotation 으로부터 가져온다.
- @Override
- 오버라이딩을 올바르게 했는지 컴파일러가 체크.
- Override 는 오버라이딩 시, 메서드의 이름을 잘못되었는지 확인한다.
// 부모 클래스
class Parent {
void parentMethod(){}
}
// 자식 클래스(부모 클래스 상속)
class Child extends Parent {
@Override
void parentMethodTest(){} // 컴파일 에러 발생! 오버라이드 메소드명이 틀림
}
- @Deprecated
- 앞으로 없어지거나, 사용하지 않을 것을 권장하는 필드나 메서드에 사용.
- StringUtils.isEmpty() 과 같이 밑줄이 그어져 있지만, 사용은 가능한 것을 종종 확인할 수 있다.
그러나 나중에 버전업시 어느 순간 메소드가 사라져 있을 수 있다.
@Deprecated
public static boolean isEmpty(@Nullable Object str) {
return (str == null || "".equals(str));
}
- @FunctionalInterface
- Java 8 부터 지원하는, 함수형 인터페이스를 지정하는 어노테이션.
- 함수형 인터페이스의 하나의 추상 메서드만 가져야 한다는 제약을 확인.
- 메서드가 존재하지 않거나, 1개 이상의 메서드(default 메서드 제외) 가 존재할 경우 컴파일 오류를 발생. - @SuppressWarnings
- 선언한 곳의 컴파일 경고를 무시하도록 하는 어노테이션.
@SuppressWarnings("unchecked")
ArrayList list = new ArrayList(); // 제네릭 타입을 지정하지 않음
list.add(obj); // 경고 발생 !!!(UnChecked)
3-2. 메타 어노테이션
- @Target
- @Target 에는 어느 위치(클래스, 파라미터, 메소드 등)에 어노테이션을 적용할지 작성한다.
ElementType 열거 상수 | 적용 대상 |
TYPE | 클래스, 인터페이스, 열거 타입 |
ANNOTATION_TYPE | 어노테이션 |
FIELD | 필드 |
CONSTRUCTOR | 생성자 |
PARAMETER | 파라미터 |
METHOD | 메소드 |
LOCAL_VARIAblE | 로컬 변수(지역 변수) |
PACKAGE | 패키지 |
- @Retention
- @Retention 에는 언제까지 어노테이션을 유지할 것인지 작성한다.
RetentionPolicy 열거 상수 | 설명 |
RUNTIME | 바이트 코드 파일(컴파일/빌드파일)까지 어노테이션 정보를 유지하여, 리플렉션을 이용해서, 런타임에 어노테이션 정보를 얻을 수 있다. |
CLASS | 바이트 코드 파일까지 어노테이션 정보를 유지하지만, 리플렉션을 이용해서 런타임에 어노테이션 정보를 얻을 수 없다. |
SOURCE | 소스상에서만 어노테이션 정보를 유지한다. 소스코드를 확인할 때만 의미가 있고, 바이트 코드 파일에는 정보가 남지 않는다. |
- @Documented
- 해당 어노테이션을 JavaDoc 에 포함시킨다. - @Inherited
- 어노테이션도 상속이 가능하다.
- 어노테이션을 자식 클래스에 상속하고자 할 때 해당 어노테이션을 작성한다.
@Inherited
@interface SuperAnno{}
@SuperAnno
class Parent{}
// SuperAnno 가 붙은 것으로 인식
class Child extends Parent{}
- @Repeatable
- Java 8 부터 지원하고, 연속적으로 어노테이션을 선언할 수 있게 한다.
3-3. 사용자 정의 어노테이션
- 위 2번 항목에서 예제로 생서안 것을 참고하면 된다.
4. 어노테이션 요소 특징
- 적용시 값을 지정하지 않으면, 사용될 수 있는 기본값을 지정할 수 있다.
- 요소가 하나이고 이름이 value 라면 어노테이션 작성 시 요소의 이름은 생략이 가능하다.
// 요소가 생략되면 default 값
@JaewonAnnotation
public void test1() {
}
// 요소가 하나이고 이름이 value 라면 이름 생략 가능
@JaewonAnnotation("***")
public void test2() {
}
// 요소가 하나가 아니면 이름 생략 불가능
@JaewonAnnotation(value = "Test", str = "JaewonAnnotaion 테스트 입니다!!!")
public void test3() {
}
- 요소의 타입이 배열인 경우 중괄호{} 를 사용해야 한다.
@interface TestAnnotation{
String[] strArr();
}
// 요소가 배열이면 중괄호를 통해 선언한다.
@TestAnnotation(strArr={"Test", "Annotation"})
// 요소가 1개일 때는 {}를 사용하지 않아도 된다.
@TestAnnotation(strArr="Hello Annotation")
// 요소가 없으면 {}를 써넣어야 한다.
@TestAnnotation(strArr={})
5. 어노테이션 규칙
- 어노테이션에서도 지켜야하는 규칙이 있다.
- 요소의 타입은 기본형, String, Enum, 어노테이션, Class 만 허용
- 괄호() 안에 매개변수를 선언할 수 없다.
- 예외를 선언 할 수 없다.
- 요소의 타입을 매개변수로 정의할 수 없다.(<T>)
참고
- https://velog.io/@jkijki12/annotation