[java] Javassist를 사용한 리플렉션 프록시 패턴 구현

리플렉션 프록시 패턴은 객체의 동작을 중간에 가로채서 추가적인 동작을 수행하거나 변경하는 디자인 패턴입니다. Java에서는 리플렉션을 통해 클래스, 메서드, 필드 등에 접근할 수 있으며, 이러한 기능을 이용하여 프록시 객체를 구현할 수 있습니다.

이번 예제에서는 Javassist를 사용하여 리플렉션 프록시 객체를 구현하는 방법을 알아보겠습니다. Javassist는 Java 클래스 파일을 생성, 수정, 분석하는 데 사용되는 라이브러리입니다.

Javassist 설치

Javassist를 사용하기 위해서는 먼저 Javassist 라이브러리를 다운로드하고 프로젝트에 추가해야 합니다. Maven을 사용하는 경우, pom.xml 파일에 다음 의존성을 추가합니다:

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

Maven을 사용하지 않고 직접 라이브러리를 추가하는 경우, 다운로드 한 Javassist JAR 파일을 프로젝트의 빌드 경로에 추가합니다.

프록시 객체 생성

  1. 프록시 객체를 생성할 인터페이스를 정의합니다. 예를 들어, 다음과 같은 UserService 인터페이스를 가정해봅시다:
public interface UserService {
    void addUser(String username);
}
  1. UserService 인터페이스를 구현하는 클래스에 대한 프록시 객체를 생성합니다. 다음은 ProxyUserService 클래스의 예시입니다:
public class ProxyUserService implements UserService {
    private UserService target;

    public ProxyUserService(UserService target) {
        this.target = target;
    }

    @Override
    public void addUser(String username) {
        // 추가적인 동작을 수행하고 원본 메서드를 호출합니다
        System.out.println("Before adding user");
        target.addUser(username);
        System.out.println("After adding user");
    }
}
  1. 메인 메서드에서 프록시 객체를 사용하도록 설정합니다:
public class Main {
    public static void main(String[] args) {
        // 원본 객체 생성
        UserService userService = new UserServiceImpl();

        // 프록시 객체 생성
        ProxyUserService proxyUserService = new ProxyUserService(userService);

        // 프록시 객체를 사용하여 메서드 호출
        proxyUserService.addUser("john");
    }
}

Javassist를 사용한 프록시 객체 생성

이제 Javassist를 사용하여 동일한 리플렉션 프록시 패턴을 구현해보겠습니다. Javassist를 사용하면 런타임에 클래스를 생성하고 수정할 수 있습니다.

import javassist.*;

public class JavassistProxy {
    public static void main(String[] args) throws Exception {
        // 클래스 파일을 생성할 클래스 객체
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.makeClass("ProxyUserService");

        // UserService 인터페이스 상속
        ctClass.addInterface(classPool.get("UserService"));

        // 원본 UserService 객체를 가리키는 필드 추가
        CtField targetField = CtField.make("private UserService target;", ctClass);
        ctClass.addField(targetField);

        // 프록시 객체 생성자 추가
        CtConstructor constructor = CtNewConstructor.make(
                "public ProxyUserService(UserService target) { this.target = target; }",
                ctClass);
        ctClass.addConstructor(constructor);

        // addUser 메서드 추가
        CtMethod addUserMethod = CtNewMethod.make(
                "public void addUser(String username) { " +
                "System.out.println(\"Before adding user\"); " +
                "target.addUser(username); " +
                "System.out.println(\"After adding user\"); }",
                ctClass);
        ctClass.addMethod(addUserMethod);

        // 클래스 파일로 저장
        ctClass.writeFile();
    }
}

위의 코드에서는 makeClass() 메서드를 사용하여 ProxyUserService 클래스를 생성하고, make() 메서드를 사용하여 필드와 메서드를 추가합니다. 필드 및 메서드의 내용은 문자열로 작성되며, 이는 자바 소스 코드와 동일한 형식으로 작성됩니다.

프록시 클래스를 생성한 후, 해당 클래스의 인스턴스를 생성하여 사용하면 됩니다.

결론

이렇게 Javassist를 사용하여 리플렉션 프록시 패턴을 구현할 수 있습니다. Javassist는 동적으로 클래스를 생성하고 수정할 수 있는 강력한 도구입니다. 프록시 패턴은 객체의 동작을 변경하거나 확장하기 위해 많이 사용되며, 리플렉션을 통한 프록시 구현은 유연성과 확장성을 제공합니다.

참고 자료