Contents
Annotation in Java
   Nov 21, 2022     8 min read

Annotaion

어노테이션(Annotation)이란 ?

JDK 5 부터 했으며, 클래스 메소드, 변수 등의 선언시에 “@” 를 사용하는 것을 뜻한다.

어노테이션은 메타데이터(Metadata)라고도 불린다.

➡️어노테이션의 역할

  • 컴파일러에게 정보를 알려줄 때
  • 컴파일할 때와 설치(deployment)시의 작업을 지정할 때
  • 실행할 때 별도의 처리가 필요할 때

기본 어노테이션

JDK 6까지는 3개의 어노테이션이 있다.

  • @Override
  • @Deprecated
  • @SupressWarnings

하나 씩 알아보자.


@Override

해당 메소드가 부모 클래스에 있는 메소드를 Override 했다는 것을 명시적으로 선언한다.

자식 클래스에 여러 개의 메소드가 있을 때, 어떤 메소드가 Override 되었는지 쉽게 알 수 있다.


@Deprecated

해당 메소드를 더 이상 사용하지 않는다는 것을 알려주는 어노테이션

안쓰는 메소드 지워버리지 왜 이런 어노테이션을 사용해서 남겨두는 것일까 ?

만약 다른 개발자하고 같이 개발 중인데 어떤 클래스나 메소드를 불필요하다고 생각해서 지워버리면 그 메소드나 클래스를 참조하는 다른 개발자가 만든 프로그램이 변경된 사항을 모르고 계속 컴파일할 때 에러를 발생하고 진땀을 흘리고 있을 것이다.

이런 상황의 예방차원으로 Deprecated 를 사용한다.

물론 가장 좋은 방법은 계도 기간을 거쳐 알림을 준 후 지우는 것이 바람직하다.

  • 예제

Untitled

Untitled 1

위의 코드를 컴파일하면

$ javac AnnotaionSample.java
Note : AnnotaionSample.java uses or overrides a deprecated API.
Note : Recomplie with -Xlint:deprecation for details.

라고 뜬다.

이처럼 Deprecated 된 메소드를 컴파일하면 Deprecated API를 사용하는 메소드가 있으니 “-Xlint:deprecation”이라는 옵션을 추가하여 컴파일을 다시 해서 확인해 보라는 메시지가 나온다.

$ javac -Xlint:deprecation AnnotionSample.java

AnnotationSample.java:6: warning : [deprecation] noMoreUse() int AnnotaionDeprecateds has been deprecated
             child.noMoreUse();
                  ^

1 warning

noMoreUse()라는 메소드는 deprecated 되었으니 조심하라고 경고가 나타난다.

따라서 @Deprecated 어노테이션만 추가해주면 “그 메소드나 클래스는 더 이상 사용하지 않는다”고 정의할 수 있다.


@SupressWarnings

컴파일러에서 경고(Warnings)를 알리 경우가 있다. 프로그램에는 문제가 없는데, 내가 잘 알아서 하는데 이러한 경고가 나타나면 웬지 마음이 불편하다.

그럴 때 컴파일러에게 “이 부분은 경고 해줄 필요가 없다”라고 말해줄 수 있다.

그럴 때 사용하는것이 SupressWarnings 어노테이션이다.

  • 예제

Untitled 2

@SuppessWarnings 를 달아주면, 컴파일에게 나도 이 경고를 인지하고 있다고 말해준다.

따라서 child.noMoreUse() 라는 곳에 아무 표시가 없는 것이다.

하지만 이 어노테이션을 너무 남용할 경우 Deprecated 된 메소드를 사용해도 모르고 넘어갈 수도 있으니 유의하기 바란다.


메타 어노테이션(Meta annotation)

메타 어노테이션은 어노테이션을 선언할 때 사용한다.

  • @Target
  • @Retention
  • @Documented
  • @Inherited

@Target

어노테이션을 어떤 것에 적용할지를 선언할 때 사용한다.

@Target(ElementType.METHOD)
  • @Target() 괄호 안에 적용 대상 지정하는데, 대상 목록
요소 타입대상
CONSTRUCTOR생성자 선언시
FIELDenum 상수를 포함한 필드(field) 값 선언시
LOCAL_VARIABLE지역 변수 선언시
METHOD메소드 선언시
PACKAGE패키지 선언시
PARAMETER매개 변수 선언시
TYPE클래스, 인터페이스, enum등 선언시

@Retention

얼마나 오래 어노테이션 정보가 유지되는지를 다음과 같이 선언

@Retention(RetentionPolicy.RUNTIME)

괄호안에 지정하는 적용 가능한 대상 목록

 대상
