어노테이션이란?
애노테이션은 인터페이스를 기반으로 한 문법으로 애노테이션(annotation)은 사전적 의미로는 주석을 의미한다. 그렇지만 우리가 원래 알던 일반적인 주석 //이나 /**/와는 다르다.
일반적인 주석과 다르게 애노테이션은 코드를 작성할 수 있다. 어노테이션은 주석 설명 그 이상의 활동을 한다.
어노테이션이 붙은 코드는 어노테이션의 구현된 정보에 따라 연결되는 방향이 결정된다. 따라서 소스코드에서 비지니스 로직에는 영향을 주지 않지만 해당 타켓의 연결 방법이나 소스코드의 구조를 변경할 수 있다. 쉽게 말해서 "이 속성을 어떤 용도로 사용할까, 이 클래스에게 어떤 역할을 줄까?" 를 결정해서 붙여준다고 볼 수 있다.
즉, 프로그램에게 추가적인 정보를 제공해주는 메타데이터(meta data: 데이터를 위한 데이터)라고 볼 수 있다.
애노테이션은 주석이기 때문에 다이나믹하게 실행되는 코드는 들어가지 않는다. 즉, 런타임 중에 알아내야하는 값은 못들어간다.
컴파일러 수준에서 해석이 되거나, 완전히 정적이어야한다. 동적으로 런타임 중에 바뀌어야하는 것들은 애노테이션에 사용할 수 없다.
어노테이션은 다음과 같은 상황에 사용된다.
- 컴파일러에게 필요한 정보를 제공
- 컴파일러가 에러를 감지하거나, 경고를 띄우지 않게 하기 위함.
- 컴파일/배포 시에 필요한 처리 기능
- SW 개발 툴에서 어노테이션의 정보를 통해 특정 코드를 자동으로 추가할 수 있음.
- 런타임 처리 제공
- 런타임에도 어노테이션의 정보를 통해 필요한 처리를 할 수 있음. (Java Reflection)
- 즉, 실행시(런타임시) 특정 기능을 실행하도록 정보를 제공
요약하자면, 어노테이션은 다음과 같이 정의가 가능하다.
어노테이션은 작성한 코드에 대해 추가적인 정보를 제공하면서 컴파일 타임 혹은 런타임 시점에서 해당 코드에 필요한 추가적인 처리를 해 주는 역할을 한다.
1. 어노테이션의 정의 (메타 어노테이션)
어노테이션을 적용할 때는 어노테이션이 어디에 적용되며 언제까지 어노테이션 소스가 유지될 것인지를 설정하여야 하는데 소스코드에는 다음과 같이 어노테이션을 정의해 주면 된다.
@Target({ElementType.[적용대상]})
@Retention(RetentionPolicy.[정보유지되는 대상])
public @interface [어노테이션명]{
public 타입 elementName() [default 값]
...
}
이때 사용하는 메타 어노테이션은 어노테이션을 위한 어노테이션 으로, 어노테이션을 정의할 때 사용된다.
@Target
@Target에는 어떠한 값(ex : 클래스, 필드, 메서드 ...)에 어노테이션을 적용할 것이지 나타낼 수 있는데 넣을 수 있는 값은 다음 표와 같다. 즉, 어노테이션이 적용 가능한 대상을 지정한다.
ElementType 열거 상수 | 적용대상 |
TYPE | 클래스, 인터페이스, 열거 타입 |
ANNOTATION_TYPE | 어노테이션 |
FIELD | 필드 |
CONSTRUCTOR | 생성자 |
METHOD | 메소드 |
LOCAL_VARIABLE | 로컬 변수 |
PACKAGE | 패키지 |
@Retention
@Retention에는 어노테이션이 유지되는 기간을 지정한다.
보통 어노테이션은 Runtime시에 많이 사용하므로 대부분의 어노테이션의 Retention 값은 RUNTIME으로 되어있다.
RetentionPolicy 열거 상수 | 설명 |
SOURCE | 소스 코드(.java)까지만 어노테이션 정보를 유지한다. 컴파일 전까지만 유효하며, 바이트 코드 파일에는 정보가 남지 않는다. |
CLASS | 클래스 파일(.class, =바이트 코드)까지 어노테이션 정보를 유지한다. 즉, 클래스 로더가 클래스를 로딩하기 전까지만 유효하다. 하지만 리플렉션을 이용해서 어노테이션 정보를 얻을 수는 없다. |
RUNTIME | 런타임까지 남아 있는다. (사실상 사라지지 않는다.) 클래스를 로딩한 이후에도 유효하다. Java 리플렉션 API를 사용하여 어노테이션 정보를 알 수 있다 |
@Documented
해당 어노테이션을 javadoc에 포함시킨다.
@Inherited
어노테이션의 상속을 가능하게 한다.
@Repeatable
어노테이션을 반복해서 적용할 수 있게 한다.
2. 표준 어노테이션
표준 어노테이션은 자바에서 기본적으로 제공하는 어노테이션을 의미한다.
Annotation | 설명 |
@Override | 컴파일러에게 오버라딩하는 메소드라는 것을 알린다. |
@Deprecated | 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다. |
@SuppressWarnings | 컴파일러의 특정 경고 메시지가 나타나지 않게 해 준다. |
@SafeVarargs | 제네릭 타입의 가변 인자에 사용한다. |
@FunctionalInterface | 함수형 인터페이스라는 것을 알린다. |
@Native | native 메소드에서 참조되는 상수 앞에 붙인다. |
그 외, 모든 메타 어노테이션은 표준 어노테이션에 속한다.
3. 어노테이션이 사용되는 상황
@Override
클래스를 상속하거나, 인터페이스를 구현하는 과정에서 @Override 어노테이션을 사용하는데, 해당 어노테이션은 다음과 같이 정의되어 있다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
해당 어노테이션은 Method가 target이고, 소스 코드 범위까지 유효하다.
@Service
스프링 프레임워크에서 자주 쓰이는 @Service 어노테이션은 핵심 비즈니스 로직을 담은 서비스 클래스를 빈으로 등록하기 위해 사용된다. @Service 어노테이션은 다음과 같이 정의되어 있다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
해당 어노테이션은 Type이 target이고, 런타임 시점까지 유효하다. 참고로, @Component는 스프링에서 관리되는 빈을 뜻한다.
@GetMapping
스프링 프레임워크에서 자주 쓰이는 @GetMapping 어노테이션은 @GetMapping 안에 적어둔 URL로 GET 요청이 들어오면, 해당 어노테이션이 붙은 메소드가 실행된다. @GetMapping 어노테이션은 다음과 같이 정의되어 있다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
@AliasFor(annotation = RequestMapping.class)
String name() default "";
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
}
해당 어노테이션은 Method가 Target이고, 런타임 시점까지 어노테이션이 유효하다. 앞선 어노테이션들과는 달리 어노테이션 내부에 다양한 필드들이 정의되어 있다. 이곳에서 필드를 적절히 사용하여 다음과 같이 정의해 줄 수 있다. 참고로 @RequestMapping 어노테이션의 method 속성은 특정 메소드의 요청만 오도록 필터링을 한다.
@GetMapping(value = "/stations", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<StationResponse>> showStations() {
return ResponseEntity.ok().body(stationService.findAllStationResponses());
}
value가 위에서 언급한 URL에 해당한다.
4. 커스텀 어노테이션을 정의하는 방법
기존에 있는 어노테이션을 혼합하여 재활용하는 방법도 있겠지만, 이번에는 순수하게 어노테이션을 정의해보겠다. 간단한 예제를 위해 @Target, @Retention 정도만 사용하여 위의 @GetMapping을 흉내내려고 한다
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JayonAnnotation {
String value();
String[] produces();
}
어노테이션 타입 선언은 @interface를 사용해야 하고, 그 옆에 어노테이션의 이름을 적으면 된다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JayonAnnotation {
String value();
String[] produces();
}
public class TestController {
@JayonAnnotation(value = "/jayon", produces = "json")
public void speak() {
System.out.println("My name is Jayon");
}
}
public class Main {
public static void main(String[] args) throws NoSuchMethodException {
final TestController testController = new TestController();
final Method testMethod = testController.getClass().getMethod("speak");
final JayonAnnotation annotation = testMethod.getAnnotation(JayonAnnotation.class);
final String value = annotation.value();
final String[] produces = annotation.produces();
System.out.println("value = " + value);
System.out.println("produces = " + produces[0]);
}
}
// 실행 결과
value = /jayon
produces = json
@JayonAnnotation은 런타임 시점까지 유효하므로 리플렉션을 사용하여 접근할 수 있다. 그래서 해당 어노테이션의 필드 값(value, produces)을 가져올 수 있다.
어노테이션을 통해 정의해 둔 값을 추출하여 런타임에 필요한 세팅을 해줄 수 있고, 이러한 원리로 스프링에서는 @GetMapping을 통해 매핑될 URL을 지정해 줄 수가 있다.
예상 면접 질문 및 답변
Q. 어노테이션이란?
애노테이션은 인터페이스를 기반으로 한 문법으로 어노테이션을 작성한 코드에 대해 추가적인 정보를 제공하면서 컴파일 타임 혹은 런타임 시점에서 해당 코드에 필요한 추가적인 처리를 해 주는 역할을 한다.
Q. 메타 어노테이션의 종류는?
@Target
어노테이션이 적용 가능한 대상을 지정한다.
- ElementType.TYPE
- 클래스, 인터페이스, 열거 타입
- ElementType.ANNOTATION_TYPE
- 어노테이션
- ElementType.FIELD
- 필드
- ElementType.CONSTRUCTOR
- 생성자
- ElementType.METHOD
- 메소드
@Retention
어노테이션이 유지되는 기간을 지정한다.
- RetentionPolicy.SOURCE: 소스 코드(.java)까지 남아있는다.
- 컴파일 전까지만 유효하다.
- RetentionPolicy.CLASS: 클래스 파일(.class)까지 남아있는다. (=바이트 코드)
- 클래스 로더가 클래스를 로딩하기 전까지만 유효하다.
- RetentionPolicy.RUNTIME: 런타임까지 남아 있는다. (사실상 사라지지 않는다.)
- 클래스를 로딩한 이후에도 유효하다.
- Java 리플렉션 API를 사용하여 어노테이션 정보를 알 수 있다.
@Documented
해당 어노테이션을 javadoc에 포함시킨다.
@Inherited
어노테이션의 상속을 가능하게 한다.
@Repeatable
어노테이션을 반복해서 적용할 수 있게 한다.
Q. 표준 어노테이션의 종류는?
@Override
컴파일러에게 오버라딩하는 메소드라는 것을 알린다.
@Deprecated
앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
@SuppressWarnings
컴파일러의 특정 경고 메시지가 나타나지 않게 해 준다.
@SafeVarargs
제네릭 타입의 가변 인자에 사용한다.
@FunctionalInterface
함수형 인터페이스라는 것을 알린다.
@Native
native 메소드에서 참조되는 상수 앞에 붙인다.
그 외, 모든 메타 어노테이션은 표준 어노테이션에 속한다.
추가 정리
https://hongsii.github.io/2018/12/12/java-annotation/
참고
- https://joel-dev.site/83?category=1018629
- https://parkadd.tistory.com/54
- https://jeong-pro.tistory.com/234