-
[스터디 10주차] 애노테이션 (annotation)프로그래밍 언어/JAVA 2021. 7. 5. 15:02더보기
목표: 자바의 열거형에 대해 학습자바의 애노테이션에 대해 학습
1. 애노테이션 (annotation)
애노테이션(annotation)이란 무엇일까?
애노테이션은 JAVA5부터 추가된 요소로 사전적 의미로는 주석을 의미한다.
하지만 자바에서는 단순 주석이 아닌 클래스에 특수한 의미를 부여하거나 기능을 주입하기 위한 메타데이터라고 볼 수 있다. 이런 어노테이션은 인터페이스 일종으로 @를 사용하여 선언한다.
애노테이션은 JDK에서 기본적으로 제공하는 것과 다른 프로그램에서 제공하는 것들이 있는데, JDK에서 제공하는 표준 애너테이션은 주로 컴파일러를 위한 것으로 컴파일러에게 유용한 정보를 제공한다.
더보기JDK에서 제공하는 애너테이션은 'java.lang.annotation' 패키지에 포함되어 있다.
애노테이션의 용도는 다음과 같다.
- 컴파일러를 위한 정보
컴파일러에게 코드 문법 에러를 체크하거나 에러 메세지를 억제하도록 정보를 제공
- 컴파일 시간 및 배포 시간 처리
소프트웨어 개발 툴이 빌드나 배치 시 코드, XML 파일 등을 생성할 수 있도록 정보를 제공
- 런타임 처리
런타임 시 특정 기능을 실행하도록 정보를 제공
그러면 이러한 애노테이션은 왜 개발된 것일까?
애노테이션이 생기기 전에는 자바 웹 애플리케이션에서 구성과 설정값을 XML 설정 파일을 통해 명시하고 관리했다.
이렇게 따로 파일을 만들어 관리한 이유는 변경될 수 있는 데이터들을 코드와 분리함으로써 컴파일 없이 변경사항을 적용하기 위해서였다.
하지만 이러한 방법은 프로그램 작성 때마다 많은 설정을 외부 파일에 작성해야 하는 불편함이 발생한다.
웹 애플리케이션이 커짐에 따라 이 불편함은 더 커졌고 이를 해결하기 위한 방법으로 애노테이션이 생기게 된 것이다.
이러한 애노테이션을 사용하면 데이터에 대한 유효성 검사 등 직접 클래스에 명시해 줄 수 있게되어 수정이 필요할때 쉽게 파악할 수 있게 되었고 어토테이션의 재사용도 가능해졌다.
또한, 어노테이션과 리플랙션을 같이 이용하면 원하는 클래스를 주입하는 것도 가능해진다.
이제 애노테이션을 정의하는 법을 알아보자.
public @interface myAnnotation { }
애노테이션을 정의하려면 interface라는 키워드를 쓰고 그 앞에 @를 붙여주면 애노테이션이 정의된다.
@ interface도 가능하지만 표준 스타일을 맞추기 위해서 공백없이 붙여주는게 좋은 방법이다.
이렇게 커스텀한 애노테이션을 정의하면 중괄호 안에 원하는 메소드를 작성해주면 되는데 작성한 메소드는 추상 메소드로 선언된다.
추상 메소드로 선언되지만 구현할 필요는 없다. 대신 사용하는 쪽에서 애노테이션에 있는 요소들의 값들을 다 넣어줘야한다.
즉, 아래와 같이 선언된 애노테이션을 사용해야한다는 것이다.
@interface houseAnnotation { int room(); String type(); String[] family(); } @BhouseAnnotation(room = 3, type = "apt", family = {"father", "mother", "brother", "me"}) public class AnnotationTest { }
애노테이션에 정의되는 요소는 아래와 같은 규칙과 특징을 따른다.
- 요소의 타입은 기본형, String, enum, 애노테이션, Class만 가질 수 있다.
- ()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
- 배열을 선언할 수 있다. ex. String[] arr();
- default값을 지정할 수 있다. ex. int number() default 5;
- 애노테이션 요소의 타입 선언은 제네릭일 수 없다.
- extends 절을 가질 수 없다. (애노테이션 타입은 암묵적으로 java.lang.annotation.Annotation을 상속하기때문이다.)
- 메서드는 throws절을 가질 수 없다.
애노테이션은 메타데이터의 저장을 위해 클래스처럼 멤버를 가질 수 있는데, 이때 멤버의 갯수에 따라 Marker Annotation, Single Value Annotation, Full Annotation으로 분류된다.
1) Marker Annotation
멤버 변수가 없으며, 단순히 표식으로서 사용되는 애노테이션이다. 컴파일러에게 의미를 전달한다.
2) Sing Value Annotation
멤버로 단일변수만을 갖는 애노테이션이다. 단일변수 밖에 없기 때문에 값만 명시하여 데이터를 전달할 수 있다.
3) Full Annotations
멤버를 둘 이상의 변수로 갖는 애노테이션으로, 데이터를 (값=쌍)의 형태로 전달된다.
내가 정의하는 것이 아닌 자바에서 기본으로 제공하는 애노테이션은 무엇이 있을까?
자바에서 기본으로 제공하는 애노테이션은 Built-in Annotation이라고 하며 아래의 표와 같은 것들이 존재한다.
종류 설명 @Override 선언한 메서드가 오버라이드 되었다는 것을 나타냄.
* 상위(부모)클래스(또는 인터페이스)에서 해당 메서드를 찾지 못하면 컴파일 에러 발생.@Deprecated 앞으로 사용하지 않을 것을 권장하는 필드나 메소드에 붙이고, 사용하면 위험하거나 해당 코드보다 개선된 코드가 존재하기 때문에 개발자에게 사용하지 말아야 하는 것을 알리기 위해 사용
* 해당 메서드를 사용할 경우 컴파일 경고를 발생.@SuppressWarnings 이미 인지하고 있는 컴파일러의 경고메세지가 나타나지 않게 제거해줄때 사용. @SafeVarargs 생성자나 메소드의 가변인자 파라미터가 안전하게 사용된다는 것을 나타내기 위해 사용.
* 제네릭 같은 가변인자 매개변수를 사용할 때 경고를 무시.(Java 7 이상)@FunctionalInterface 람다 함수 등을 위한 인터페이스 지정.(Java 8 이상)
* 메소드가 없거나 두개이상 되면 컴파일 오류 발생2. 메타 애노테이션 (meta annotation)
메타 애노테이션은 '애노테이션을 위한 애노테이션' 즉, 우리가 커스텀한 애노테이션을 정의할 때 커스텀 애노테이션의 적용대상(target)이나 유지기간(retention)등을 지정하는데 사용된다.
메타 애노테이션은 java.lang.annotation 패키지에 포함되어 있고 총 5개가 존재한다.
지금부터 하나씩 알아보자.
1) @Target
@Target은 애노테이션이 적용가능한 대상(동작 대상)을 지정한다.
만약 다른 타입이 온다면 컴파일 에러를 띄운다.
아래와 같은 ElmentType이라는 enum을 통해 지정한다. ( @Target(ElemntType.~)와 같이 사용 )
public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, MODULE, @jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,essentialAPI=true) RECORD_COMPONENT; }
그러면 각각은 무엇을 의미할까?
- TYPE : class, interface, annotation, enum 에만 적용 가능
- FIELD : 필드, enum 상수에만 적용 가능
- METHOD : 메소드에만 적용 가능
- PARAMETER : 파라미터에만 적용 가능
- CONSTRUCTOR : 생성자에만 적용 가능
- LOCAL_VARIABLE : 지역변수에만 적용 가능
- ANNOTATION_TYPE : 애노테이션에만 적용 가능
- PACKAGE : 패키지에만 적용 가능
- TYPE_PARAMETER : 자바 8부터 추가되었으며, 타입 파라미터(T, E와 같은)에만 적용 가능
- TYPE_USE : TYPE_PARAMETER와 동일하게 자바 8부터 추가되었으며, JLS의 15가지 타입과 타입 파라미터에 적용 가능
- RECORD_COMPONENT : Record 컴포넌트에만 적용 가능
@Target를 사용하여 커스텀 애노테이션을 선언한 예시 코드를 보며 이해해보자.
import static java.lang.annotation.ElementType.*; @Target(ElementType.FIELD) public @interface MyAnnotation { }
이렇게 MyAnnotaion을 선언하면 필드에만 MyAnnotaion애노테이션이 적용 가능하다는 것이다.
만약에 필드가 아닌 다른 곳에 MyAnnotation을 사용한다면 컴파일에러가 발생한다.
더보기java.lang.annotation.ElementType 이라는 열거형에 정의되어 있다.
static import문을 사용하면 ElementType.TYPE 이 아니라 TYPE 과 같이 간략히 사용할 수 있다.
위와 같이 하나에만 적용하는 것이 하닌 여러 개에 적용하고 싶다면 아래와 같이 배열처럼 중괄호{} 를 이용하여 지정할 수 있다.
import static java.lang.annotation.ElementType.*; @Target({TYPE, FIELD, METHOD}) public @interface MyAnnotation { }
2) @Retention
@Retention은 애노테이션이 어느 시점까지 유지되는지를 나타낼 수 있다.
RetentionPolicy이라는 enum을 통해 지정할 수 있는 3가지 유지 정책(retention policy)이 있다.
@Retention 애노테이션을 생략하여 커스텀 애노테이션을 선언한다면 자동으로 RetentionPolicy.CLASS가 적용된다.
3가지 유지 정책(retention policy)은 다음과 같다.
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
그러면 각각은 무엇을 의미할까?
- RetentionPolicy.Source : 컴파일 전까지만 유효하여 컴파일 시점에 컴파일러에 의해 제거됨
- RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효
- RetentionPolicy.RUNTIME : 컴파일 이후에도 JVM에 의해 계속 참조 가능
실제로 @Retention을 쓴 코드를 보면 아래와 같다.
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { }
3) @Documented
@Documented는 해당 어노테이션을 javadoc으로 작성한 문서에 추가시킬지 여부를 결정하는 것이다.
정확히 말하면 공식 자바docs에 올라간다는 말이 아니라 내가 직접 만든 javadoc에 해당 애노테이션이 추가된다는 뜻이다.
표준 애노테이션(Built-in Annotation) 중 @Override와 @SuppressWarnings를 제외하고 모두 Documented 메타 애노테이션이 붙어 있다.
그러면 실습 코드를 진행해보면서 @Documented를 쓴 애노테이션과 @Documented를 쓰지 않은 애노테이션의 javadoc에서의 차이점을 알아보자.
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; //Documented를 포함한 사용자 정의 애노테이션 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CustomAnnotation { String testDocument(); }
import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.ElementType; //@Documented를 포함하지 않는 사용자 정의 애노테이션 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CustomAnnotationNoDocument { String testNoDocument(); }
public class CustomAnnotationMain { public static void main(String args[]) { new CustomAnnotationMain().testDocument(); new CustomAnnotationMain().testNoDocument(); } @CustomAnnotation(testDocument = "document test") public void testDocument() {}; @CustomAnnotationNoDocument(testNoDocument = "document test") public void testNoDocument() {}; }
위와 같이 코드를 짜준 후, javadoc을 만들어 확인해보자.
javadoc을 만드는 방법은 아래의 링크에서 확인하면 된다.
https://straw961030.tistory.com/149
나는 IDE로 이클립스를 쓰고 있기 때문에 인텔리제이를 쓰고 있는 사람이라면 아래의 글을 참고하면 좋을 것같다.
(https://www.notion.so/37d183f38389426d9700453f00253532)
@Documented를 단 애노테이션을 사용한 것은 해당 애노테이션까지 표기됨을 확인할 수 있습니다.
반대로 @Documented 사용하지 않은 애노테이션은 해당 애노테이션이 표기 안되는 것을 볼 수 있다.
4) @Inherited
@Inherited은 부모클래스에 선언된 애노테이션이 자식클래스에 상속되는 기능을 한다.
즉, @Inherited가 붙은 애노테이션을 부모클래스에 붙이면 자손클래스도 이 애노테이션이 붙은 것과 같이 인식된다.
실습코드를 보며 어떻게 동작하는지 확인해보자.
// @Inherited가 포함된 커스텀 애노테이션 정의 import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD}) @Inherited public @interface SonAnnotation { String value(); }
// @SonAnnotation을 사용한 부모 클래스 @SonAnnotation("example") public class Parent { }
// @SonAnnotation를 사용한 부모클래스를 상속받은 자식클래스 // 자식클래스는 @SonAnnotation를 사용하지 않음. public class Child extends Parent{ }
위와 같이 커스텀 애노테이션, 부모클래스, 자식클래스를 선언하고 리플랙션을 이용해 Child클래스(자식클래스)의 애노테이션을 확인해보면 아래와 같다.
Child클래스(자식클래스)에 @SonAnnotation을 사용하지 않았지만 Child클래스(자식클래스)에 @SonAnnotaion을 사용한 것으로 결과가 나온다.
이것이 @inherited를 사용하여 커스텀 애노테이션을 정의한 것의 기능이다.
5) @Repeatable
@Repeatable은 여러 개의 동일한 애노테이션을 선언할 수 있게 해준다.
원래는 동일한 애노테이션을 여러 개 선언할 경우 컴파일 에러가 발생한다.
실습 코드를 보며 이해해보자.
먼저, @Repeatable을 사용하지 않은 커스텀 애노테이션을 정의한 경우이다.
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.CLASS) public @interface NoRepeatableAnnotation { String value(); }
이 애노테이션을 다른 class에서 중복해서 사용해보면 아래와 같이 컴파일 애러가 발생한다.
그러면 @Repeatable을 사용해서 커스텀 애노테이션을 정의해보자.
@Repeatable을 사용해서 커스텀 애노테이션을 정의할 때 중요한 것은 @Repeatable인 애노테이션을 하나로 묶을 컨테이너 애노테이션을 정의해야 한다는 것이다.
일반적인 애노테이션과 달리 같은 이름의 애노테이션이 여러 개가 하나의 대상에 적용될 수 있기 때문에, 이 애노테이션들을 하나로 묶어서 다룰 수 있는 애노테이션이 추가로 필요하기 때문이다.
import java.lang.annotation.Repeatable; @Repeatable(ToDos.class) @interface Todo { String value(); } //@Todo 애노테이션의 @Repeatable 애노테이션을 위한 ToDos 애노테이션 @interface ToDos { Todo[] value(); }
위와 같이 Todo애노테이션을 담을 컨테이너 애노테이션 ToDos를 만들어야 한다는 것이다.
또한, Todo애노테이션 배열타입의 요소를 선언해줘야 하고 컨테이너 애노테이션안에 요소의 이름이 반드시 value 이어야 한다.
이렇게 정의한 Todo애노테이션을 중복해서 사용해보면 컴파일 에러가 발생하지 않는 것을 확인할 수 있다.
3. 애노테이션 프로세서
애노테이션 프로세서는 자바 컴파일러의 컴파일 단계에서 유저가 정의한 애노테이션의 소스코드를 분석하고 처리하기 위해 사용하는 훅이다. 컴파일 에러나 컴파일경고를 만들어 내거나 소스코드(.java)와 바이트코드(.class)를 내보내기도 한다.
더보기'훅'이란 후킹(Hooking)이라고 불리며 이미 작성되어 있는 코드의 특정 지점을 가로채서 동작 방식에 변화를 주는 일체의 기술이라고 의미른 지닌 용어이다.
애노테이션 프로세서 동작구조는 다음과 같다.
1. 어노테이션 클래스를 생성한다.
2. 어노테이션 파서 클래스를 생성한다.
3. 어노테이션을 사용한다.
4. 컴파일하면, 어노테이션 파서가 어노테이션을 처리한다.
5. 자동 생성된 클래스가 빌드 폴더에 추가된다.
<참고 자료>
https://blog.naver.com/swoh1227/222229853664
https://www.notion.so/37d183f38389426d9700453f00253532
https://parkadd.tistory.com/54
https://gowoonsori.site/java/annotation/
https://b-programmer.tistory.com/264
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
[이클립스] javadoc 만들기 (0) 2021.07.11 [스터디 11주차] Enum (0) 2021.07.10 [스터디 9주차] JAVA 예외처리 (0) 2021.06.30 [스터디 8주차] JAVA 인터페이스 (0) 2021.06.25 [스터디 7주차] 패키지 (0) 2021.06.20