https://github.com/whiteship/live-study
백기선님 자바 기초 스터디 12주차
목표
자바의 애노테이션에 대해 학습하세요.
학습할 것 (필수)
- 애노테이션 정의하는 방법
- @retention
- @target
- @documented
- 애노테이션 프로세서
애노테이션 정의하는 방법
애노테이션
자바를 개발한 사람들은 소스코드의 주석으로부터 HTML문서를 생성해내는 프로그램(javadoc.exe)을 만들어서 사용했다. 다음은 모든 애노테이션의 조상인 Annotation인터페이스의 소스코드의 일부이다.
/**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*
* More information about annotation types can be found in section 9.6 of
* <cite>The Java™ Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
'/**'로 시작하는 주석 안에 소스코드에 대한 설명들이 있고, 그 안에 '@'이 붙은 태그들이 있다. 미리 정의된 태그들을 이용해서 주석 안에 정보를 저장하고, javadoc.exe라는 프로그램이 이 정보를 읽어서 문서를 작성하는데 사용한다.
이 기능을 응용하여, 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애노테이션이다. 자바에서 애노테이션은 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다.
새로운 애노테이션 정의
새로운 애노테이션을 정의하는 방법은 아래와 같다. '@' 기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.
@interface 애노테이션이름 {
타입 요소이름(); // 애노테이션의 요소롤 선언한다.
...
}
애노테이션의 요소
애노테이션 내에 선언된 메서드를 '애노테이션의 요소(element)'라고 한다. 아래에 선언된 TestInfo 애노테이션은 다섯 개의 요소를 갖는다.
public @interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType { FIRST, FINAL }
DateTime testDate(); // 자신이 아닌 다른 애노테이션(@DateTime)을 포함할 수 있다.
}
@interface DateTime {
String yymmdd();
String hhmmss();
}
애노테이션의 요소는 반환값이 있고 매개변수는 없는 추상 메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 된다. 다만. 애노테이션을 적용할 때 이 요소들이 값을 빠짐없이 지정해주어야 한다. 요소의 이름도 같이 적어주므로 순서는 상관없다.
@TestInfo(
count = 3, testedBy = "Kim",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testDate = @DateTime(yymmdd = "160101", hhmmss = "235959")
)
public class NewClass { ... }
애노테이션의 각 요소는 기본값을 가질 수 있으며, 기본값이 있는 요소는 애노테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용된다.
@interface TestInfo {
int count() default 1;
}
애노테이션 요소가 오직 하나뿐이고 이름이 value인 경우, 애노테이션을 적용할 때 요소의 이름은 생략하고 값만 적어도 된다.
@interface TestInfo {
String value();
}
@TestInfo("passed")
class NewClass { ... }
애노테이션 요소의 규칙
애노테이션 요소를 선언할 때 반드시 지켜야 하는 규칙은 다음과 같다.
- 요소의 타입은 기본형, String, enum, 애노테이션, Class만 허용된다.
- ()안에 매개변수를 선언할 수 없다.
- 예외를 선언할 수 없다.
- 요소를 타입 매개변수로 정의할 수 없다.
@interface AnnoTest {
int id = 100; // OK. 상수 선언. static final int id = 100;
String major(int i, int j); // 에러. 매개변수를 선언할 수 없음
String minor() throws Exception; // 에러. 예외를 선언할 수 없음
ArrayList<T> list(); // 에러. 요소의 타입에 타입 매개변수 사용불가
}
다음은 애노테이션을 직접 정의하고 애노테이션 요소의 값을 출력하는 방법을 보여주는 예제이다.
@Deprecated
@TestInfo(testedBy = "aaa", testDate = @DateTime(yymmdd = "160101", hhmmss = "235959"))
class AnnotationEx5 {
public static void main(String[] args) {
// AnnotationEx5의 Class객체를 얻는다.
Class<AnnotationEx5> cls = AnnotationEx5.class;
TestInfo anno = cls.getAnnotation(TestInfo.class);
System.out.println("anno.testedBy() = " + anno.testedBy());
System.out.println("anno.testDate().yymmdd() = " + anno.testDate().yymmdd());
System.out.println("anno.testDate().hhmmss() = " + anno.testDate().hhmmss());
for (String str : anno.testTools()) {
System.out.println("testTools = " + str);
}
System.out.println();
// AnnotationEx5에 적용된 모든 애노테이션을 가져온다.
Annotation[] annoArr = cls.getAnnotations();
for (Annotation a : annoArr) {
System.out.println(a);
}
}
}
@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정
@interface TestInfo {
int count() default 1;
String testedBy();
String[] testTools() default "JUnit";
TestType testType() default TestType.FIRST;
DateTime testDate();
}
@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정
@interface DateTime {
String yymmdd();
String hhmmss();
}
enum TestType { FIRST, FINAL }
<결과>
anno.testedBy() = aaa
anno.testDate().yymmdd() = 160101
anno.testDate().hhmmss() = 235959
testTools = JUnit
@java.lang.Deprecated(forRemoval=false, since="")
@week12.TestInfo(count=1, testType=FIRST, testTools={"JUnit"}, testedBy="aaa", testDate=@week12.DateTime(yymmdd="160101", hhmmss="235959"))
@Deprecated 애노테이션은 자바에서 기본적으로 제공하는 애노테이션으로 앞으로 사용하지 않을 것을 권장하는 대상에 붙이는 애노테이션이다.
이 밖에, 자바에서 기본적으로 제공하는 애노테이션 중 자주 쓰이는 몇 가지 메타 애노테이션에 대해 알아보자.
메타 애노테이션은 애노테이션을 위한 애노테이션으로 예제의 @Retention처럼 애노테이션을 정의할 때 사용하는 애노테이션을 말한다.
@retention
애노테이션이 유지(retention)되는 기간을 지정하는데 사용된다. 애노테이션의 유지 정책의 종류는 다음과 같다.
유지 정책 | 의미 |
SOURCE | 소스 파일에 존재. 클래스파일에는 존재하지 않음. |
CLASS | 클래스 파일에 존재. 실행시에 사용불가. 기본값 |
RUNTIME | 클래스 파일에 존재. 실행시에 사용가능. |
@Override처럼 컴파일러가 사용하는 애노테이션은 유지 정책이 SOURCE이다. 따라서 @Override는 컴파일 할 때만 사용하고, 컴파일 이후에는 정보가 사라지게 된다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
유지 정책 CLASS는 컴파일러가 애노테이션의 정보를 클래스 파일에 저장할 수 있게는 하지만, 클래스 파일이 JVM에 로딩될 때는 애노테이션의 정보가 무시되어 실행 시에 애노테이션에 대한 정보를 얻을 수 없다. 이것이 CLASS가 기본값임에도 불구하고 잘 사용되지 않는 이유이다.
유지 정책을 RUNTIME으로 하면, 실행 시에 클래스 파일에 저장된 정보를 읽어서 처리할 수 있다. @FunctionalInterface는 @Override처럼 컴파일러가 체크해주는 애노테이션이지만, 실행 시에도 사용되므로 유지 정책이 RUNTIME으로 되어 있다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
@target
애노테이션이 적용가능한 대상을 지정하는데 사용된다. 아래는 자바가 기본적으로 제공하는 애노테이션인 @SuppressWarnings를 정의한 것인데, 이 애노테이션에 적용할 수 있는 대상을 @Target으로 지정하였다.
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
...
String[] value();
}
@Target으로 지정할 수 있는 애노테이션 적용대상의 종류는 아래와 같다.
대상 타입 | 의미 |
ANNOTATION_TYPE | 애노테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(클래스, 인터페이스, enum) |
TYPE_PARAMETER | 타입 매개변수 |
TYPE_USE | 타입이 사용되는 모든 곳 |
@documented
애노테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다. 다시 말해, 해당 애노테이션을 사용하는 클래스가 javadoc과 같은 문서화가 될 때, 해당 애노테이션이 적용되었음을 명시하도록 한다. 자바에서 제공하는 기본 애노테이션 중에 @Override와 @SuppressWarnings를 제외하고는 모두 이 애노테이션이 붙어 있다.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
애노테이션 프로세서
애노테이션 프로세서는 자바 컴파일러 플러그인의 일종으로, 애노테이션에 대한 코드베이스를 검사, 수정, 생성하는 역할이다.
따라서, 애노테이션을 사용하기 위해서는 애노테이션 프로세서가 필요하다.
<동작 구조>
- 애노테이션 프로세서를 사용한다는 것을 자바 컴파일러가 알고 있는 상태에서 컴파일을 수행한다.
- 애노테이션 프로세서 내부에서 각 애노테이션에 대한 처리를 한다.
- 자바 컴파일러가 모든 애노테이션 프로세서가 실행되었는지 검사하고, 모든 애노테이션 프로세서가 실행되지 않았다면 반복한다.
그동안 애노테이션은 주석 정도의 역할만 했지만, 직접 코드를 생성해주는 애노테이션 프로세서의 도움으로 애노테이션은 실제 코드에서도 다양한 역할을 할 수 있게 되었다.
애노테이션 프로세서를 사용하는 대표적인 도구로 '롬복(lombok)'이 있다.
롬복은 컴파일 시점에 애노테이션 프로세서를 사용하여 ServiceLoader용 파일을 생성한다.
ServiceLoader란 간단하게 말해서 어떤 jar 파일에 대한 의존성을 추가하기만 하면 해당 jar 파일의 인터페이스에 대한 여러 구현체들을 사용할 수 있게 한다. 주로 애플리케이션 내부에서 플러그인을 제공할 때 사용한다.
따라서 애노테이션 프로세서와 ServiceLoader를 통해 롬복의 여러 구현체들을 사용할 수 있기 때문에, @Getter 애노테이션을 클래스에 선언하는 것만으로 필드의 getXxx() 메서드를 정의할 수 있는 것이다.
'java > java' 카테고리의 다른 글
[Java] 제네릭 (1) | 2021.09.19 |
---|---|
[Java] I/O (1) | 2021.09.08 |
[Java] 다중 조건 정렬 (0) | 2021.08.29 |
[Java] Enum (1) | 2021.08.27 |
[Java] 멀티쓰레드 프로그래밍 (1) | 2021.08.23 |