본문 바로가기
우아한테크코스/Level2

저는 어노테이션이 처음이라니까요?

by 조엘 2021. 6. 11.

안녕하세요! 조엘입니다!

 

"처음이라니까요" 시리즈 일곱 번째 토픽은 어노테이션입니다. 📜📜

스프링 프레임워크로 웹 개발을 하면서 참 많은 어노테이션을 쓰게 되는데요. 

하지만 막상 누가 어노테이션이 뭐냐고, 왜 쓰냐고 물어보면 대답을 못할 것 같더라고요? 🤔🤔

이번 기회에 "어노테이션은 이래서 쓰는 거야" 확실히 말할 수 있게 정리해봅시다! 👏👏

 

참고로 리플렉션에 대한 이해가 있으면 포스팅 읽기가 더 수월해요 :)

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/

 

반응형

댓글2