애노테이션 정의
애노테이션은 해석 가능한 주석이라고 생각하면 편하다. 일반 주석과 달리, 애노테이션은 컴파일러나 런타임에서 해석될 수 있는 메타데이터를 제공한다. 즉, 애노테이션은 코드에 메모를 달아놓는 것처럼 특정 정보나 지시를 추가하는 도구로, 코드에 대한 메타데이터를 표현하는 방법이다.
=> 애노테이션은 코드에 대한 추가적인 정보를 주석처럼 달아놓는 것이고, 이는 컴파일러나 런타임에 사용된다
예시 코드
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoElement {
String value();
int count() default 0;
String[] tags() default {};
//MyLogger data(); // 다른 타입은 적용X
Class<? extends MyLogger> annoData() default MyLogger.class; // 클래스 정보는 가능
}
애노테이션 정의 규칙
- 데이터 타입
- 기본 타입 (int, float, boolean 등)
- String
Class
(메타데이터) 또는 인터페이스- enum
- 다른 애노테이션 타입
- 위의 타입들의 배열
- 앞서 설명한 타입 외에는 정의할 수 없다. 쉽게 이야기해서 일반적인 클래스를 사용할 수 없다.
- default 값
- 요소에 default 값을 추가할 수 있다
- ex)
String value() default "기본값";
- 요소 이름
- 메서드 형태로 정의된다
- 괄호를 포함하되 매개변수는 없어야 한다.
- 반환 값
- void를 반환 타입으로 사용 불가
- 예외
- 예외를 선언할 수 없다
- 특별한 요소 이름
- value라는 이름의 요소를 하나만 가질 경우, 애노테이션 사용 시 요소 이름을 생략할 수 있다
애노테이션 사용 예시
@AnnoElement(value = "data", count = 10, tags = {"t1", "t2"})
public class ElementData1 {
}
public class ElementData1Main {
public static void main(String[] args) {
Class<ElementData1> annoClass = ElementData1.class;
AnnoElement annotation = annoClass.getAnnotation(AnnoElement.class);
String value = annotation.value();
System.out.println("value = " + value);
int count = annotation.count();
System.out.println("count = " + count);
String[] tags = annotation.tags();
System.out.println("tags = " + Arrays.toString(tags));
}
}
annoClass.getAnnotation(AnnoElement.class)
: 클래스 메타데이터를 통해 특정 애노테이션 정보를 조회할 수 있다.- annotation 정보 조회 후, 애노테이션에 지정된 값을 메서드 호출을 통해 조회할 수 있다.
메타 애노테이션
: 애노테이션을 정의하는데 사용하는 특별한 애노테이션
@Retention
: 애노테이션의 생존 기간을 지정한다.RetentionPolicy.SOURCE
: 소스 코드에만 남아잇다. 컴파일 시점에 제거된다 (= 진짜 주석 같은 느낌)RetentionPolicy.CLASS
: 컴파일 후 class 파일까지는 남아있지만 자바 실행 시점에 제거된다. (기본값)RetentionPolicy.RUNTIME
: 자바 실행 중에도 남아있다. 대부분 이 설정을 사용한다.
@Target
: ElementType을 인자로 넘겨주어 애노테이션을 적용할 수 있는 위치를 지정한다.public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, MODULE, RECORD_COMPONENT; }
- 주로
TYPE
,FIELD
,METHOD
를 사용한다.
- 주로
@Documented
: 자바 API 문서를 만들 때 해당 애노테이션이 함께 포함되는지 지정한다. 보통 함께 사용한다.@Inherited
: 자식 클래스가 애노테이션을 상속 받을 수 있다.
적용 예시)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface AnnoMeta {
}
애노테이션과 상속
- 모든 애노테이션은
java.lang.annotation.Annotation
인터페이스를 묵시적으로 상속 받는다. - 모든 애노테이션은 기본적으로 Annotation 인터페이스르 확장하며, 이로 인해 자바에서 애노테이션은 특별한 형태의 인터페이스로 간주된다.
- 하지만 자바에서 애노테이션을 정의할 때, 개발자가 명시적으로 Annotation 인터페이스를 상속하거나 구현할 필요는 없다.
- @interface 키워드를 통해 정의하면, 자바 컴파일러가 자동으로 Annotation 인터페이스를 확장하도록 처리해준다.
public @interface MyAnnotation {}
->public interface MyAnnotation extends java.lang.annotation.Annotation {}
- 애노테이션과 상속
- 애노테이션은 다른 애노테이션이나 다른 인터페이스를 직접 상속할 수 없다. -> 애노테이션 사이에는 상속이라는 개념이 없다.
- 오직 java.lang.annotation.Annotation 인터페이스만 상속한다.
@Inherited
애노테이션끼리는 상속을 할 수 없지만, 애노테이션을 적용한 클래스들의 상속은 자유이다. Parent 클래스에서 애노테이션을 붙이면, 이를 상속한 Child 클래스는 해당 애노테이션의 영향을 받을까?
애노테이션을 정의할 때 @Inherited 메타 애노테이션을 붙이면, 애노테이션을 적용한 클래스의 자식도 해당 애노테이션을 부여받을 수 있다.
즉, Parent가 @Inherited가 붙은 애노테이션을 사용 중이라면, Child 클래스도 해당 애노테이션을 부여받는다.
단, 주의할 점으로 이 기능은 클래스 상속에서만 작동하고, 인터페이스의 구현체에는 적용되지 않는다. 그 이유는 다음과 같다. - **클래스 상속과 인터페이스 구현의 차이** - 인터페이스는 메서드의 시그니처만을 정의할 뿐, 상태나 행위를 가지지 않기 때문에, 인터페이스의 구현체가 애노테이션을 상속한다는 개념이 잘 맞지 않는다. - 구현과 상속은 다르다 - **인터페이스와 다중 구현, 다이아몬드 문제** - 인터페이스는 다중 구현이 가능하다. 만약 인터페이스의 애노테이션을 구현 클래스에서 상속하게 되면, 여러 인터페이스의 애노테이션 간의 충돌이나 모호한 상황이 발생할 수 있다.
자바 기본 애노테이션
앞서 확인해본 메타 애노테이션 말고도 코드에 직접 사용할 수 있는 자바 언어가 기본으로 제공하는 애노테이션도 있다.
@Override
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
- 메서드 재정의가 정확하게 잘 되었는지 컴파일러가 체크하는데 사용한다.
@Deprecated
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) public @interface Deprecated { String since() default ""; boolean forRemoval() default false; }
- @Deprecated` 는 더 이상 사용되지 않는다는 뜻이다. 이 애노테이션이 적용된 기능은 사용을 권장하지 않는다.
- 인자
since
: 더 이상 사용하지 않게 된 버전 정보forRemoval
: 미래 버전에 코드가 제거될 예정
@SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }
- 이름 그대로 경고를 억제하는 애노테이션이다. 자바 컴파일러가 문제를 경고하지만, 개발자가 해당 문제를 잘 알고 있기 때문에, 더는 경고하지 말라고 지시하는 애노테이션이다.
- 사용되는 대표적인 값
all
: 모든 경고를 억제deprecation
: 사용이 권장되지 않는(deprecated) 코드를 사용할 때 발생하는 경고를 억제unchecked
: 제네릭 타입과 관련된 unchecked 경고를 억제serial
: Serializable 인터페이스를 구현할 때 serialVersionUID 필드를 선언하지 않은 경우 발생하는 경고를 억제rawtypes
: 제네릭 타입이 명시되지 않은(raw) 타입을 사용할 때 발생하는 경고를 억제unused
: 사용되지 않는 변수, 메서드, 필드 등을 선언했을 때 발생하는 경고를 억제
스프링에서의 애노테이션(with 리플렉션)
스프링은 리플렉션과 애노테이션을 활용하여 다음의 마법 같은 기능들을 제공한다
- 의존성 주입: 스프링은 리플렉션을 사용하여 객체의 필드나 생성자에 자동으로 의존성을 주입한다. 개발자는 단순히
@Autowired
애노테이션만 붙이면 된다. - ORM: JPA는 애노테이션을 사용하여 자바 객체와 데이터베이스 테이블 간의 매핑을 정의한다. 예를 들어,
@Entity
,@Table
,@Column
등의 애노테이션으로 객체-테이블 관계를 설정한다 - AOP: 스프링은 리플렉션을 사용하여 런타임에 코드를 동적으로 주입하고,
@Aspect
,@Before
,@After
등의 애노테이션으로 관점 지향 프로그래밍을 구현한다. - 설정의 자동화: @Configuration
,
@Bean` 등의 애노테이션을 사용하여 다양한 설정을 편리하게 적용한다. - 트랜잭션 관리:
@Transactional
애노테이션만으로 메서드 레벨의 DB 트랜잭션 처리가 가능해진다.
'Java' 카테고리의 다른 글
[Java] 자바 기본 개념 정리 (기본 문법 제외) (2) | 2025.01.02 |
---|---|
[Java] Garbage Collection (3) | 2025.01.02 |
[Java] JVM / JRE / JDK (3) | 2025.01.01 |
[Java] Reflection (0) | 2024.10.10 |
[Java] I/O (0) | 2024.10.10 |