어노테이션

반응형
반응형

어노테이션 


  어노테이션은 주석이라는 뜻을 가지고 있다.
  기본적으로 우리가 아는 주석은 // 또는/* */ 이렇게 생겼는데,
  어노테이션과 일반적인 주석은 뭐가 다른걸까? 

 - 어노테이션의 역할도 주석과 크게 다르지 않는다.
 - 일반주석과 큰 차이점은 코드를 작성할 수 있다는 것이 다르다.
 - 코드를 작성할 수 있다는 뜻은 어노테이션으로 뭔가를 할 수 있다는 뜻이 된다.
 - 어노테이션도 enum과 마찬가지로 1.5에 등장했다고 한다.

어노테이션을 정의하는 방법

public @interface Make {
}

이렇게 정의하면 된다.
enum은 java.lang.Enum에 상속되어있다고 학습하였다. 
그러면 어노테이션의 조상님은 과연 뭘까? 바이트 코드를 통해 확인해보자.

public abstract @interface study/whiteship/homework12/Make 
                             implements java/lang/annotation/Annotation {
    // compiled from: Make.java
}

확인 결과 java.lang.annotation.Annotation에 라는 것을 알 수 있다.
어노테이션은 인터페이스로 구성되었기 때문에 굳이 구현하려면
implements를 하던가 아니면 익명클래스로 만들어야 된다.  

Annotation annotation = new Annotation() {
     @Override
     public Class<? extends Annotation> annotationType() {
        return null;
};

어노테션은 리플렉션 기술을 이용해서 사용된다는 것을 짐작할 수 있다.

어노테이션 요소의 규칙

 - 요소의 타입은 기본형, String, enum, 어노테이션, Class만 허용된다.
 - ()안에 매개변수는 선언할 수 없다.
 - 예외를 선언할 수는 없다.
 - 요소를 타입 매개변수로 정의 할 수 없다.

표준 어노테이션

자바에서 제공되는 어노테이션은 크게 2가지로 구성되어져 있다.
하나는 자바코드를 작성할 때, 사용되는 어노테이션이구,
다른 하나는 어노테이션을 정의하기 위해 필요한 것들이다,

@Override

오버라이드를 할 때 사용되며, 메소드가 오버라이드 되었는지 알려주는 역할을 한다.
이건 생략이 가능하다. (이 어노테이션이 존재하는 이유는 미연의 실수를 방지하기 위함이니 생략이 가능해도 안 하는것이 좋다..)
왜 생략이 가능한지 이따 살펴보자. 그전에 @Override의 어노테이션 정보를 알아보자.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

이렇게 생겼다고 한다. 

@Deprecated

앞으로 사용되지 않을 대상에 붙여진다고 한다.
이 어노테이션이 존재하는 이유는 버전이 올라가면서, 더 이상 사용이 되지 않는 것들이 존재한다.
예를들어, Date클래스가 있는데,

/**
* Allocates a {@code Date} object and initializes it so that
* it represents midnight, local time, at the beginning of the day
* specified by the {@code year}, {@code month}, and
* {@code date} arguments.
*
* @param   year    the year minus 1900.
* @param   month   the month between 0-11.
* @param   date    the day of the month between 1-31.
* @see     java.util.Calendar
* @deprecated As of JDK version 1.1,
@Deprecated
public Date(int year, int month, int date) {
    this(year, month, date, 0, 0, 0);
}

Date클래스를 살펴보면 생성자가 deprecated가 존재한다는 것을 확인 할 수 있다.
읽어보면 이 클래스는 더 이상 사용하지 말고, Calender클래스를 사용을 권장하고 있다.

그렇다면 이 클래스를 자바자체에서 삭제하면 되지 않을까?
그것도 하나의 방법일 수 도 있겠지만,
그렇게 될수 기존에 Date클래스가 작성이 되있다면 잘 동작했던 프로그램이 동작하지 않는다.
그러면 개발자가 일일이 그것을 찾아다니면서 수정해된다는 뜻이다.
결국 호환성 때문에 존재하는 어노테이션이라는 것을 알 수 있다.  

어노테이션 정보를 알아보자.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
    /**
     * Returns the version in which the annotated element became deprecated.
     * The version string is in the same format and namespace as the value of
     * the {@code @since} javadoc tag. The default value is the empty
     * string.
     *
     * @return the version string
     * @since 9
     */
    String since() default "";

    /**
     * Indicates whether the annotated element is subject to removal in a
     * future version. The default value is {@code false}.
     *
     * @return whether the element is subject to removal
     * @since 9
     */
    boolean forRemoval() default false;
}

생각보다 다양한 정보가 존재하는 것을 알 수 있다.
- 어노테이션은 인터페이스로 되어있기 때문에 구현이 되지 않는것을 알 수 있다.
- 그렇지만 default,static은 작성되지 않는다.

@FunctionalInterface

이 어노테이션은 이 인터페이스는 무조건 함수형으로 사용한다는 뜻이다.
함수형으로 작성할때는 몇 가지 규칙이 존재하는데,
그 중에, 인터페이스는 단 하나의 메소드만 존재해야 된다,