SOURCE어노테이션 정보가 컴파일시 사라짐
CLASS클래스 파일에 있는 어노테이션 정보가 컴파일러에 의해서 참조 가능함. 하지만 가상 머신(Virtual Machine)에서는 사라짐
RUNTIME실행시 어노테이션 정보가 가상 머신에 의해서 참조 가능

@Documented

어노테이션에 대한 정보가 Javadoc(API) 문서에 포함된다는 것을 선언한다.

javadoc 만드는 법


@Inherited

모든 자식 클래스에서 부모 클래스의 어노테이션을 사용 가능하다는 것을 선언한다.


추가 : @interface

이 어노테이션은 어노테이션을 선언할 때 사용한다.

예제를 통해서 이해하자

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)  // ①
@Retention(RetentionPolicy.RUNTIME) // ②
public @interface UserAnnotaion { // ③
    public int number();  // ④
    public String text() default "This is first annotaion"; // ⑤
}

Annotaion에 관련된 클래스들은 java.lang.annotation 패키지에 선언되어 있다.

  1. @Target은 해당 어노테이션 사용 대상을 지정한다. 여기서 ElementType.METHOD를 소괄호 안에 넣어 줌으로써 이 어노테이션은 메소드에 사용할 수 있다고 지정된 것이다.
  2. @Retention은 어노테이션 유지 정보를 지정하는 데 사용한다. 소괄호 안에 RetentionPolicy.RUNTIME으로 지정하면 실행시에 이 어노테이션을 참조하게 된다.
  3. 어노테이션 이름인 UserAnnotation 앞에는 @interface가 선언되어 있다. 처음 보면 익숙하지 않겠지만, 클래스나 인터페이스를 선언할 때처럼 @interface로 선언하면 @UserAnnotaion으로 어노테이션이 사용 가능해진다.
  4. 어노테이션 선언 안에는 numer() 라는 메소드와 text()라는 메소드가 있다. number()의 리턴 타입은 int이며, text()의 리턴 타입은 String이다. 이렇게 메소드처럼 어노테이션 안에 선언해 놓으면, 이 어노테이션을 사용할 때 해당 항목에 대한 타입으로 값을 지정 가능하다.
  5. text()를 보면 default라는 예약어를 쓴 뒤 문자열이 지정되어 있는 것을 볼 수 있다. default 예약어를 사용할 경우에는 , default 뒤에 값이 이 어노테이션을 사용할 때의 기본값이 된다. 즉, 값을 지정하지 않아도 default 값으로 지정된다.
  • 직접 만든 어노테이션 사용 예제
public class UserAnnotationSample {
    @UserAnnotation(number=0)
    public static void main(String[] args) {
        AnnotationSample sample = new AnnotationSample();
    }

    @UserAnnotation(number = 1)
    public void annotationSample1() {

    }

    @UserAnnotation(number = 2, text="second")
    public void annotationSample2(){

    }

    @UserAnnotation(number = 3, text="third")
    public void annotationSample3(){

    }
}

어노테이션 선언 클래스에 지정해 놓은 각 메소드의 이름에 해당하는 값을 소괄호 안에 넣어 주어야만 한다.

따라서 number()와 text()에 해당하는 어노테이션 값들을 지정해 주면 된다.

text()의 경우 default를 사용하여 기본 값을 지정해 주었기 때문에 별도로 값을 지정해 주지 않아도 컴파일하는 데 전혀 문제가 없다.

반면에

number()는 기본값이 지정되어 있지 않으므로 반드시 값을 지정해줘야한다.

UserAnnotation에서 사용한 @Target 어노테이션을 보면

@Target(ElementType.METHOD)

만약 두 개 이상의 어노테이션을 선언할 때에는 중괄호를 한 후 쉼표(,)로 구분해 주면 된다.

클래스 선언시와 메소드 선언시에 어노테이션을 사용할 수 있도록 하려면 아래와 같이한다.

@Target({ElementType.METHOD, ElementType.TYPE})

만약 범위를 벗어나서 선언하게 되면 컴파일 시 에러가 발생한다.

  • 예시: 대상을 메소드로만 한정해 놓고 UserAnnotationSample 클래스의 선언부에 어노테이션을 지정하면 에러가 뜬다.
@UserAnnotation(number=0)
public class UserAnnotationSample {
    //중간 생략
}
$ javac UserAnnotationSample.java
UserAnnotationSample.java:4: annotation type not applicable to this kind of declaration
@UserAnnotation(number=0)
^

1 error

이와 같이 에러가 뜬다.


어노테이션에 선언한 값은 어떻게 확인할까?

