여기서부터는 많은 개발자들이 이름만 알고 정확히는 이해하지 못하는 개념이다.
실제로 Spring을 사용하면서
@Service
@Transactional
@RestController
@GetMapping
을 매일 사용하지만,
"이 Annotation은 왜 또 Annotation이 붙어 있지?"
라는 의문을 가진 적이 있을 것이다.
이번 글을 이해하면 Spring이 새로운 Annotation을 얼마나 유연하게 확장하는지 알 수 있다.
Meta Annotation
"Annotation에 붙는 Annotation"
Annotation에도 Annotation을 붙일 수 있다.
우리는 지금까지
@Service
public class UserService {
}
처럼 사용했다.
그런데 실제 Spring 내부 코드를 보면
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
}
이렇게 되어 있다.
여기서
@Target
@Retention
@Documented
@Component
모두 Annotation이다.
즉,
Annotation 위에 또 Annotation이 붙어 있다.
이것을 Meta Annotation이라고 한다.
왜 이런 구조를 만들었을까?
먼저 Meta Annotation이 없다면 어떻게 될까?
Spring이 Bean을 찾는 기준이
@Service
@Repository
@Controller
세 개뿐이라고 가정해보자.
그러면 Spring 내부는
if (@Service)
Bean 등록
if (@Repository)
Bean 등록
if (@Controller)
Bean 등록
처럼 계속 늘어나야 한다.
새로운 Annotation이 생길 때마다
Spring 코드를 수정해야 한다.
굉장히 비효율적이다.
Spring은 더 좋은 방법을 선택했다.
Spring은
"Bean으로 등록하고 싶은 Annotation이라면 @Component를 붙여라."
라는 규칙을 만들었다.
즉,
@Service
의 내부는
@Component
public @interface Service {
}
이다.
@Repository
도
@Component
public @interface Repository {
}
이다.
@Controller
도
@Component
public @interface Controller {
}
이다.
그러면 Spring은
@Service 발견
↓
@Component가 붙어있는지 확인
↓
붙어있다
↓
Bean 등록
만 수행하면 된다.
그림으로 이해하기
@Component
▲
│
┌────────────┼────────────┐
│ │ │
@Service @Repository @Controller
│ │ │
▼ ▼ ▼
UserService UserRepository UserController
Spring은
맨 위의 @Component만 이해하면 된다.
직접 만들어보자
Bean 역할을 하는 Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {
}
MyComponent를 사용하는 Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@MyComponent
public @interface MyService {
}
사용
@MyService
public class UserService {
}
Spring과 비슷한 프로그램이라면
UserService
↓
@MyService 존재
↓
MyService에 @MyComponent 존재
↓
Bean 등록
을 수행할 수 있다.
Meta Annotation을 검사하는 과정
Reflection은 Annotation도 조사할 수 있다.
Class<?> clazz = UserService.class;
Annotation[] annotations =
clazz.getAnnotations();
여기서
@MyService
를 얻는다.
그리고
Class<?> annotationType =
annotation.annotationType();
를 호출하면
MyService.class
를 얻는다.
다시 Reflection을 사용한다.
annotationType.isAnnotationPresent(MyComponent.class);
결과
true
즉,
UserService
↓
@MyService
↓
MyService.class
↓
@MyComponent 존재
↓
Bean 등록
이라는 구조이다.
Spring에서 가장 많이 쓰이는 Meta Annotation
@Component
가장 기본이 되는 Annotation이다.
@Component
public class UserService {
}
직접 Bean 등록
@Service
@Service
public class UserService {
}
실제로는
@Component
public @interface Service {
}
이다.
@Repository
@Repository
public class UserRepository {
}
역시
@Component
public @interface Repository {
}
이다.
@Controller
@Controller
public class UserController {
}
역시
@Component
public @interface Controller {
}
이다.
그런데 왜 굳이 나눌까?
모두 @Component인데
@Component
public class UserService {
}
@Component
public class UserRepository {
}
로 쓰면 안 될까?
가능하다.
하지만 의미가 사라진다.
@Service
를 보면
서비스 계층
임을 알 수 있다.
@Repository
를 보면
데이터 접근 계층
임을 알 수 있다.
@Controller
를 보면
웹 요청을 처리하는 클래스
임을 알 수 있다.
즉,
기능은 @Component와 같지만 역할을 표현하기 위해 별도의 Annotation을 제공하는 것이다.
Spring은 Meta Annotation을 적극 활용한다.
예를 들어
@RestController
를 보자.
내부는 거의 이런 형태이다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Controller
@ResponseBody
public @interface RestController {
}
즉,
@RestController
↓
@Controller
↓
@Component
↓
Bean 등록
동시에
@ResponseBody
↓
JSON 응답
도 수행한다.
사용자도 만들 수 있다.
실무에서도 자주 사용한다.
예를 들어
모든 API에 공통 정책을 적용하고 싶다면
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/api/v1")
public @interface ApiController {
}
사용
@ApiController
public class UserController {
}
그러면
@RestController
+
@RequestMapping("/api/v1")
를 동시에 적용할 수 있다.
전체 흐름
UserService
↓
@MyService
↓
MyService Annotation
↓
@MyComponent 존재
↓
Spring Reflection
↓
Bean 등록
면접 포인트
Meta Annotation이란?
Annotation에 붙는 Annotation으로, Annotation의 동작 방식이나 의미를 정의하기 위해 사용된다.
Spring에서 가장 대표적인 Meta Annotation은?
@Service, @Repository, @Controller는 모두 내부적으로 @Component를 Meta Annotation으로 가지고 있으며, Spring은 이를 기반으로 Bean을 등록한다.
Meta Annotation을 사용하는 이유는?
- 코드 중복 제거
- 기능 확장 용이
- 역할 표현 가능
- 새로운 Annotation을 쉽게 조합 가능
다음 글 추천
이 흐름이라면 다음은 Dynamic Proxy 원리가 가장 자연스럽다.
Reflection → Class 객체 → Annotation → Meta Annotation을 이해한 상태에서 보면,
Spring AOP, @Transactional, @Async가 왜 프록시를 통해 동작하는지
를 한 번에 이해할 수 있다.
'language > java' 카테고리의 다른 글
| JDK Proxy vs CGLIB (0) | 2026.06.16 |
|---|---|
| Dynamic Proxy 원리 (0) | 2026.06.16 |
| Annotation 동작 원리 (0) | 2026.06.16 |
| Class 객체란? (0) | 2026.06.16 |
| Reflection 원리 (0) | 2026.06.16 |
댓글