애너테이션(annotaion)이란?
들어가며
Spring이나 NestJS로 개발을 하다 보면 @Controller, @Service, @Transactional 같은 코드를 자연스럽게 쓰게 됩니다. 그런데 문득 이런 생각이 든 적이 있을거라 생각합니다.
"이 @ 기호는 도대체 뭐고, 어떻게 동작하는 걸까?"
처음엔 그냥 "붙이면 되는 것"으로 넘어가기 쉽지만, 애노테이션이 내부적으로 어떻게 동작하는지 이해하면 프레임워크를 훨씬 자신 있게 다룰 수 있습니다. 이 글에서는 애노테이션이 무엇인지, 어떤 원리로 동작하는지 차근차근 살펴보겠습니다.
애너테이션이란?
애너테이션은 코드에 메타데이터(metadata) 를 부여하는 문법입니다. 쉽게 말해, "이 클래스는 컨트롤러야", "이 메서드는 트랜잭션 안에서 실행해줘" 처럼 코드에 추가적인 정보를 표시하는 라벨 이라고 생각하면 됩니다.
직접 비즈니스 로직을 수행하지는 않지만, 컴파일러나 프레임워크가 이 라벨을 읽고 그에 맞는 동작을 대신 처리해줍니다.
@Controller // "나는 컨트롤러야"
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
@Transactional // "나는 트랜잭션 안에서 실행돼야 해"
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
표준 애너테이션
자바에서 기본적으로 제공하는 애너테이션입니다. 일부는 '메타 애너테이션'으로 커스텀 애너테이션을 정의하는 데 사용됩니다.
일반 애너테이션(java.lang)
| 에너테이션 | 설명 |
| @Override | 부모 클래스나 인터페이스의 메서드를 재정의할 때 사용. 컴파일러가 올바르게 오버라이딩됐는지 검증해줍니다. |
| @Deprecated | 더 이상 사용을 권장하지 않는 메서드·클래스에 표시. 사용 시 컴파일러가 경고를 발생시킵니다. |
| @SuppressWarnings | 컴파일러 경고를 억제할 때 사용. @SuppressWarnings("unchecked") 처럼 억제할 경고 종류를 지정합니다. |
| @SafeVarargs | 가변인자(varargs)와 제네릭을 함께 사용할 때 타입 안전성을 보장한다고 선언. final 또는 static 메서드에만 사용 가능합니다. |
| @FunctionalInterface | 함수형 인터페이스임을 명시. 추상 메서드가 딱 하나여야 하며, 람다식과 함께 사용됩니다. |
메타 애너테이션 (java.lang.annotation)
| 애너테이션 | 설명 |
| @Target | 애너테이션을 적용할 수 있는 위치를 지정. METHOD, FIELD, CLASS 등을 설정합니다. |
| @Documented | JavaDoc 생성 시 해당 애너테이션 정보도 문서에 포함되도록 합니다. |
| @Inherited | 부모 클래스에 붙인 애너테이션을 자식 클래스도 자동으로 상속받도록 합니다. |
| @Retention | 애너테이션 정보를 어느 시점까지 유지할지 결정. SOURCE · CLASS · RUNTIME 세 가지가 있습니다. |
| @Repeatable | 같은 애너테이션을 동일한 대상에 여러 번 반복 적용할 수 있도록 합니다. |
@Target
애너테이션이 적용가능한 대상을 지정하는데 사용된다. 아래는 '@UserAgentHeader'를 정의한 것인데, 이 애너테이션에 적용할수 있는 대상을 '@Target'으로 지정했다. 여려 개의 값을 지정할 때는 배열에서처럼 괄호{}를 사용해야한다.