그렇지 않으면 자바에서는 함수형으로 만들 수 없기 때문이다.(나중에 람다에서 자세히 학습하자.)

이것도 어노테이션 정보를 살펴보자.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

@SupperessWarnings

컴파일러가 보여주는 경고 메시지가 보이지 않게 억제 해준다고 한다.

이상하게도 인텔이제이에서는 경고 메시지가 나오지 않는것 같아 찾아보았다.

 

Disabling and enabling inspections - Help | IntelliJ IDEA

 

www.jetbrains.com

확인결과 노란 하이라이트로 등장된다는것을 알 수 있었다.
(이클립스에서는 저게 노란 밑줄로 나와서 누가봐도 경고였는데, 이렇게 보여주니 안 나온다고 착각한것 같다.)

억제 코드 억제 내용
deprecation @Deprecated가 붙은 대상에 발생하는 경고
unchecked 제네릭 타입을 지정하지 않을 때 발생하는 경고
rawtypes 제네릭을 사용하지 않아서 발생하는 경고
varargs 가변인자의 타입이 제네릭 타입일 때 발생하는 경고 

이것도 어노테이션 정보를 확인해보자.

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    
    String[] value();
}

이 밖에도
제네릭 타입의 가변인자에 사용되는 @safeVarargs
native메서드에서 참조되는 상수 앞에 붙이는 @Native
가 존재한다.

이 둘도 어노테이션 정보를 확인 해보자.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}

이제 어노테이션을 정의하기 위한 어노테이션들을 살펴보자.

@Target

어노테이션이 적용 가능한 대상을 지정하는데 사용된다.
위에서 Override를 다시 확인해보자.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

그렇다는건 메소드에서 사용되어진다고 한다. 즉, 다른 필드변수라던지, 생성자, 파라미터 등에서는 사용이 불가능하다는 뜻이다.

메소드말고, 타입, 필드, 어노테이션 타입, 생성자, 지역 변수, 모듈, 패키지, 파라미터, 타입 파라미터, 타입 유즈에 사용할 수 있다고 한다.

예제에서는 한곳만 작성했지만, 배열을 이용하면 여러개도 추가가 가능하다.

이것도 어노테이션 정보를 보자

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

이것은 어노테이션 타입에만 붙일 수 있다고 한다.

@Retention

어노테이션이 유지되는 범위를 지정하는데 사용된다.
이번에는 무엇으로 확인해볼까?

이번엔 funtionalInterface로 확인해보자.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

확인결과 런타임까지 유지가 된다고 한다.

종류는 다음과 같다.

source로 사용되면 일반 주석처럼 사용된다는 뜻이다.

소스단에서만 사용하고 컴파일될때는 이 어노테이션이 필요 없다는 뜻이 된다.
대표적으로 override를 들 수 있다.

public class Korea implements Great{

    @Override
    public String country() {
        return "한국";
    }
}

 분명히 override로 오버라이드를 시도했지만, 이것을 컴파일 돌린 결과는

override가 사라졌다.

class

컴파일될때까지 어노테이션을 유지한다는 것을 의미한다.
아쉽게도 자바에서 제공한 어노테이션들은 class가 존재하지 않는다. 
이것이 retation의 기본전략이라고 한다.
그래서 직접 만들어보자.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Make {}

대충 Make라는 어노테이션을 만들었다.

 

public class Korea implements Great{

    @Override
    @Make
    public String country() {
        return "한국";
    }
}

이런식으로 재활용을 했다.
컴파일을 돌려보자.

소스와 달리 컴파일시는 유지되는 것을 알 수 있다.
그러면 런타임시에는 유지될까?
이것의 바이트 코드를 확인해보면 대략적으로 짐작이 가능하다.

  // access flags 0x1
  public country()Ljava/lang/String;
  @Lstudy/whiteship/homework12/Make;() // invisible
   L0
    LINENUMBER 8 L0
    LDC "\ud55c\uad6d"
    ARETURN
   L1
    LOCALVARIABLE this Lstudy/whiteship/homework12/Korea; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}

보면 invisible이라고 이 어노테이션은 보이지 않는다고 한다.
그러면 

정책을 runtime으로 변경시키면 어떻게 나올까?

  // access flags 0x1
  public country()Ljava/lang/String;
  @Lstudy/whiteship/homework12/Make;()
   L0
    LINENUMBER 8 L0
    LDC "\ud55c\uad6d"
    ARETURN
   L1
    LOCALVARIABLE this Lstudy/whiteship/homework12/Korea; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}

놀랍게도 class에서 확인한 invisible이라는 주석은 보이지 않는다는 것을 확인 할 수 있다.

이제 Retention의 어노테이션 정보를 확인해보자.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

@Documented

어노테이션 정보가 javadoc으로 작성된 문서에 포함된다고 한다.
이것이 무슨말일까? 내 코드가 자바docs에 올라간다는 말일까?

정확히 말하면 자바docs에 올라간다는 말이 아니라,
내가 직접 자바doc을 만들 수 있다는 뜻이다.

