
지난 시간 마법 같은 일들을 쉽게 처리해주는 Spring의
Servlet 핵심 기술 중 하나인 Reflection에 대해 알아봤다.
객체 자신을 반사하여 자신의 구조를 살펴볼 수 있는 리플렉션 덕분에
우리는 HTTP Request start-line의 request-target에 따라
메서드를 동적으로 수행할 수 있게 됐다!
리플렉션은 애노테이션과 함께 사용되어 놀라운 수행 능력을 보여주는데
이번엔 애노테이션이란 무엇인가, 어떻게 쓰일 수 있는가에 대해 알아보자.

애노테이션이란 읽어서 사용할 수 있는 주석을 의미한다.
하지만 주석처럼 키워드와 함께 그냥 사용할 수 있는 것이 아니고,
특수한 애노테이션 클래스를 설계해서 목적에 맞게 주석처럼 사용할 수 있다.
그렇다면 애노테이션은 어떻게 만들 수 있을까?

애노테이션을 직접 만들기 전에 먼저 메타 애노테이션에 대해 이해해야 한다.
메타 애노테이션이란, 애노테이션을 정의하는데 사용하는 애노테이션이다.
애노테이션을 설계할 때 필요한 기본 설정 정보와 같다고 생각하면 된다.
@Retention
public @interface Anno {
// 코드
}
애노테이션의 생존 기간을 정의하는 애노테이션이다.
@Retention(RetentionPolicy.SOURCE)
소스 코드일 때에만 생존하는 애노테이션으로 컴파일 시점에 제거된다.
@Retention(RetentionPolicy.CLASS)
컴파일 이후 .class 파일 까지만 생존하는 애노테이션으로 자바 실행 시점에 제거된다.
@Retention(RetentionPolicy.RUNTIME)
실행 중에도 생존하는 애노테이션으로 대부분 RUNITME 생존을 사용한다.
애노테이션 적용 위치를 설정하는 애노테이션이다.
적용 위치 외에 해당 애노테이션을 선언하면 예외가 발생한다.
@Target(ElementType.TYPE)
클래스, 인터페이스 등 Type 위치에 애노테이션을 선언할 수 있다.
@Target(ElementType.FIELD)
필드 위치에 애노테이션을 선언할 수 있다.
@Target(ElementType.METHOD)
메서드 위치에 애노테이션을 선언할 수 있다.
자바 API 문서인 Javadoc 생성 시에 애노테이션 포함 여부를 지정한다.
보통 애노테이션 설계 시 함께 사용한다.
자식 클래스의 애노테이션 상속 여부를 결정한다.
구현은 포함되지 않고 상속 받은 자식 클래스에만 적용 가능하다.

Annotation 설계에 필요한 메타 애노테이션에 대해 알아봤다.
그렇다면 메타 애노테이션과 함께 애노테이션을 어떻게 만들 수 있는지 알아보자.
@Retention(RententionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface Anno { // ** @interface 타입으로 선언 가능 **
// 코드
}
먼저 애노테이션은 @interface 키워드를 사용하여 선언할 수 있다.
접근 제어자는 public이며, 생략 시 자동으로 public으로 선언된다.
애노테이션 내부에 선언되는 멤버를 element라고 한다.
@Retention(RententionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface Anno {
String name(); // ** element **
int age() default 20; // ** element **
}
element를 선언하기 위해서는 몇가지 규칙이 필요하다.

애노테이션에는 상속 관계가 없지만 모든 애노테이션이 상속하는 유일한 것이
바로 java.lang.annotation.Annotation이다.
해당 클래스에 있는 기능들은 구현할 필요는 없다. 바로 사용할 수 있다.
@Retention(RententionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface Anno { // ** @interface 타입으로 선언 가능 **
boolean eqauls(Object obj);
int hashCode();
String toString();
Class<? extends Anootation> annotationType();
}
boolean equals(Object obj)
파라미터로 전달한 Object와의 동일성을 비교한다.
int hashCode()
애노테이션의 해시코드를 반환한다.
String toString()
애노테이션 문자열 표현을 반환한다.
위 애노테이션을 예시로 @Anno(value = , ..) 형식으로 반환한다.
Class<? extends Anootation> annotationType()
애노테이션 타입을 반환한다.
위 예시의 경우 Anno.class를 반환한다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface Anno { // ** @interface 타입으로 선언 가능 **
String name();
int age() default 20;
int[] arr() default {};
}
@Anno(name = "java", arr = 1)
public void annoMethod() {
// 메서드 바디
}
애노테이션은 element의 값을 부여하여 사용할 수 있다.
element가 한개인 경우에는 element 이름을 생략하고 값을 지정할 수 있다.
default가 설정된 element는 애노테이션 선언 시 생략할 수 있다.
또한 element가 배열인 경우 항목이 1개면 배열 블럭 '{}'를 생략할 수 있다.

Reflection을 통해 HTTP Request start-line request target에 따라
메서드를 동적으로 호출할 수 있게 됐다.
여기에 Annotation을 더하면 리플렉션을 통해 메서드에 선언된 Annotation을 확인하고,
메서드 이름이 아닌 Annotation의 element와 request target을 비교하면서
더이상 메서드의 이름에 구애받지 않고 메서드를 호출할 수 있게 됐다.
이 외에도 Annotation의 element의 값을 꺼내어 다양하게 활용할 수 있다.

Spring Servlet의 핵심 기술인 Reflection과 Annotation을 이해했다면
Spring의 내부 동작 원리를 더욱 자세하게 이해한 것이다.
Reflection은 클래스의 내부 구조를 살펴보면서 동시에 사용할 수 있는 것,
Annotation은 읽을 수 있는 주석으로 Annotation과 내부 element 값을 통해
다양한 로직을 수행할 수 있다는 점을 기억하자.