티스토리 뷰

Java/개념정리

[Java] 애노테이션(annotation)

통통푸딩 2021. 10. 18. 22:11
반응형

애노테이션?

애노테이션(annotation)은 사전적 의미로는 주석을 의미한다.

그렇지만 우리가 원래 알던 일반적인 주석 //이나 /**/와는 다르다.

일반적인 주석과 다르게 애노테이션은 코드를 작성할 수 있다.

 

즉, 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된

형식으로 포함시킨 것이 애노테이션이다.

 

애노테이션은 주석이기 때문에 다이나믹하게 실행되는 코드는 들어가지 않는다.

즉, 런타임 중에 알아내야하는 값은 못들어간다.

컴파일러 수준에서 해석이 되거나, 완전히 정적이어야한다.

동적으로 런타임중에 바뀌어야하는 것들은 애노테이션에 사용할 수 없다.

@RestController
public HelloController{

    private static final String hello = "hello";

    @GetMapping(hello)
    public String hello(){
        return "hello";
    }    
}

위에서 hello 변수가 static final한 정적변수이기 때문에 @GetMapping 애노테이션에

사용하더라도 문제가 되지 않는다.

 

그런데, hello 변수가 private String hello = "hello"; 이런식으로 선언된 동적 변수라면

애노테이션에 사용하지 못한다. 컴파일 에러가 발생한다.



 

1. 애노테이션 정의하는 방법 📝

//@interface 애노테이션 이름
public @interface MyAnnotation {
  //타입 요소 이름();
}

@interface 는 애노테이션 타입을 선언하는 키워드다.

@ 기호를 붙이는것을 제외하면 인터페이스를 정의하는 방식과 동일하다.

 

 

애노테이션 필드

애노테이션에 필드같은 요소들을 정의할 수 있다.

 

- 요소의 타입은 기본형, String, enum, 애노테이션, Class만 가질 수 있다.

- ()안에 매개변수를 선언할 수 없다.

- 예외를 선언할 수 없다.

- 요소를 타입 매개변수로 정의할 수 없다.

- 배열을 선언할 수 있다. String[] arr();

- default 값을 지정할 수 있다. 기본값 요소는 애노테이션을 지정할 때 값을 지정하지 않으면 기본값이 사용된다. ( null을 제외한 모든 리터럴 가능)

@interface ExAnnotation {
    int number() default 100;  //int 타입(기본형)
    String value();  //String
    String[] arr();  //배열로 생성
    Month month();  //Enum 타입
    Class exClass();  //Class 타입
    Target tg();  //Target 애노테이션
}

애노테이션의 요소는 반환 값이 있고 매개변수는 없는 추상 메서드의 형태를 가진다.

 

 

이 애노테이션을 적용할 때는 이 요소들의 값을 빠짐없이 지정해주어야한다.

@ExAnnotation (
    value = "java",
    arr = {"hello", "world", "java"},
    month = Month.FEB,
    exClass = MyClass.class,
    tg = @Target(ElementType.ANNOTATION_TYPE))
public void new Class {
    //...
}




2. 자바의 표준 애노테이션 📝

자바에서 기본적으로 제공하는 애노테이션들 몇 개를 알아보자.

이것들의 일부는 메타 애노테이션(meta annotation)으로 애노테이션을

정의하는데 사용되는 애노테이션의 애노테이션을 말한다.

 

 

아래는 표준 애노테이션들의 설명이다.

1) @Override

메서드를 오버라이드를 할 때 사용된다.

컴파일러에게 현재 메서드는 슈퍼 클래스의 메서드를 오버라이딩하는 메서드라는 것을 알린다.

이 애노테이션은 생략이 가능하지만, 작성할 경우 메서드가 슈퍼클래스에 없다면 에러를 발생시키므로 오타와 같은 실수도 알 수 있으므로 작성하는 것이 좋다.

 

 

아래는 애노테이션이 정의된 코드이다.

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

 

 

2) @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가 있다.

읽어보면 이 클래스의 사용 대신에 Calendar 클래스 사용을 권장하고 있다.

아예 이 클래스를 삭제하면 작성되었던 기존의 프로그램의 동작에 문제가 생길 수 있다.

그래서 삭제는 하지 않고 호환성을 위한 애노테이션을 만든 것이다.

 

 

