[java] Javassist를 통한 어노테이션 삽입과 제거

Javassist는 자바 프로그램을 동적으로 조작하기 위한 도구입니다. 이를 통해 어노테이션을 클래스 파일에 삽입하거나 제거할 수 있습니다. 이번 포스트에서는 Javassist를 사용하여 어노테이션을 삽입하고 제거하는 방법에 대해 알아보겠습니다.

Javassist 라이브러리 추가

먼저, Javassist를 사용하기 위해 프로젝트에 라이브러리를 추가해야 합니다. Maven을 사용한다면 pom.xml 파일에 다음과 같이 의존성을 추가합니다.

<dependencies>
  <dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
  </dependency>
</dependencies>

Gradle을 사용한다면 build.gradle 파일에 다음과 같이 의존성을 추가합니다.

dependencies {
  implementation 'org.javassist:javassist:3.27.0-GA'
}

어노테이션 삽입하기

어노테이션을 삽입하기 위해서는 Javassist의 CtClass 클래스와 CtMethod 클래스를 사용해야 합니다. 다음은 클래스와 메소드에 어노테이션을 삽입하는 예제 코드입니다.

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.StringMemberValue;

public class AnnotationExample {
    public static void main(String[] args) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.get("com.example.MyClass");

            // MyClass에 @MyAnnotation 어노테이션 삽입
            AnnotationsAttribute attribute = new AnnotationsAttribute(ctClass.getClassFile().getConstPool(), AnnotationsAttribute.visibleTag);
            Annotation annotation = new Annotation("com.example.MyAnnotation", ctClass.getClassFile().getConstPool());
            annotation.addMemberValue("value", new StringMemberValue("Hello, Javassist!", ctClass.getClassFile().getConstPool()));
            attribute.setAnnotation(annotation);
            ctClass.getClassFile().addAttribute(attribute);

            // MyClass의 printMessage 메소드에 @Deprecated 어노테이션 삽입
            CtMethod printMessageMethod = ctClass.getDeclaredMethod("printMessage");
            AnnotationsAttribute methodAttribute = new AnnotationsAttribute(ctClass.getClassFile().getConstPool(), AnnotationsAttribute.visibleTag);
            Annotation methodAnnotation = new Annotation("java.lang.Deprecated", ctClass.getClassFile().getConstPool());
            methodAttribute.setAnnotation(methodAnnotation);
            printMessageMethod.getMethodInfo().addAttribute(methodAttribute);

            // 수정된 클래스 파일 저장
            ctClass.writeFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

위의 예제 코드에서는 com.example.MyClass 클래스에 @MyAnnotation 어노테이션을 삽입하고, printMessage 메소드에 @Deprecated 어노테이션을 삽입합니다.

어노테이션 제거하기

어노테이션을 제거하기 위해서는 AnnotationsAttribute 클래스를 사용하여 해당 어노테이션을 가져온 뒤, 적절한 방법으로 제거하면 됩니다. 다음은 어노테이션을 제거하는 예제 코드입니다.

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;

public class AnnotationRemovalExample {
    public static void main(String[] args) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.get("com.example.MyClass");

            // MyClass에서 @MyAnnotation 어노테이션 제거
            AnnotationsAttribute attribute = (AnnotationsAttribute) ctClass.getClassFile()
                    .getAttribute(AnnotationsAttribute.visibleTag);
            if (attribute != null) {
                Annotation[] annotations = attribute.getAnnotations();
                for (Annotation annotation : annotations) {
                    if (annotation.getTypeName().equals("com.example.MyAnnotation")) {
                        attribute.removeAnnotation(annotation);
                    }
                }
                ctClass.getClassFile().addAttribute(attribute);
            }

            // MyClass의 name 필드에서 @Deprecated 어노테이션 제거
            CtField nameField = ctClass.getDeclaredField("name");
            AnnotationsAttribute fieldAttribute = (AnnotationsAttribute) nameField.getFieldInfo()
                    .getAttribute(AnnotationsAttribute.visibleTag);
            if (fieldAttribute != null) {
                Annotation[] fieldAnnotations = fieldAttribute.getAnnotations();
                for (Annotation annotation : fieldAnnotations) {
                    if (annotation.getTypeName().equals("java.lang.Deprecated")) {
                        fieldAttribute.removeAnnotation(annotation);
                    }
                }
                nameField.getFieldInfo().addAttribute(fieldAttribute);
            }

            // 수정된 클래스 파일 저장
            ctClass.writeFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

위의 예제 코드에서는 com.example.MyClass 클래스에서 @MyAnnotation 어노테이션을 제거하고, name 필드에서 @Deprecated 어노테이션을 제거합니다. 어노테이션을 제거하기 위해서는 해당 어노테이션을 찾아서 삭제하는 작업이 필요합니다.

마무리

이번 포스트에서는 Javassist를 사용하여 어노테이션을 삽입하고 제거하는 방법에 대해 알아보았습니다. Javassist를 통해 동적으로 클래스 파일을 조작하는 것은 런타임 시에 유연하고 가볍게 프로그램을 수정할 수 있는 좋은 방법입니다. Javassist에 대한 자세한 내용은 공식 문서를 참고하시기 바랍니다.