Annotation

어노테이션은 잘만 사용하면 정말 유용한 Java의 구문이다.

기본적인 종류는 몇 가지에 한정되지만, 내가 원하는대로 커스텀 어노테이션을 만들어 사용할 수 있다.

 

어노테이션의 본질적인 목적은 소스 코드에 메타데이터를 표현하는 것.

하지만 단순히 부가적인 표현을 넘어, reflection을 이용하면 어노테이션 지정만으로 원하는 클래스를 주입하는 것도 가능하다.

 

Built-in Annotation

Java에서 기본적으로 제공하는 어노테이션은 다음과 같다.

  • @Override : 오버라이딩할 때 사용.
  • @Deprecated : 더이상 사용되지않는 메서드를 지정한다. 해당 어노테이션이 붙은 메서드를 사용했을 시 컴파일 경고.
  • @SuppressWarnings : 컴파일 경고를 무시한다.
  • @SafeVarargs : 제네릭과 같은 가변인자 파라미터를 사용할 때, 경고를 무시한다.
  • @FunctionalInterface : 람다 함수를 위한 인터페이스를 명시적으로 지정한다. 메서드가 없거나 두 개 이상이면 컴파일 에러 발생.

 

Meta Annotation

메타 어노테이션은 커스텀 어노테이션을 만들 때 사용된다.

메타 어노테이션의 종류는 다음과 같다.

  • @Retention : 해당 어노테이션이 저장될 범위를 지정한다.
                      코드단에서만 저장되고말지, 컴파일된 클래스 파일 안에도 저장되어야할지, 또는 런타임 내내 유지할지.
  • @Documented : 문서에 어노테이션의 정보를 포함한다.
  • @Target : 해당 어노테이션이 적용될 위치를 지정한다.
  • @Inherited : 자식 클래스가 해당 어노테이션을 상속받을 수 있게 한다.
  • @Repeatable : 반복적으로 해당 어노테이션을 선언할 수 있게 한다.

 

Declare Custom Annotation

Java에서 커스텀 어노테이션을 만드는 방법 자체는 간단하다.

public @Interface MyAnnotation {
}

 

위의 형식에서 메타 어노테이션을 붙여 기능을 추가해가며 만들면 된다.

@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME) // 컴파일 이후에도 JVM에 의해 참조가 가능하다.
// @Retention(RetentionPolicy.CLASS) // 컴파일러가 클래스를 참조할 때까지 유효하다.
// @Retention(RetentionPolicy.SOURCE) // 컴파일 이후 해당 어노테이션 정보는 사라진다.
@Target({
ElementType.PACKAGE, // 패키지 선언부에 해당 어노테이션을 기입한다.
ElementType.TYPE, // 타입 선언부에 ~
ElementType.CONSTRUCTOR, // 생성자 선언부에 ~
ElementType.FIELD, // 필드 선언부에 ~
ElementType.METHOD, // 메서드 선언부에 ~
ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언부에 ~
ElementType.LOCAL_VARIABLE, // 지역변수 선언부에 ~
ElementType.PARAMETER, // 매개변수 선언부에 ~
ElementType.TYPE_PARAMETER, // 매개변수 타입 선언부에 ~
ElementType.TYPE_USE // 타입 사용부에 ~
})
public @Interface MyAnnotation {
// enum 타입을 선언할 수 있다.
public enum Color {RED, GREEN, BLUE}
// String은 기본 자료형이 아니지만 사용이 가능하다.
String value();
// 배열 형태도 사용이 가능하다.
int[] values();
// enum 형태를 사용하는 방법
Color color() default Color.RED;
}

 

Simple Example

@StringInjector 라는 커스텀 어노테이션을 만들어보자.

/**
* String 문자열을 주입하기 위해 선언할 수 있는 어노테이션.
* 필드에만 선언할 수 있고, JVM이 해당 어노테이션 정보를 참조한다.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StringInjector {
String value() default "This is StringInjector.";
}

 

@StringInjector 어노테이션을 적용할 클래스는 다음과 같다.

@Data
public class MyObject {
@StringInjector("My name is Pangtrue.")
private String name;
@StringInjector
private String testText;
}

 

다음 코드는 어노테이션을 찾고, 주입하는 역할을 하는 컨테이너 클래스다.

@NoArgsConstructor
public class MyContextContainer {
/**
* 파라미터로 받은 클래스의 객체를 반환한다.
*/
public <T> T get(Class clazz) throws IllegalAccessException, InstantiationException {
T instance = (T) clazz.newInstance();
instance = invokeAnnotations(instance);
return instance;
}
private <T> T invokeAnnotations(T instance) throws IllegalAccessException {
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
StringInjector annotation = field.getAnnotation(StringInjector.class);
if (annotation != null && field.getType() == String.class) {
field.setAccessable(true);
field.set(instance, annotation.value());
}
}
return instance;
}
}

 

위 예제 코드들의 실행을 담당할 메인 엔트리.

public class AnnotationDemo {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
MyContextContainer demo = new MyContextContainer();
MyObject obj = demo.get(MyObject.class);
System.out.println(obj.getName()); // print is "My name is Pangtrue."
System.out.println(obj.gettestText()); // print is "This is StringInjector."
}
}

 

 

참고 사이트

https://jdm.kr/blog/216

 

 

댓글

댓글을 사용할 수 없습니다.