이런식으로 만들 수 있는데,

1

Local 지역입력 ko_KR

other command line arguments : 한글깨짐 방지
-encoding UTF-8 -charset UTF-8 -docencoding UTF-8

적절하게 내용을 채운뒤 output directory에 경로를 입력해주면 끝이다.
그러면 @Documented를 붙인거와 안 붙인것을 비교해보자.

0.코드

public class Korea implements Great{

    @Override
    @Make
    public String country() {
        return "한국";
    }
}

1. 없는거

2. 있는거

자바doc을 만들 수 있다니 몰랐다.;;
이것때문에 이것을 왜 사용하는지 이해를 할 수 없었는데ㅎㅎ;

이것도 어노테이션 정보를 보자.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Inherited

상속받은 클래스에도 어노테이션이 유지된다는 뜻이다.

코드를 이렇게 수정하구,

@Make
public class Korea implements Great{
    @Override
    public String country() {
        return "한국";
    }
}

경기도 코드를 추가했다.

public class Gyeonggi extends Korea{

    @Override
    public String country() {
        return super.country();
    }
}

바이트코드, 컴파일코드 전부 봤지만, Inherited가 사용되었다는 증거를 찾지 못했다.
그래서 리플랙션을 이용해서 어떤 어노테이이 존재하는지 확인했다.
대충 코드를 작성하구,

    public static void main(String[] args) {
        Gyeonggi gyeonggi = new Gyeonggi();
        Annotation[] annotations = gyeonggi.getClass().getAnnotations();

        Arrays.stream(annotations)
                .forEach(System.out::println);
    }

실행했더니 놀라운 결과를 알 수있었다.

나는 분명히 경기에는 make라는 어노테이션을 넣지 않았는데,
리플렉션 결과는 make어노테이션이 존재한다는 것을 알 수 있었다.

어노테이션 정보.

@Documented
@Repeatable(value = Colors.class)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

이 밖에도 어노테이션을 여러번 반복할 수 있는 Repeatable가 존재한다.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

매타 어노테이션

사실 위에서 살펴본 어노테이션들은 메타 어노테이션이라고 한다. 위에서 찬찬히 살펴보면 한 가지 공통점이 존재한다.
@Target이 전부 annotation-type으로 되어있다는 점이다.
결국, 타겟이 어노테이션 타입으로 지정 되어있으며 매타 어노테이션이라고 생각해도 될듯 싶다.

그렇다는 이야기는 커스텀 어노테이션도 매타 어노테이션이 될 수 있다는 뜻이 된다.

어노테이션 프로세서

어노테이션을 프로세싱하는 기술이라고 한다.
어노테이션 프로세서의 대표적인 기술로는 롬복이 있다.(롬복은 해킹을 통해 만들어졌다고,,,,,.)

혹시 롬복이라는 라이브러리를 알고 있는가?
롬복은 다음처럼 동작한다.

@Data
public class Test {
    private int num;
}

이것을 돌려보자.

나는 분명히 set을 추가한적이 없지만,
이상하게도 추가되어있다. 이것을 돌려보면 더 확실히 알 수 있는데...

public class Test {
    private int num;

    public Test() {
    }

    public int getNum() {
        return this.num;
    }

    public void setNum(final int num) {
        this.num = num;
    }

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Test)) {
            return false;
        } else {
            Test other = (Test)o;
            if (!other.canEqual(this)) {
                return false;
            } else {
                return this.getNum() == other.getNum();
            }
        }
    }

    protected boolean canEqual(final Object other) {
        return other instanceof Test;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        int result = result * 59 + this.getNum();
        return result;
    }

    public String toString() {
        return "Test(num=" + this.getNum() + ")";
    }
}

클래스 파일을 열어보니 내가 만들지도 않은 메소드가 추가된것을 확인 할 수 있었다.
이것은 런타임시 생성된것이 아닌,
컴파일 할때 생성 되었다.

위 를 설명하면 @Data라는 어노테이션이 붙으면 자바는 이 어노테이션이 붙은 곳을 찾는다.
마치 모자에서 토끼를 꺼내듯이신기하게도 위 코드를 만들어 줬다.

비슷한 예로 스프링의 IOC도 마찬가지다.

@Compoment 이 어노테이션이 붙으면 스프링은 이 어노테이션이 붙은 곳을 빈으로 만들어준다.
여기에서는 빈에 대해 설명하는것이 아니기 때문에 자바객체를 만들어준다고 생각하면 될것 같다.

어노테이션은 단독으로 사용되면 단순한 주석에 불과하지만, 어노테이션 프로세서라는 기술때문에
어노테이션을 다양하게 사용하게 사용할 수 있는 것이 아닐까?

반응형

'프로그래밍 언어 > 자바' 카테고리의 다른 글

멀티쓰레드 프로그래밍  (0) 2021.02.13
I/O  (0) 2021.02.11
ENUM  (0) 2021.01.24
예외 처리  (0) 2021.01.14
프록시  (0) 2021.01.04

댓글

Designed by JB FACTORY