Annotation
Annotation (어노테이션)
왜 쓰는지
Java 코드는 코드 자체(비즈니스 로직)와 메타정보(설정, 검증, 마크)를 섞어서 작성합니다. 메타정보를: - 컴파일러에게 전달 (오류 체크) - 빌드 도구에게 전달 (코드 생성) - 런타임 프레임워크에게 전달 (자동 설정)
이를 명확하고 체계적으로 하기 위해 Annotation이 필요합니다.
핵심: Annotation은 코드에 메타데이터를 붙이는 문법입니다. 클래스, 메서드, 필드에 정보를 추가하면, 컴파일러·프레임워크·런타임이 이를 읽고 처리합니다.
어떻게 쓰는지
기본 Annotation (Java 내장)
// 1️⃣ @Override: 부모 메서드를 재정의했음을 선언
class Parent {
public void greet() {}
}
class Child extends Parent {
@Override
public void greet() { // 컴파일러가 부모에 greet 메서드 있는지 검사
System.out.println("Hello");
}
}
// 2️⃣ @Deprecated: 더 이상 사용하지 말 것을 알림
class OldApi {
@Deprecated(since = "2.0", forRemoval = true)
public void oldMethod() {} // 경고: 대체 메서드를 사용하세요
}
// 3️⃣ @SuppressWarnings: 컴파일 경고 무시
@SuppressWarnings("unchecked")
List list = new ArrayList(); // 경고 메시지 표시 안 함
// 4️⃣ @FunctionalInterface: 함수형 인터페이스임을 보증
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
커스텀 Annotation 정의
// 메타 어노테이션: Annotation을 정의하는 데 사용
import java.lang.annotation.*;
@Target(ElementType.METHOD) // 어디에 붙일지
@Retention(RetentionPolicy.RUNTIME) // 언제까지 유지할지
@Documented // JavaDoc에 포함
public @interface MyAnnotation {
String value(); // 필수 속성
int count() default 1; // 기본값 있는 속성
String[] tags() default {}; // 배열 속성
}
// 사용
class MyClass {
@MyAnnotation(value = "test", count = 5, tags = {"a", "b"})
public void myMethod() {}
}
Spring의 Annotation 예시
// 컨테이너에 빈 등록
@Component
@Service
@Repository
@Controller
public class UserService {
// 의존성 자동 주입
@Autowired
private UserRepository repo;
// 메서드 레벨 Annotation
@Transactional // 트랜잭션 자동 관리
public void saveUser(User user) {
repo.save(user);
}
// 유효성 검증
public void register(@NotNull String name, @Email String email) {}
}
// 설정 클래스
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public DataSource dataSource() { ... }
}
Reflection으로 Annotation 읽기
class MyClass {
@MyAnnotation(value = "test")
public void myMethod() {}
}
// Reflection으로 Annotation 정보 읽기
Method method = MyClass.class.getDeclaredMethod("myMethod");
// Annotation 존재 여부 확인
if (method.isAnnotationPresent(MyAnnotation.class)) {
// Annotation 객체 얻기
MyAnnotation anno = method.getAnnotation(MyAnnotation.class);
System.out.println(anno.value()); // "test"
}
// 모든 Annotation 조회
Annotation[] annotations = method.getDeclaredAnnotations();
for (Annotation anno : annotations) {
System.out.println(anno.annotationType().getName());
}
언제 쓰는지
| 상황 | 선택 | 이유 |
|---|---|---|
| 프레임워크 설정 | ✅ Annotation | 설정을 코드에 가깝게, 자동 스캔 |
| 컴파일 타임 검사 | ✅ @Override 등 | 실수 방지 |
| 메타데이터 관리 | ✅ Annotation | 코드와 설정 통합 |
| 테스트 마킹 | ✅ @Test, @Before | 테스트 프레임워크 인식 |
| 유효성 검증 | ✅ @NotNull, @Email | 런타임 검증 |
| 과도한 마킹 | ❌ 코드가 지저분함 | 가독성 떨어짐 |
장점
| 장점 | 설명 |
|---|---|
| 코드 간결성 | XML 설정 파일 제거, 설정을 코드에 통합 |
| 자동화 | 프레임워크가 자동으로 처리 (DI, 트랜잭션 등) |
| 타입 안전성 | Annotation 속성도 타입 체크 가능 |
| 메타정보 보관 | 클래스, 메서드의 의도를 명확하게 표현 |
| Reflection과 결합 | 런타임에 유연한 처리 가능 |
단점
| 단점 | 설명 |
|---|---|
| 학습곡선 | Annotation 개념과 메타 어노테이션 이해 필요 |
| 마법(Magic) | Annotation이 자동으로 처리되면 동작 추적 어려움 |
| 과다 사용 | 코드가 Annotation으로 지저분해질 수 있음 |
| 오류 메시지 | 문제 발생 시 Annotation 관련 오류가 명확하지 않음 |
| 성능 | Reflection으로 Annotation 읽으면 성능 비용 발생 |
특징
1. 메타 어노테이션 (Annotation을 정의하는 Annotation)
@Target(ElementType.METHOD) // 적용 대상
@Retention(RetentionPolicy.RUNTIME) // 생명주기
@Documented // JavaDoc 포함
@Inherited // 상속 여부
public @interface MyAnnotation {}
| 메타 어노테이션 | 설명 |
|---|---|
@Target |
어디에 붙을 수 있는가 (TYPE, METHOD, FIELD, PARAMETER, ...) |
@Retention |
언제까지 유지되는가 (SOURCE, CLASS, RUNTIME) |
@Documented |
JavaDoc에 포함되는가 |
@Inherited |
상속되는가 |
@Repeatable |
같은 대상에 여러 번 붙을 수 있는가 |
2. @Retention 레벨별 동작
// 1️⃣ SOURCE: 컴파일 시 삭제됨
@Retention(RetentionPolicy.SOURCE)
@Override // 컴파일러만 확인, 컴파일 후 사라짐
// 2️⃣ CLASS: Class 파일까지만 유지
@Retention(RetentionPolicy.CLASS)
public @interface BuildTime {} // 빌드 도구가 읽음, 런타임에는 없음
// 3️⃣ RUNTIME: 런타임에도 유지
@Retention(RetentionPolicy.RUNTIME)
@Service // 프레임워크가 런타임에 Reflection으로 읽음
3. Spring의 Annotation 처리 프로세스
@Bean 달린 메서드
↓ (Spring ApplicationContext가 Reflection으로 스캔)
ClassPathBeanDefinitionScanner
↓ (메타데이터 수집)
BeanDefinition 생성
↓ (Bean 인스턴스 생성 & 의존성 주입)
@Autowired 붙은 필드에 주입
4. 주요 Java/Jakarta EE Annotation
| Annotation | 용도 |
|---|---|
@Override |
부모 메서드 재정의 |
@Deprecated |
더 이상 사용 금지 |
@FunctionalInterface |
함수형 인터페이스 보증 |
@SuppressWarnings |
경고 무시 |
@SafeVarargs |
가변인수 경고 무시 |
주의할 점
❌ Annotation은 동작을 수행하지 않음
@MyAnnotation // 이것만으로는 아무것도 안 함
public class MyClass {}
// Annotation 처리 코드가 필요
public class AnnotationProcessor {
public static void process(Class<?> clazz) {
if (clazz.isAnnotationPresent(MyAnnotation.class)) {
// 실제 동작
System.out.println("처리됨");
}
}
}
✅ 올바른 방식: - 프레임워크가 처리하는 표준 Annotation 사용 (Spring, JPA) - 커스텀 Annotation은 처리 코드와 함께
⚠️ @Retention(RUNTIME) 주의
⚠️ Annotation 값은 상수만 가능
⚠️ @Inherited는 메서드에 상속되지 않음
정리
| 항목 | 설명 |
|---|---|
| Annotation | 메타데이터를 코드에 붙이는 문법 |
| 메타 어노테이션 | Annotation을 정의하는 @Target, @Retention 등 |
| @Retention | SOURCE(컴파일만), CLASS(빌드), RUNTIME(프레임워크) |
| 용도 | 프레임워크 설정, 컴파일 검사, 런타임 처리 |
| 주의 | Annotation 자체는 동작 안 함, 처리 코드 필요 |
| 권장 | 표준 Annotation(Spring, JPA) 사용 |
관련 파일: - Reflection — 런타임에 Annotation 읽는 방법 - Lambda — @FunctionalInterface와 함께 사용