[Java] Annotation

벼랑 끝 코딩·2025년 3월 17일
0

Java

목록 보기
32/40

지난 시간 마법 같은 일들을 쉽게 처리해주는 Spring의
Servlet 핵심 기술 중 하나인 Reflection에 대해 알아봤다.

객체 자신을 반사하여 자신의 구조를 살펴볼 수 있는 리플렉션 덕분에
우리는 HTTP Request start-line의 request-target에 따라
메서드를 동적으로 수행할 수 있게 됐다!

리플렉션은 애노테이션과 함께 사용되어 놀라운 수행 능력을 보여주는데
이번엔 애노테이션이란 무엇인가, 어떻게 쓰일 수 있는가에 대해 알아보자.

Annotation

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

메타 애노테이션

애노테이션을 직접 만들기 전에 먼저 메타 애노테이션에 대해 이해해야 한다.
메타 애노테이션이란, 애노테이션을 정의하는데 사용하는 애노테이션이다.
애노테이션을 설계할 때 필요한 기본 설정 정보와 같다고 생각하면 된다.

@Retention

@Retention
public @interface Anno {
	// 코드
}

애노테이션의 생존 기간을 정의하는 애노테이션이다.

  • @Retention(RetentionPolicy.SOURCE)
    소스 코드일 때에만 생존하는 애노테이션으로 컴파일 시점에 제거된다.

  • @Retention(RetentionPolicy.CLASS)
    컴파일 이후 .class 파일 까지만 생존하는 애노테이션으로 자바 실행 시점에 제거된다.

  • @Retention(RetentionPolicy.RUNTIME)
    실행 중에도 생존하는 애노테이션으로 대부분 RUNITME 생존을 사용한다.

@Target

애노테이션 적용 위치를 설정하는 애노테이션이다.
적용 위치 외에 해당 애노테이션을 선언하면 예외가 발생한다.

  • @Target(ElementType.TYPE)
    클래스, 인터페이스 등 Type 위치에 애노테이션을 선언할 수 있다.

  • @Target(ElementType.FIELD)
    필드 위치에 애노테이션을 선언할 수 있다.

  • @Target(ElementType.METHOD)
    메서드 위치에 애노테이션을 선언할 수 있다.

@Documented

자바 API 문서인 Javadoc 생성 시에 애노테이션 포함 여부를 지정한다.
보통 애노테이션 설계 시 함께 사용한다.

@Inherited

자식 클래스의 애노테이션 상속 여부를 결정한다.
구현은 포함되지 않고 상속 받은 자식 클래스에만 적용 가능하다.

Annotation 설계

Annotation 설계에 필요한 메타 애노테이션에 대해 알아봤다.
그렇다면 메타 애노테이션과 함께 애노테이션을 어떻게 만들 수 있는지 알아보자.

@interface

@Retention(RententionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface Anno {  // ** @interface 타입으로 선언 가능 **
	// 코드
}

먼저 애노테이션은 @interface 키워드를 사용하여 선언할 수 있다.
접근 제어자는 public이며, 생략 시 자동으로 public으로 선언된다.

element

애노테이션 내부에 선언되는 멤버를 element라고 한다.

@Retention(RententionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface Anno {
	
    String name();  // ** element **
    int age() default 20;  // ** element **
}

element를 선언하기 위해서는 몇가지 규칙이 필요하다.

  • 괄호 '()'를 사용하여 메서드 형태로 선언한다.
  • 매개변수는 없다.
  • 블럭은 존재하지 않는다.
  • void 타입 선언은 불가능하다.
  • 일반 클래스 외에 기본 타입, String, 인터페이스, ENUM, 배열 타입 등으로 선언 가능하다.
  • default 키워드를 사용하여 기본 값을 지정할 수 있다.

java.lang.annotation.Annotation 상속

애노테이션에는 상속 관계가 없지만 모든 애노테이션이 상속하는 유일한 것이
바로 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를 반환한다.

Annotation 사용

@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과 Annotation

Reflection을 통해 HTTP Request start-line request target에 따라
메서드를 동적으로 호출할 수 있게 됐다.

여기에 Annotation을 더하면 리플렉션을 통해 메서드에 선언된 Annotation을 확인하고,
메서드 이름이 아닌 Annotation의 element와 request target을 비교하면서
더이상 메서드의 이름에 구애받지 않고 메서드를 호출할 수 있게 됐다.

이 외에도 Annotation의 element의 값을 꺼내어 다양하게 활용할 수 있다.

마무리

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

profile
복습에 대한 비판과 지적을 부탁드립니다

0개의 댓글