[Java] 32. Reflection 기능을 사용하는 방법 - Annotation편


Study/Java  2020. 6. 9. 16:32

안녕하세요. 명월입니다.


이 글은 Java에서의 Reflection 기능을 사용하는 방법 - Annotation편에 대한 글입니다.


이전에 Java의 Reflection의 기능을 Class, Method, Variable로 설명했습니다.

링크 - [Java] 29. Reflection 기능을 사용하는 방법 - Class편

링크 - [Java] 30. Reflection 기능을 사용하는 방법 - Method편

링크 - [Java] 31. Reflection 기능을 사용하는 방법 - Variable편


지금까지는 Reflection이 클래스를 할당하거나 내부의 함수, 변수를 참조하는 것으로 사용했습니다.

Java에서 이 Annotation은 사실 아무런 기능이 없습니다. Annotation은 Java에서 그냥 메타데이터의 하나인데, 메타데이터는 Java의 코딩에 대한 기술또는 설명에 대한 데이터입니다.

근데 이 Annotation은 Java의 Reflection를 만나면 단순히 메타데이터의 기능만 하는 건 아닙니다.

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 어노테이션 생성 (클래스용)
// RUNTIME 설정하지 않으면 실행할 때 getDeclaredAnnotations로 탐색이 되지 않는다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {
  // 기본 default값
  public String value() default "";
}
// 어노테이션 설정
@TestAnnotation("Hello world")
class Example {
  // 실행 함수
  public static void main(String[] args) {
    // Example에서 어노테이션 
    Annotation[] annos = Example.class.getAnnotations();
    // 모든 어노테이션 출력
    for (Annotation anno : annos) {
      // 콘솔 출력
      System.out.println(anno.toString());
      // TestAnnotation 어노테이션이면,
      if (anno.annotationType() == TestAnnotation.class) {
        TestAnnotation a = (TestAnnotation) anno;
        // TestAnnotation의 value의 값 출력
        System.out.println(a.value());
      }
    }
  }
}

위 예제를 보면 Example에서 어노테이션의 값을 취득해서 설정되어 있는 value값을 콘솔에 출력했습니다.


여기까지 보면 딱히 어노테이션과 Reflection으로 크게 활용도가 없어 보이기는 합니다.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
// 어노테이션 생성 (맴버 변수용)
// RUNTIME 설정하지 않으면 실행할 때 getDeclaredAnnotations로 탐색이 되지 않는다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DependancyInjection {
  // 자동 생성할 클래스 타입. 기본은 타입은 Object
  public Class<?> value() default Object.class;
}
// 클래스
class Node {
  // 생성자
  public Node() {  }
  // 함수 생성
  public void print() {
    // 콘솔 출력
    System.out.println("Hello world");
  }
}
// 상위 클래스
class Abstract {
  // 생성자
  public Abstract() {
    try {
      // 맴버 변수를 취득
      for (Field field : Example.class.getDeclaredFields()) {
        // DependancyInjection 어노테이션을 취득
        DependancyInjection anno = field.getDeclaredAnnotation(DependancyInjection.class);
        // 가지고 있다면?
        if (anno != null) {
          // 접근 제한자 무시
          field.setAccessible(true);
          // value 함수 값 취득
          Class<?> clz = anno.value();
          Constructor<?> constructor;
          // 만약 Object라면
          if (clz == Object.class) {
            // 맴버 변수의 타입을 취득
            clz = field.getType();
          }
          // 생성한다.
          constructor = clz.getConstructor();
          // 값에 넣는다.
          field.set(this, constructor.newInstance());
        }
      }
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }
}
// 상위 클래스를 상속 받는다.
class Example extends Abstract {
  // 의존성 주입 어노테이션을 맴버 변수에 설정
  @DependancyInjection()
  private Node node1;
  // 의존성 주입 어노테이션이 없는 맴버 변수
  private Node node2;
  // 출력함수
  public void print() {
    // nod1이 null이 아니면
    if (this.node1 != null) {
      // print 함수 호출
      this.node1.print();
    } else {
      // 콘솔 출력
      System.out.println("node1 null");
    }
    // nod1이 null이 아니면
    if (this.node2 != null) {
      // print 함수 호출
      this.node2.print();
    } else {
      // 콘솔 출력
      System.out.println("node2 null");
    }
  }
  // 실행 함수
  public static void main(String[] args) {
    // Example 인스턴스 생성
    Example ex = new Example();
    // 함수 호출
    ex.print();
  }
}

위 소스를 보시면 Example 클래스에서 맴버 변수를 두개 선언합니다. 그리고 상위 추상 클래스의 생성자에서 DependancyInjection의 어노테이션을 가지고 있는 것에 대해 인스턴스 생성을 합니다.


결과로 print함수를 호출하면 node1은 null이 아니고 print함수가 호출이 되는 것을 확인할 수 있습니다.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
// 어노테이션 생성 (함수용)
// RUNTIME 설정하지 않으면 실행할 때 getDeclaredAnnotations로 탐색이 되지 않는다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface AutoExecute {
  public int value() default 0;
}
// 상위 클래스
class Abstract {
  // 생성자
  public Abstract() {
    try {
      // 함수를 취득
      for (Method method : Example.class.getDeclaredMethods()) {
        // AutoExecute 어노테이션을 취득
        AutoExecute anno = method.getDeclaredAnnotation(AutoExecute.class);
        // 가지고 있다면?
        if (anno != null) {
          // 접근 제한자 무시
          method.setAccessible(true);
          // 함수 실행
          method.invoke(this);
        }
      }
    } catch (Throwable e) {
      // 에러 콘솔 출력
      e.printStackTrace();
    }
  }
}
// 상위 클래스를 상속 받는다.
class Example extends Abstract {
  // 자동 실행 어노테이션 설정
  @AutoExecute
  // 함수 설정
  public void print() {
    // 콘솔 출력
    System.out.println("print");
  }
  // 자동 실행 어노테이션 설정
  @AutoExecute
  // 함수 설정
  public void run() {
    // 콘솔 출력
    System.out.println("run");
  }
  // 자동 실행 어노테이션 설정
  @AutoExecute
  // 함수 설정
  public void test() {
    // 콘솔 출력
    System.out.println("test");
  }
  // 함수 설정
  public void close() {
    // 콘솔 출력
    System.out.println("close");
  }
  // 실행 함수
  public static void main(String[] args) {
    new Example();
  }
}

위 함수는 생성자에서 AutoExecute 어노테이션을 가지고 있는 함수를 찾아서 실행하는 소스입니다.

print 함수와 run, test는 어노테이션이 설정되어 있기 때문에 실행 되는 것을 확인 할 수 있습니다.


원래 어노테이션은 메타 데이터의 기능만 있는데 이렇게 Reflection과 함께 사용하게 되면 의존성 주입이던가 실행 함수 정하기등 여러가지 패턴을 설정할 수 있습니다.


여기까지 Java에서의 Reflection 기능을 사용하는 방법 - Annotation편에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.