본문 바로가기

Java

자바에서 애너테이션(Annotation) 직접 정의하기

728x90

애너테이션이란, 주석처럼 프로그래밍 언어에 영향을 미치지는 않으면서도 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킴으로써 다른 프로그램에게 유용한 정보를 제공할 수 있는 기능을 제공하는 '메모'라고 할 수 있습니다.

 

Java에서는 @Override나 @SuppressWarnings와 같은 다양한 애너테이션을 제공합니다.  이번 시간에는 우리가 직접 애너테이션을 만들어서 어떻게 사용할 수 있는지에 대해서 알아보겠습니다.

 


애너테이션 정의

먼저, 애너테이션을 정의하는 방법은 아래와 같습니다.


@interface 애너테이션이름{
   타입 요소이름 (); // 애너테이션 요소 선언
   ...
}

 

애너테이션의 요소는 반환값이 있고 매개변수는 없는 추상 메서드의 형태를 가집니다. 상속을 통해서 구현될 필요는 없습니다. 하지만, 애너테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해주어야 합니다.

예시를 통해 확인해 보겠습니다.

 

1. 애너테이션 정의 

애너테이션애너테이션 정의 후, 실제로 클래스에서 애너테이션 요소에 접근하고자 한다면 아래와 같이 @Retention(RententionPolicy.RUNTIME)으로 설정을 해주어야 합니다.


// TestClass.java
@Retention(RetentionPolicy.RUNTIME) // 실행시에 사용 가능하도록 지정!!
public @interface TestClass{
   int version();
   String author();
   String[] coworker();
   TestType testType(); // enum 타입
   TestTime testTime(); // 애너테이션 타입
}

//TestType.java
public enum TestType{First(1), Second(2);
   int i;
   TestType(int i) {
      this.i = i;
   }
}

//TestTime.java
public @interface TestTime{
   String yymmdd();
   String hhmmss();
}

[참고] - @Retention

Java에서 제공하는 메타 애너테이션으로, '애너테이션을 위한 애너테이션'이라고 할 수 있습니다. 즉, 애너테이션에 붙이는 애너테이션으로, 애너테이션을 정의할 때 애너테이션의 적용대상(target)이나 유지기간(retention)등을 지정하는 데 사용됩니다.

 

<@Retention 정책 종류>

유지 정책 의미
SOURCE 소스 파일에만 존재. 클래스파일에는 존재하지 않음.
CLASS 클래스 파일에 존재. 실행시에 사용불가. 기본값
RUNTIME 클래스 파일에 존재. 실행시에 사용가능.

2. 애너테이션 적용

아래 코드와 같이 여러 개의 요소를 가진 애너테이션이라면 각 요소의 이름을 적어주어야 합니다. (이름을 명시해주므로 순서는 상관없습니다.) 


// AnnotationExample.java
@TestClass(version = 1, 
        author = "Silver Programmer", 
        coworker = {"Eun", "Kim"}, 
        testType = TestType.First, 
        testTime = @TestTime(yymmdd="231021", hhmmss="155400"))

public class AnnotationExample {}

 

만약 하나의 요소를 가진 애너테이션이고, 그 요소의 이름이 value라면 애너테이션 적용 시, 요소의 이름을 적을 필요 없이 값만 적어주면 됩니다.


// 애너테이션 정의
public @interface Simple {
    int value();
}

// 애너테이션 적용
@Simple(123)
class ... {
	...
}

아래와 같이 배열도 가능합니다.


// 애너테이션 정의
public @interface Simple {
    String[] value();
}

// 애너테이션 적용
@Simple("123")
class ...{
	...
}

자바에서 제공하는 @SuppressWarnings 애너테이션이 위와 같이 String [] value(); 로만 요소가 정의되어 있어 애너테이션을 적용할 때 값을 생략할 수 있습니다.


// 자바에서 제공하는 SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}


// 애너테이션 적용
@SuppressWarnings({"deprecation", "unchecked", "varags", "rawtypes"})
// @SuppressWarnings(value = {"deprecation", "unchecked", "varags", "rawtypes"})

또한, 배열로 선언된 요소라면 위의 코드처럼 { }로 여러 개의 값을 지정할 수 있습니다. 만약, 여러 개의 값을 지정할 필요가 없다면 { }는 생략 가능합니다. 마지막으로, 값이 없을 때는 { }가 반드시 필요합니다.


@TestClass(coworker = {"Eun", "Kim"}) // 값이 여러 개 일 때
@TestClass(coworker = "Eun") // 값이 한 개 일 때
@TestClass(coworker = {}) // 값이 없을 때

애너테이션의 각 요소는 기본값(디폴트 값)을 가질 수도 있습니다. 디폴트 값을 가지면 애너테이션 적용 시, 값을 지정하지 않아도 되고 지정하지 않으면 디폴트 값이 적용됩니다.


// TestClass.java
@Retention(RetentionPolicy.RUNTIME)
public @interface TestClass{
   int version();
   String author() default "Silver Programmer"; // 기본값 
   String[] coworker();
   TestType testType() default TestType.First; // 기본값
   TestTime testTime();
}

// TestType.java
public enum TestType{First(1), Second(2);
   int i;
   TestType(int i) {
      this.i = i;
   }
}

// TestTime.java
public @interface TestTime{
   String yymmdd();
   String hhmmss();
}