어노테이션의 값을 어떻게 확인할 수 있을까?

  • 예시
import java.lang.reflect.Method;

public class UserAnnotationCheck {
    public static void main(String[] args) {
        UserAnnotationCheck sample = new UserAnnotationCheck();
        sample.checkAnnotations(UserAnnotationSample.class);
    }

    public void checkAnnotations(Class useClass){
        Method[] methods = useClass.getDeclaredMethods(); // ①

        for(Method tempMethod: methods){
            UserAnnotation annotation = tempMethod.getAnnotation(UserAnnotation.class); //②
            if (annotation != null) {
                int number = annotation.number(); //③
                String text = annotation.text(); // ③
                System.out.println(tempMethod.getName() + "() : number=" +number+ " text="+ text);
            }else {
                System.out.println(tempMethod.getName() + "() : annotation is null.");
            }
        }
    }
}

여기서 Class, Method는 자바의 리플렉션(Reflection)이라는 API에서 제공하는 클래스들이다.

Class라는 클래스는 클래스의 정보를 확인한다.

Method라는 클래스는 메소드의 정보를 확인하는 데 사용한다.

  • 코드 분석해보면 …
    1. Class 클래스에 선언되어 있는 getDeclaredMethods() 메소드를 호출하면, 해당 클래스에 선언되어 있는 메소드들의 목록을 배열로 리턴한다.
    2. Method 클래스에 선언되어 있는 getAnnotation() 이라는 메소드를 호출하면, 해당 메소드에 선언되어 있는 매개 변수로 넘겨준 어노테이션이 있는지 확인하고, 있을 경우 그 어노테이션의 객체를 리턴해 준다.
    3. 어노테이션에 선언된 메소드를 호출하면, 그 값을 리턴해준다.

리플렉션 API를 사용하면 선언하여 사용하는 어노테이션에 대한 정보를 확인할 수 있다.

  • 결과
annotationSample1() : number=1 text=This is first annotaion
annotationSample2() : number=2 text=second
annotationSample3() : number=3 text=third
main() : number=0 text=This is first annotaion

어노테이션은 상속이 불가

어노테이션은 확장하는 것이 불가능하다. 즉, extends라는 예약어를 사용할 수가 없다..

코드가 반복되는 문제가 발생한다고 하더라도 어쩔 수 없이 재사용은 불가능하다.

그렇다면 어노테이션을 왜 만드는 것일까 ?

용도에 따라 다 다르다.

  • 제약사항 등을 선안하기 위해 : @Deprecated, @Override, @NotNull
  • 용도를 나타내기 위해 : @Entity, @TestCase, @WebService
  • 행위를 나타내기 위해 : @Statefull, @Transcation
  • 처리를 나타내기 위해 : @Column, @XmlElement

위와 같은 어노테이션이 만들어지기 전까지 모든 자바 애플리케이션의 설정을 XML이나 properties라는 파일에 지정해 왔다. 그러면서 설정이 복잡해지고, 어떤 설정이 어디에 쓰이는지 이해하려면 많은 시간이 소요되었다.

그런데 어노테이션이 만들어 지면서, 각 설정이 필요한 위치에 관련 설정이 존재하면서 코드에 대한 가독성이 매우 좋아졌다.

하지만 아직도 XML과 같은 설정파일들을 필요한 부분이 존재하기 때문에 남아 있다.

롬복(lombok)

  • 롬복 홈페이지

https://projectlombok.org

  • 롬복의 예제
private boolean employed = true;

이 변수에 접근하기 위해서

private boolean employed = true;

public boolean isEmployed(){
    return employed;
}

public void setEmployed(final boolean employed){
    this.employed = employed;
}

위와 같이 선언해줘야했다. (소위 말해서 getter, setter) 하지만 롬복이 나오고 정말 편리해졌다.

@Getter @Setter private boolean employed = true;

간단한 선언으로 getter 메소드setter 메소드를 생성해 준다.

어노테이션이 변환된 모습은 컴파일 단계에서 생성되기 때문에 역컴파일을 하지 않는 이상 직접 확인할 수 없다.


참고문헌

  • 자바의 신
  • 자바의 정석

Annotation의 회고

Annotation은 어느정도는 알고 있었다.

롬복도 마찬가지고 개발할 때 정말 편리하다.

하지만 롬복에도 안에를 살펴보면 쓸 때 주의사항 같은 것들이 있는 것으로 알고 있다.

이제 어느정도 큰 틀은 알게되었으니 어노테이션의 내부를 조금씩 공부하면서 더 알아가 보자.

어노테이션 정말 편리한 친구다.. 앞으로 친하게 지내자 친구야 !!

giphy-2