안녕하세요! 조엘입니다!
"처음이라니까요" 시리즈 일곱 번째 토픽은 어노테이션입니다. 📜📜
스프링 프레임워크로 웹 개발을 하면서 참 많은 어노테이션을 쓰게 되는데요.
하지만 막상 누가 어노테이션이 뭐냐고, 왜 쓰냐고 물어보면 대답을 못할 것 같더라고요? 🤔🤔
이번 기회에 "어노테이션은 이래서 쓰는 거야" 확실히 말할 수 있게 정리해봅시다! 👏👏
참고로 리플렉션에 대한 이해가 있으면 포스팅 읽기가 더 수월해요 :)
Reflection API: https://papimon.tistory.com/82
*** 어노테이션이란? ***
어노테이션이 뭔지 정의부터 알아봐야겠죠?
Oracle의 Java Tutorial에서는 어노테이션을 다음과 같이 설명하는데요.
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.
Annotations have a number of uses, among them:
- Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
- Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
- Runtime processing — Some annotations are available to be examined at runtime.
해석을 해보면 다음과 같아요.
어노테이션은 메타데이터로써, 프로그램의 그 자체의 일부분은 아니면서 프로그램에 대한 데이터를 제공한다.
어노테이션 자체는 어노테이션을 붙인 코드 동작에 영향을 주진 않는다.
어노테이션은 다음과 같은 상황에 쓰임:
- 컴파일러에게 필요한 정보를 제공: 컴파일러가 에러를 감지하거나, 경고를 띄우지 않게 하기 위함
- 컴파일/배포 시에 필요한 처리 가능: SW 개발 툴에서 어노테이션의 정보를 통해 특정 코드를 추가할 수 있음
- 런타임 처리 제공: 런타임에도 어노테이션의 정보를 통해 필요한 처리를 할 수 있음 (Java Reflection)
이를 요약하면 어노테이션을 다음과 같이 정의할 수 있어요.
"어노테이션은 작성한 코드에 대해 추가적인 정보를 제공한다."
"어노테이션을 통해 컴파일 타임 혹은 런타임에 해당 코드에 필요한 추가적인 처리가 가능하다."
*** 어노테이션 써봤는데? ***
어노테이션의 내부 구현을 알아보기 전에, 무의식적으로 사용하던 어노테이션을 살펴보아요.
1. @Override
클래스를 상속하거나, 인터페이스를 구현하는 과정에서 @Override 어노테이션을 쓰는데요.
@Override 어노테이션은 다음과 같이 정의되어 있어요.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
해당 어노테이션은 Method가 Target이고, Source까지의 Retention 정책을 가지고 있다고 해석이 되어요.
아마 메서드에 붙일 수 있고, 유지 기한이 소스코드 범위 까지라는 것 같네요.
2. @Service
스프링 프레임워크에서 자주 쓰이는 @Service 어노테이션인데요.
핵심 비즈니스 로직을 담은 서비스 클래스를 빈으로 등록시켜주기 위해 해당 어노테이션을 사용했어요.
@Service 어노테이션은 다음과 같이 정의되어 있어요.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
해당 어노테이션은 Type이 Target이고, Runtime까지의 Retention 정책을 가지고 있나 보네요.
아마 타입에 작성해주고, 유지 기한이 런타임 까지라는 것 같아요!
또한 스프링에서 관리되는 객체임을 증명이라도 하듯, @Component 어노테이션도 붙어 있는 것을 볼 수 있어요.
3. @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이고, Runtime까지의 Retention 정책을 가지고 있네요.
메서드 위에서 작성해줘야 하고, 유지 기한이 런타임까지인가 봐요.
앞선 @Override, @Service와 달리 어노테이션 내부에 다양한 필드들이 정의되어 있는 것을 볼 수 있어요.
필드명을 보아하니, @GetMapping 어노테이션의 괄호 안에 써줬던 것들과 관련이 있어 보이네요!
@GetMapping(value = "/stations", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<StationResponse>> showStations() {
return ResponseEntity.ok().body(stationService.findAllStationResponses());
}
*** 어떻게 만들지? ***
설명 없이 예시부터 접했는데요. 어노테이션을 직접 만들면서 위의 코드들을 이해해 보도록 할게요.
어노테이션 타입 선언은 @interface를 통해서 하게 됩니다. 다음과 같이요!
public @interface JoelAnnotation {
}
위의 예시에서 빠지지 않고 등장한 @Target과 @Retention을 작성해줘야 하는데요.
이 둘은 메타 어노테이션으로, 어노테이션을 정의하는 데 사용되는 어노테이션이에요.
각각의 역할은 다음과 같아요.
@Target
어노테이션이 적용 가능한 대상을 지정해요. 대표적으로 다음과 같은 타입들을 지정할 수 있어요.
- TYPE: 클래스, 인터페이스, 열거 타입
- ANNOTATION_TYPE: 어노테이션
- FIELD: 필드
- CONSTRUCTOR: 생성자
- METHOD: 메서드
이번 예시에서는 METHOD에서 사용되는 어노테이션을 만들어 볼게요.
@Target(ElementType.METHOD)
public @interface JoelAnnotation {
}
@Retention
어노테이션이 유지되는 기간을 지정해요.
총 3가지(SOURCE, CLASS, RUNTIME)의 유지 정책을 가질 수 있어요.
위에 어노테이션을 정의하는 부분에서 컴파일 타임 혹은 런타임에 필요한 처리를 해준다고 했는데,
어노테이션 용도에 따라 유지 기간을 정해줄 필요가 있어요.
- SOURCE: 어노테이션이 소스파일에만 존재. 컴파일 후 클래스 파일에서는 사라짐
- CLASS: 어노테이션이 클래스 파일까지 존재. 런타임 시 사라짐. 디폴트 Retention 정책
- RUNTIME: 어노테이션이 런타임까지 존재. 리플렉션을 통해 어노테이션 정보를 사용 가능
이번 예시에서는 RUNTIME까지 유지되는 어노테이션을 만들어 볼게요.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JoelAnnotation {
}
이제 어노테이션 내부에 필드를 정의해 볼게요.
필드들을 정의해둠으로써 어노테이션에 추가적인 정보를 부여할 수 있어요.
기본형, 문자열, 이넘, 배열, 어노테이션, 클래스를 필드로 가질 수 있어요. 또한 default 값을 지정해 둘 수 있어요.
이번 예시에서는 위의 @GetMapping처럼 value와 produces를 필드로 가지게 해 볼게요.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JoelAnnotation {
String value();
String[] produces();
}
이제 이렇게 만든 어노테이션을 적용해볼게요.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JoelAnnotation {
String value();
String[] produces();
}
public class TestController {
@JoelAnnotation(value = "/joel-test", produces = "jsonType")
public void testMethod() {
System.out.println("Testing Method");
}
}
public class Main {
public static void main(String[] args) throws NoSuchMethodException {
final TestController testController = new TestController();
final Method testMethod = testController.getClass().getMethod("testMethod");
final JoelAnnotation annotation = testMethod.getAnnotation(JoelAnnotation.class);
final String value = annotation.value();
final String[] produces = annotation.produces();
System.out.println("value = " + value);
System.out.println("produces = " + produces[0]);
}
}
>> 결과
value = /joel-test
produces = jsonType
런타임까지 유지되는 어노테이션이라 리플렉션을 통한 접근이 가능해요.
코드를 보면 메서드에 붙은 JoelAnnotation을 추출한 것을 알 수 있어요.
추출한 JoelAnnotation의 필드 값 또한 가져올 수 있고요.
어노테이션을 통해 정의해 둔 값을 추출하여 런타임에 필요한 세팅을 해줄 수 있을 것이에요.
스프링에서 @GetMapping을 통해 매핑될 URL을 지정해줄 수 있던 이유도 이와 같았겠군요!!
사실 이번 포스팅에서 알아본 어노테이션의 기능은 굉장히 적어요.
참고에 있는 포스팅들에서 어노테이션의 더 많은 기능을 찾아볼 수 있으실 거예요.
*** 정리 ***
어노테이션의 정의는 다음과 같아요.
"어노테이션은 작성한 코드에 대해 추가적인 정보를 제공한다."
"어노테이션을 통해 컴파일 타임 혹은 런타임에 해당 코드에 필요한 추가적인 처리가 가능하다."
어노테이션을 쭉 알아봤는데, 근본적으로 어노테이션이 왜 필요할까요?
사실 추가적인 정보 제공 및 처리 기능은 클래스 내부에서도 충분히 구현할 수 있는데 말이죠. 🤔
어노테이션의 강점은 추가적으로 필요한 처리를 비즈니스 로직에 영향을 주지 않은 채로 할 수 있다는 데 있어요.
시스템 설정과 같은 부가적인 사항들을 어노테이션을 통해 구현할 수 있도록 위임하는 것이죠.
그렇다면 클래스 자체에서는 비즈니스 로직만 탄탄하게 작성해 두면 될 것이에요.
적절한 관심사의 분리가 이루어지겠네요!
어노테이션에 따라서 클래스/메서드가 어떻게 쓰일지 경로가 결정이 되는데요.
그동안 큰 생각 없이 쓰던 @Controller, @GetMapping, @PostMapping 등의 어노테이션들이,
사실 하나하나 클래스와 메서드가 큰 틀에서 어떻게 활용될 수 있는지를 정의해 줬던 것이었어요.
혹시 잘못된 부분이 있다거나, 얘기하고 싶은 부분이 있다면 편하게 댓글 남겨주세요.
읽어주셔서 감사합니다~ 🎁🎁
참고
- https://docs.oracle.com/javase/tutorial/java/annotations/
- https://parkadd.tistory.com/54
- https://www.inflearn.com/course/the-java-code-manipulation/
- https://honeyinfo7.tistory.com/56
- https://coding-factory.tistory.com/575
- https://www.nextree.co.kr/p5864/
'프로그래밍 언어 > Java' 카테고리의 다른 글
JVM의 GC (2) | 2021.10.03 |
---|---|
저는 GC가 처음이라니까요? (0) | 2021.10.02 |
Reflection API (4) | 2021.06.09 |
저는 JVM이 처음이라니까요? (6) | 2021.05.28 |
HashMap/HashSet의 원리 (4) | 2021.03.13 |
댓글