'@Target' 으로 지정할수 있는 애너테이션 적용대상의 종류는 아래와 같다.
| 대상 타입 | 의미 |
| ANNOTATION_TYPE | 애너테이션 |
| CONSTRUCTOR | 생성자 |
| FIELD | 필드(멤버변수, enum) 상수 |
| LOCAL_VARIABLE | 지역변수 |
| METHOD | 메서드 |
| PACKAGE | 패키지 |
| PARAMETER | 매개변수 |
| TYPE | 타입(클래스, 인터페이스, enum) |
| TYPE_PARAMETER | 타입 매개변수(JDK1.8) |
| TYPE_USE | 타입이 사용되는 모든 곳(JDK1.8) |
동작 원리 - 리플렉션 (Reflection)
애너테이션 자체는 단순한 표시에 불과합니다. 실제로 동작하게 만드는 건 리플렉션(Reflection) 입니다.
리플렉션이란 런타임 시점에 클래스, 메서드, 필드 등의 정보를 동적으로 읽고 조작할 수 있는 Java의 기능입니다. Spring은 애플리케이션이 시작될 때 리플렉션으로 클래스를 스캔하고, 애너테이션이 붙어 있으면 그에 맞는 처리를 자동으로 수행합니다.
// Spring이 내부적으로 하는 일 (단순화된 예시)
Class<?> clazz = UserController.class;
if (clazz.isAnnotationPresent(Controller.class)) {
// 컨트롤러로 등록
registerAsController(clazz);
}
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Transactional.class)) {
// 트랜잭션 프록시 적용
applyTransactionProxy(method);
}
}
커스텀 애너테이션 만들기
주의 사항
애너테이션은 상속이 허용되지 않으므로 아래와 같이 명시적으로 Annotation을 조상으로 지정할 수 없다.
@interface TestInfo extends Annotation {
int count();
String testedBy();
}
직접 애너테이션을 만들면 반복되는 코드를 줄이고, 의도를 명확하게 표현할 수 있습니다.
// 1. 애너테이션 정의
@Target(ElementType.METHOD) // 메서드에만 붙일 수 있음
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 유지
public @interface LoginRequired {
}
// 2. AOP로 처리 로직 작성
@Aspect
@Component
public class LoginAspect {
@Before("@annotation(LoginRequired)")
public void checkLogin(JoinPoint joinPoint) {
// 로그인 여부 확인 로직
if (!isLoggedIn()) {
throw new UnauthorizedException("로그인이 필요합니다.");
}
}
}
// 3. 사용
@GetMapping("/mypage")
@LoginRequired // 이 메서드는 로그인한 사용자만 접근 가능
public MyPage getMyPage() {
...
}
애너테이션 요소 규칙
애너테이션의 요소를 선언할 때 반드시 지켜야 하는 규칙은 다음과 같다.
- 요소의 타입은 기본형, String,enum, 애너테이션, Class만 허용된다.
- ()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
@interface BadAnnotation {
// 허용되지 않는 타입 (List는 사용 불가)
List<String> tags();
// 매개변수 선언 불가
String name(String prefix);
// 예외 선언 불가
int count() throws Exception;
// 타입 매개변수 사용 불가
<T> Class<T> element();
}
@interface GoodAnnotation {
// 기본형
int count();
// String
String name();
// enum
RetentionPolicy policy();
// 애너테이션
Target target();
// Class
Class<?> element();
}
마치며
애너테이션은 단순히 "붙이면 동작하는 것"이 아니라, 리플렉션을 통해 프레임워크가 런타임에 읽고 처리하는 구조입니다. 이 원리를 이해하면 @Transactional이 왜 private 메서드에서 동작하지 않는지, @Autowired가 어떻게 자동으로 주입되는지 같은 질문에도 스스로 답을 찾을 수 있게 됩니다.
'Java' 카테고리의 다른 글
| [JAVA] 멀티프로세스&멀티쓰레드란 (1) | 2026.03.30 |
|---|---|
| [JAVA] 오버로딩(Overloading) 와 오버라이딩(Overriding)차이 (0) | 2024.02.23 |
| [JAVA] 자바의 JVM, JDK, JRE: 자바 개발 환경의 핵심 개념 (0) | 2024.02.23 |