아래는 애노테이션이 정의된 코드다.

@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;
}

 

 

3) @SuppressWarnings

경고를 제거하는 애노테이션이다.

개발자가 의도를 가지고 설계를 했는데 컴파일이 이를 알아채지 못하고

컴파일 경고를 띄울 수 있기 때문에 이를 제거하는 목적으로 사용한다.

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

    String[] value();
}

 

 

4) @SafeVarargs

자바 7 이상에서 사용이 가능하다.

제네릭 같은 가변인자 매개변수 사용 시에 경고를 무시하도록 한다.

@SafeVarargs는 static이나 final이 붙은 메서드와 생성자에만 붙일 수 있다.

즉, 오버라이드될 수 있는 메서드에는 사용할 수 없다.

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

 

 

5) @Native

native 메서드에서 참조되는 상수 앞에 붙이는 애노테이션이다.

native 메서드는 JVM이 설치된 OS의 메서드를 말한다.

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}

 

 

6) @FunctionalInterface

자바 8 이상에서 사용가능하고 컴파일러에게 함수형 인터페이스라는 것을 알려주는 애노테이션이다.

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

 

 

즉, 이 인터페이스는 무조건 함수형으로 사용한다는 뜻인데, 함수형으로 작성할 때는 몇가지 규칙이 존재한다.

함수형 인터페이스는 단 하나의 추상 메서드만 존재해야 된다.

@FunctionalInterface
public interface Example {
    void method01();
    //void method02();  -> 작성하면 오류!
}



 

 

이 다음부터는 메타 애노테이션들의 설명이다.

7) @Target

애노테이션이 적용가능한 대상을 지정하는 데 사용된다.

애노테이션에 적용할 수 있는 대상을 @Target으로 지정한다.

애노테이션 정보는 다음과 같다.

@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();
}

 

 

이제 예시로 @Override 애노테이션을 보자.

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

코드에서 볼 수 있듯 @Override는 메서드에서 사용되어진다고 정해져있다.

다른 필드변수나 생성자, 파라미터와 같은 곳에서는 사용이 불가능한 것이다.

 

 

적용 대상은 여러가지가 있다.

여러 개의 값도 지정할 수 있는데, 이 때는 아래와 같이 배열처럼 괄호 {} 를 이용하면된다.

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})

 

 

 

8) @Retention

애노테이션이 유지되는 기간을 지정하는 데 사용된다.

이 기간을 지정하는 유지 정책(Retention Policy)의 종류는 세가지가 있다.

 

- SORUCE

소스파일에만 존재한다. 컴파일 시에만 사용하겠다는 의미이다.

컴파일 하고 나면 애노테이션은 없어진다.

클래스 파일(바이트코드)에는 없다.

컴파일러를 직접 작성할 것이 아니면, SOURCE 이상의 유지정책을 가질 필요가 없다.

 

- CLASS (기본값)

애노테이션에 대한 정보를 클래스 파일(바이트코드)에 남겨둔다.

바이트코드를 읽어들이는 방법을 바탕으로 애노테이션 정보를 읽어와 처리한다.

이 클래스 파일을 JVM이 실행할 때 클래스에 대한 정보를 클래스로더가 읽어서 메모리에 적재하게 된다.

이후 사용 시점에 메모리에서 읽어올 때 애노테이션 정보를 제외하고 읽어온다.

그래서 실행시에는 사용이 불가능하다.

 

- RUNTIME

CLASS와 동일하지만 사용 시점에 메모리에 적재된 클래스 정보를 읽어올 때 애노테이션 정보를 그대로 포함한다.

그래서 실행시에 사용이 가능하다.

실행 시에 리플렉션(Reflection)을 통해서 클래스 파일에 저장된 애노테이션의 정보를 읽어서 처리한다.

자바 리플렉션(Java Reflection)
구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API다. 런타임 시에 클래스 이름만 알고있다면 클래스에 대한 정보를 가져오고 활용할 수 있게 해준다.

- getAnnotations() : 상속받은 애노테이션까지 조회
- getDeclareAnnotations() : 자기자신에만 붙어있는 애노테이션 조회

 

 

9) @Documented

애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.

JavaDoc : 자바 코드에서 API 문서를 HTML 형식으로 생성해주는 도구

 

일단 애노테이션 정보는 다음과 같다.

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

 

