[Java] 다이나믹 프록시를 이용한 프록시 패턴 개선

gyu·2024년 5월 6일

다이나믹 프록시(Dynamic proxy)

기존 프록시 패턴은 원본 클래스의 수만큼 일일이 프록시 클래스를 만들어줘야 한다. 그만큼 코드량이 많아지면서 복잡도가 증가하게 되는 치명적인 단점이 존재한다.

java는 이러한 단점을 보완하여 컴파일 시점이 아닌 런타임 시점에 프록시 클래스를 만들어주는 다이나믹 프록시(Dynamic proxy) 기능이 존재한다. java.lang.reflect.Proxy 패키지에 있는 API를 이용하여 동적으로 프록시 객체를 만들어 사용할 수 있다.

다이나믹 프록시 예제 코드

이전 게시물의 기존 서비스 로직에 트랜잭션 로직을 대신 수행해주는 프록시 패턴을 다이나믹 프록시 방식으로 개선해보자.

UserService.java(interface)

public interface UserService {
    void doAction();
}

UserServiceImpl.java

public class UserServiceImpl implements UserService {

    @Override
    public void doAction() {
        System.out.println("doAction!");
    }
}

TransactionHandler.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TransactionHandler implements InvocationHandler {

    private final Object target;

    public TransactionHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            System.out.println("Transaction start");
            Object result = method.invoke(target, args);
            System.out.println("Transaction commit");
            return result;
        } catch (Exception e) {
            System.out.println("Transaction rollback");
        } finally {
            System.out.println("Transaction end");
        }
        return method.invoke(target, args);
    }
}

Main.java

import java.lang.reflect.Proxy;

public class Main {

    public static void main(String[] args) throws Exception {
        try {
            UserService userService = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                new Class[]{UserService.class},
                new TransactionHandler(new UserServiceImpl())
            );

            System.out.println("userService.getClass() = " + userService.getClass());
            userService.doAction();

        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

newProxyInstance()

Proxy 클래스의 newProxyInstance 메서드를 사용해서 별도 프록시 클래스 정의 없이 자동으로 프록시 객체를 등록할 수 있다. 사용되는 3개의 매개변수는 다음과 같다.

  • ClassLoader loader
    • 프록시 클래스를 만드는 클래스 로더
  • Class<?>[] interfaces
    • 프록시 클래스가 구현하고자 하는 인터페이스 목록
  • InvocationHandler h
    • 프록시 메서드가 호출되었을때 실행되는 핸들러 메서드

InvocationHandler

newProxyInstance() 메서드의 3번째 인자로 사용되는 핸들러 메서드를 정의하는 인터페이스이다. 내부의 invoke() 메서드는 런타임 시점에 생긴 동적 프록시의 메서드가 호출될때 대신 실행되는 메서드이다. 메서드의 정보와 전달된 인자까지 invoke() 메서드의 인자로 들어오게 된다.

위 예제에서는 InvocationHandler를 구현한 TransactionHandler를 따로 생성하여 newProxyInstance 메서드의 3번째 인자로 사용하였다. 이렇게 사용하면 프록시 클래스를 따로 생성하지 않고도 InvocationHandler 하나로 동일한 전처리, 후처리를 수행하는 프록시 객체를 생성할 수 있다.

0개의 댓글