// AnnotationExample.java
// 애너테이션 적용시 author, testType 요소 지정은 생략 가능
@TestClass(version = 1,
       // author = "Silver Programmer",
        coworker = {"Eun"},
        // testType = TestType.First,
        testTime = @TestTime(yymmdd="231021", hhmmss="155400"))
        
public class AnnotationExample {}

마커 애너테이션

자바에서 제공하는 @Override처럼 요소가 필요 없는 경우 요소를 하나도 정의하지 않는 애너테이션을 마커 애너테이션이라고 합니다.


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

애너테이션 요소의 규칙

  1. 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용됩니다.
  2. ( ) 안에 매개변수를 선언할 수 없습니다.
  3. 예외를 선언할 수 없습니다.
  4. 요소를 타입 매개변수로 정의할 수 없습니다.

애너테이션의 조상 Annotation

모든 애너테이션의 조상은 java.lang.annotation.Annotation 입니다. 하지만 애너테이션은 상속이 불가하여 Annotation을 상속받도록 지정할 수 없습니다. 또한, Annotation은 애너테이션이 아니라 일반적인 interface 입니다. 따라서, 모든 애너테이션 객체들은 Annotation에서 제공하는 메서드들을 사용할 수 있습니다.


// java에서 제공하는 Annotation 인터페이스
package java.lang.annotation;

public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}

애너테이션 사용하기

애너테이션을 직접 정의한 후 애너테이션의 요소 값을 출력하려면 아래와 같이 코드를 작성하면 됩니다.


 Class<AnnotationExample> cls = AnnotationExample.class;
 TestClass testAnno = cls.getAnnotation(TestClass.class);
 System.out.println(testAnno.author());

 

위 getAnnotation()은 해당 클래스에 적용된 특정 애너테이션만 가지고 옵니다. 만약, 해당 클래스 객체에 적용된 모든 애너테이션을 가지고 오려면 getAnnotations() 메서드를 사용합니다.


Annotation[] annotations = cls.getAnnotations();

 

전체 코드로 확인하겠습니다.


// TestClass.java
@Retention(RetentionPolicy.RUNTIME) 
public @interface TestClass {
    int version();
    String author() default "Silver Programmer";
    String[] coworker();
    TestType testType() default TestType.First;
    TestTime testTime();
}

// TestType.java
public enum TestType {
    First(1), Second(2);

    int i;

    TestType(int i) {
        this.i = i;
    }
}

// TestTime.java
// @Retention(RetentionPolicy.RUNTIME) 필요 없음
public @interface TestTime {
    String yymmdd();

    String hhmmss();
}

// Simple.java
@Retention(RetentionPolicy.RUNTIME)
public @interface Simple {
    String[] value();
}

// AnnotationExample.java
@TestClass(version = 1,
        // author = "Silver Programmer",
        coworker = {"Eun"},
        // testType = TestType.First,
        testTime = @TestTime(yymmdd = "231021", hhmmss = "155400"))

@Simple("123")
public class AnnotationExample {}
// Main.java
public class Main {
    public static void main(String[] args) {
        Class<AnnotationExample> cls = AnnotationExample.class;
        TestClass testAnno = cls.getAnnotation(TestClass.class);
        System.out.println("TestClass Author: " + testAnno.author());
        System.out.println("TestClass testDate: "+testAnno.testTime().yymmdd());
        System.out.println("TestClass testTime: "+testAnno.testTime().hhmmss());

        System.out.println("===================================================");

        Annotation[] annotations = cls.getAnnotations();
        for (Annotation anno : annotations) {
            System.out.println("Anootation toString(): " + anno.toString());
            System.out.println("Annotation hashCode(): " + anno.hashCode());
            System.out.println("Annotation annotationType(): " +anno.annotationType());
            System.out.println("------------------------------------------------------");
        }

    }
}

 

[결과]

TestClass Author: Silver Programmer
TestClass testDate: 231021
TestClass testTime: 155400
===================================================
Anootation toString(): @com.example.demo.TestClass(testType=First, author="Silver Programmer", version=1, coworker={"Eun"}, testTime=@com.example.demo.TestTime(yymmdd="231021", hhmmss="155400")) Annotation hashCode(): -786566109
Annotation annotationType(): interface com.example.demo.TestClass
------------------------------------------------------
Anootation toString(): @com.example.demo.Simple({"123"})
Annotation hashCode(): 1335662942
Annotation annotationType(): interface com.example.demo.Simple
------------------------------------------------------

[참고자료]

남궁 성, [Java의 정석 3rd Edition], 도우출판, 2016, p702~714

https://docs.oracle.com/javase/8/docs/technotes/guides/language/annotations.html

 

Annotations

Annotations Many APIs require a fair amount of boilerplate code. For example, in order to write a JAX-RPC web service, you must provide a paired interface and implementation. This boilerplate could be generated automatically by a tool if the program were

docs.oracle.com

728x90
반응형

'Java' 카테고리의 다른 글

Thread 구현과 실행 (Runnable vs Thread)  (0) 2023.10.25
자바의 애너테이션(Annotation)  (2) 2023.10.22
-Xlint 옵션  (0) 2023.10.21
열거형(enums)  (2) 2023.10.21
Comparable과 Comparator  (4) 2023.10.20