이 애노테이션은 정확히는 내가 직접 javadoc을 만들 수 있다는 뜻이다.

(참고 : https://b-programmer.tistory.com/264)

아래와 같은 방법으로 만들 수 있다. (인텔리제이)

위와 같이 입력 후에 output directory에 경로를 입력해준다.

 

 

 

@Documented 를 붙였을 때와 안붙였을 때를 비교해보자.

public class Korea implements Great{

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

 

 

안붙였을 때

 

 

붙였을 때

 

 

10) Inherited

상속받은 클래스에도 애노테이션이 유지된다는 것을 의미한다.

자식 클래스에도 이 애노테이션이 붙은것과 같이 인식된다.

 

일단 애노테이션 정보는 다음과 같다.

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

 

 

아래 예제를 살펴보자.

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface ExAnnotation {}

@ExAnnotation
class Parent {}

class Child extends Parent {}

public class InheritedExample {
  public static void main(String[] args) {
    ExAnnotation annotation = Child.class.getAnnotation(ExAnnotation.class);
    System.out.println(annotation);
  }
}

일단 , @Inherited 애노테이션을 사용하여 ExAnnotation 애노테이션을 만든다.

이후에 Parent 클래스를 만들어 @ExAnnotation을 사용한다.

그 Parent 클래스를 상속받는 Child 클래스를 생성한다.

이 때, Child 클래스의 인스턴스 getAnnotation() 메서드를 호출하여 ExAnnotation이 있으면 반환을 받아 출력하도록 한다.

즉, 리플렉션을 이용하여 Child 클래스의 애노테이션을 확인해보자.

 

결과는 다음과 같다. ExAnnotation이 출력되었다.

즉, Child 클래스에도 ExAnnotation이 적용된 것을 알 수 있다.

 

11) @Repeatable

이 애노테이션이 붙은 애노테이션은 특정 클래스에 여러번 붙일 수 있다.

@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();
}

이 애노테이션은 하나의 애노테이션의 배열 요소에 여러개의 요소값을 넣어주는 것과 다르다.

일반적인 애노테이션과 다르게 같은 이름의 애노테이션이 여러개가 하나의 대상에 적용될 수 있기 때문에

이 애노테이션들을 하나로 묶어서 다룰 수 있는 애노테이션도 추가로 정의해야 한다.

@Repeatable(Ex02.class)
@interface Ex01 {
    String value();
}

@interface Ex02 {
    Ex01[] value();
}

@Ex01("hello")
@Ex01("hi")
class MyClass {
    //..
}

위 코드의 Ex01 애노테이션을 담을 컨테이너 애노테이션 Ex02를 만들어야한다.

그리고 Ex01 애노테이션 배열타입의 요소를 선언해줘야한다.

컨테이너 애노테이션 안에 요소의 이름이 반드시 value여야한다.



 


3. 애노테이션 프로세서 📝

애노테이션을 프로세싱하는 기술이다.

소스코드 레벨에서 소스코드에 붙어있는 애노테이션 정보를 읽어와

컴파일러가 컴파일 중에 새로운 소스코드를 생성하거나 기존의 코드 변경을 가능하게한다. ( 코드 변경은 비추)

클래스 즉, 바이트 코드도 생성할 수 있고 소스코드와 별개의 리소스도 생성할 수 있다.

 

대표적인 기술로는 롬복(Lombok)이 있다.

롬복에서 @Getter 와 같은 애노테이션을 사용하면 메서드가 자동으로 추가된다.

이것은 컴파일 시에 생성된다.

 

바이트코드에 대한 조작은 런타임에 발생되는 조작이기 때문에 런타임에 대한 비용이 발생한다.

그런데, 애노테이션 프로세서는 애플리케이션을 구동하는 런타임 시점이 아닌 컴파일 시점에 조작하여 사용하기 때문에 런타임에 대한 비용이 없다.

반응형

'Java > 개념정리' 카테고리의 다른 글

[Java] 상속, 다형성, Override 원리 (+메모리 구조)  (0) 2023.12.16
[Java] enum(열거형)  (0) 2021.10.14
[Java] 멀티쓰레드 프로그래밍  (0) 2021.10.14
[Java] 예외 처리  (1) 2021.10.06
[Java] 인터페이스(interface)  (0) 2021.10.06
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday