[java] Javassist를 통한 프록시 클래스 생성

Java에서는 동적으로 클래스를 생성하여 프록시 패턴을 구현할 수 있습니다. 이를 위해 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를 사용하여 프록시 클래스를 생성하는 간단한 예제입니다. 예를 들어, UserService 인터페이스의 구현체를 프록시로 감싸는 프록시 클래스를 생성해봅시다.

import javassist.*;

public class ProxyGenerator {

    public static <T> T createProxy(Class<T> interfaceClass, T target) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(interfaceClass.getName() + "Proxy");

        // 인터페이스 구현
        ctClass.addInterface(pool.get(interfaceClass.getName()));

        // 필드 추가
        ctClass.addField(CtField.make("private " + interfaceClass.getName() + " target;", ctClass));

        // 생성자 추가
        CtConstructor constructor = new CtConstructor(new CtClass[]{pool.get(interfaceClass.getName())}, ctClass);
        constructor.setBody("{this.target = $1;}");
        ctClass.addConstructor(constructor);

        // 메소드 추가
        for (CtMethod method : pool.get(interfaceClass.getName()).getDeclaredMethods()) {
            ctClass.addMethod(CtNewMethod.wrapped(method, "{return target." + method.getName() + "($$);}", ctClass));
        }

        // 프록시 객체 생성
        Class<?> proxyClass = ctClass.toClass();
        return (T) proxyClass.getConstructor(interfaceClass).newInstance(target);
    }
}

위의 예제 코드에서는 createProxy 메소드를 통해 프록시 클래스를 생성합니다. 이 메소드는 인터페이스 클래스와 대상 객체를 인자로 받아 프록시 객체를 반환합니다. 이 때, 프록시 객체는 대상 객체에 정의된 모든 메소드를 호출하고 그 결과를 반환합니다.

사용 예제

아래는 위에서 생성한 ProxyGenerator 클래스를 사용하는 예제입니다.

public interface UserService {
    void registerUser(User user);
    User getUser(String username);
}

public class UserServiceImpl implements UserService {
    public void registerUser(User user) {
        // 사용자 등록 로직
    }

    public User getUser(String username) {
        // 사용자 조회 로직
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        // 프록시 클래스 생성
        try {
            UserService userService = new UserServiceImpl();
            UserService proxy = ProxyGenerator.createProxy(UserService.class, userService);

            // 프록시 메소드 호출
            User user = new User("John");
            proxy.registerUser(user);
            User foundUser = proxy.getUser("John");
            
            // 프록시 결과 확인
            System.out.println("Found user: " + foundUser.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

위의 예제에서는 UserServiceImpl 클래스를 UserService 인터페이스를 구현하는 프록시로 감싸는 프록시 객체를 생성합니다. 프록시 객체를 사용하여 메소드를 호출하고, 호출 결과를 확인합니다.

결론

Javassist를 사용하면 동적으로 프록시 클래스를 생성할 수 있습니다. 이를 활용하여 프록시 패턴을 구현하면, 코드의 재사용성과 유지보수성을 향상시킬 수 있습니다. Javassist의 다양한 기능을 활용하여 복잡한 클래스 변환 및 동적 코드 생성 기능을 구현할 수도 있습니다.