자바 언어로 개발하다 보면, 다양한 어노테이션을 활용할 때가 많습니다. 도대체 내부가 어떻게 되어있는지 궁금해서 들어가 보면, 알 수 없는 이상한 코드들과 마주칩니다.
어노테이션을 직접 생성하거나 유심히 공부해보지 않았다면 처음 보는 코드가 많을 겁니다. 비교적 자주 사용하는 @Getter 어노테이션인데도, 내부를 까보면 마치 외계어처럼 낯설기만 합니다.
이번 포스팅에서는 어노테이션이 무엇인지, 종류에는 어떤 것이 있는지, 그리고 샘플 코드를 통해 어노테이션을 조금 더 깊게 알아봅시다. 끝에는 이 낯선 @Getter 코드도 조금은 이해할 수 있을 것입니다.
어노테이션이란?
어노테이션이란 과연 무엇일까요? 단순히 앞에 골뱅이(@) 기호를 붙인 것일까요? 사용법으로는 맞지만, 우리가 원하는 어노테이션의 정의와는 조금 다를 겁니다.
어노테이션은, 소스코드에 추가해서 사용할 수 있는 메타데이터의 일종입니다. 메타데이터는 또 뭘까요? 바로 다른 정보를 설명해주는 데이터입니다. 풀어쓰자면, 어노테이션은 소스코드에 추가해서 사용할 수 있는 소스코드를 설명해 주는 데이터라고 볼 수 있겠네요.
어노테이션은 다음과 같은 상황에서 쓸 수 있습니다.
- 컴파일러에게 어떠한 정보를 제공할 때
- 컴파일할 때와 설치 시 작업을 지정할 때
- 실행 시 별도의 처리가 필요할 때
@Deprecated 어노테이션을 사용하여 개발자에게 해당 메서드나 클래스가 더 이상 사용되지 않음을 알려주기도 하고, @Autowired 어노테이션을 사용하여 의존성 주입을 진행하거나 JUnit 테스트 메서드임을 나타내기 위해 @Test 어노테이션을 사용하기도 합니다.
어노테이션의 종류에는 세가지가 있습니다.
- 기본 어노테이션: 자바에서 기본적으로 제공하는 내장 어노테이션
- 메타 어노테이션: 다른 어노테이션을 정의할 때 사용되는 어노테이션
- 사용자 정의 어노테이션: 개발자가 직접 정의하는 어노테이션
여기서 기본 어노테이션과 메타 어노테이션에 대하여 조금 더 알아보겠습니다.
기본(내장) 어노테이션
JDK 6 기준으로는 다음과 같은 세 가지의 기본 어노테이션이 있었습니다.
- @Override
- @Deprecated
- @SupressWarnings
11 버전과 17 버전을 사용하는 지금은 @Native, @SafeVarargs와 같은 기본 어노테이션이 더 늘어났습니다. 그러나 개발하면서 자주 보는 것은 아직도 저 세 개의 어노테이션으로 한정되는 것 같습니다.
@Override 어노테이션은 해당 메서드가 부모 클래스에 있는 메서드를 Override 했다는 것을 명시적으로 선언합니다. 어떠한 클래스를 상속할 때 자식 클래스에서 많이 사용합니다.
public class Parent {
public void printName() {
System.out.println("I am parent.");
}
}
public class Child extends Parent {
@Override
public void printName() { // 매개변수를 추가하면 error 발생!
System.out.println("I am child");
}
}
Parent 클래스의 printName이라는 메서드를 Child 클래스에서 @Override 어노테이션을 사용하여 재정의 하였습니다. 만약, 여기서 Child 클래스의 printName 메서드에 어떤 매개변수든지 추가하면 에러가 발생합니다. 왜냐면 Parent 클래스에는 그러한 메서드가 존재하지 않기 때문이죠. 물론 @Override 어노테이션을 제거하면 에러가 발생하지 않습니다.
@Deprecated 어노테이션은 미리 만들어져 있는 클래스나 메서드가 더 이상 사용되지 않음을 개발자에게 알려줍니다. 만약에 해당 어노테이션이 붙은 메서드를 사용한다면, 해당 메서드가 deprecated 되었으니 조심하라고 “경고”가 나타납니다. “에러”가 아닌 “경고”이기에 여전히 사용을 할 수는 있습니다.
“그냥 지우면 되는 거 아닌가?” 라고 생각할 수도 있습니다. 메서드를 지워버린다고 가정을 해 봅시다. 지운 메서드를 참조하고 있는 프로그램은 어떻게 될까요? 에러가 발생하게 됩니다. 가장 좋은 방법은, @Deprecated로 알림을 주고, 수정 기간을 거쳐 코드를 지우는 것입니다.
@SupressWarnings 어노테이션은 컴파일러 경고를 무시하도록 지시합니다. 왜 이런 어노테이션이 필요할까요? 상황을 가정해 봅시다.
만약 deprecated한 메서드를 사용한다면 컴파일러에서 경고를 날릴 것입니다. 경고를 무시할 수도 있지만, 혹시 그러한 경고가 좀 거슬린다면? @SupressWarnings 어노테이션을 사용하여 “나도 경고 있는 거 아는데, 좀 무시해 줘~”라고 컴파일러에게 명시해 줄 수 있습니다.
@SupressWarnings("deprecation")
public void useDeprecated()
하지만, 이 어노테이션을 너무 남용할 경우 컴파일러 경고문의 존재 의의가 없어지니 유의해서 사용하길 바랍니다.
기본 어노테이션은 이정도로 살펴보았습니다. 그렇다면, 직접 어노테이션을 정의할 때는 어떻게 해야 할까요? 바로 메타 어노테이션을 사용하면 됩니다.
메타 어노테이션
메타 어노테이션은 어노테이션을 직접 정의할 때 사용됩니다. 자바에서는 다음과 같은 메타 어노테이션들이 있습니다.
- Target
- Retention
- Documented
- Inherited
@Target은, 해당 어노테이션을 어디에 적용할지 유형을 지정합니다. 적용 방법은 다음과 같습니다.
@Target(ElementType.METHOD) // 메서드에 적용
괄호 안에 적용 대상을 지정하는데, 그 대상 목록은 다음과 같습니다. 컴마(,)를 사용하여 여러 대상에 적용할 수 있습니다.
@Retention은, 어노테이션을 얼마나 오래 유지할지 결정합니다.
@Retention(RetentionPolicy.RUNTIME)
적용 가능한 대상은 다음과 같습니다.
@Documented는, 해당 어노테이션을 javadoc에 포함시킬지 여부를 결정합니다. javadoc 문서에 어노테이션 정보가 표시됩니다.
@Inherited는 해당 어노테이션이 상위 클래스로 상속되도록 지정합니다. 즉, 어노테이션이 적용된 클래스가 다른 클래스를 상속하는 경우, 하위 클래스에도 어노테이션 정보가 상속됩니다.
예제로 어노테이션을 살펴보자
그럼 다시 처음으로 돌아와서 @Getter 어노테이션을 살펴봅시다.
@Target({ElementType.FIELD, ElementType.TYPE}) - 1
@Retention(RetentionPolicy.SOURCE) - 2
public @interface Getter {
lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
AnyAnnotation[] onMethod() default {};
boolean lazy() default false;
@Deprecated - 3
@Retention(RetentionPolicy.SOURCE)
@Target({})
@interface AnyAnnotation {}
}
- @Getter는 필드와 클래스, 인터페이스, enum 등에 사용할 수 있습니다.
- @Getter 정보는 컴파일시 사라집니다.
- @AnyAnnotation은 곧 사라질 예정입니다.
이제 조금은 해석할 수 있겠죠? 참고로 default는 기본 값을 의미합니다. @Getter의 value 기본 값은 PUBLIC이고, onMethod 기본 값은 {}입니다.
마무리
어노테이션은 개발자가 코드에 추가적인 정보를 제공하고 개발 시간을 줄여주는 효율적인 도구지만, 오용하면 코드가 복잡해질 수도 있습니다. 따라서 적절한 상황에 잘 사용하고, 사용 목적과 의도를 명확히 이해하는 것이 중요하겠죠?
참고 자료
'Java & Spring Boot' 카테고리의 다른 글
자바의 기본 트랜잭션 매니저와 그 구현체들 (0) | 2024.04.13 |
---|---|
멀티모듈에서 공통 모듈의 의존성이 포함이 안돼요 (0) | 2024.02.21 |
Java 예외에는 어떤 유형이 있을까? (0) | 2023.04.02 |
자주 쓰이는 디자인 패턴을 알아보자(with Java) (0) | 2023.03.19 |
스프링 빈 중복 에러 해결하기 (0) | 2023.03.07 |