JAVA

[Java] 동적 프록시에 대해

코카멍멍 2023. 11. 28. 15:38
반응형

프록시는 기존 코드에 영향을 주지 않으면서 타깃의 기능을 확장하거나 접근 방법을 제어할 수 있는 유용한 방법입니다.

하지만 데코레이터 패턴을 활용해서 부가적인 기능을 하는 코드를 클래스마다 매번 정의해야하고 클래스도 매번 넣어야 했습니다. 여기서 오는 코드의 중복과 다수의 클래스가 생겨났습니다.

이러한 문제점들을 해결해주는게 바로 동적 프록시입니다. 자바에서는 동적프록시를 어떻게 사용하는지 알아보겠습니다.

자바에서 동적프록시를 사용하기 위한 클래스

  • Proxy
  • InvocationHandler
  • 서비스로직 인터페이스
  • 서비스로직 구현체
  • 부가기능 구현체(InvocationHandler를 구현한)

위 그림에서 보면 Proxy클래스를 기반으로 동적 프록시가 생성되고 필드에 클래스 로더서비스로직 인터페이스 부가기능 구현체를 넣어주면 됩니다.

Proxy객체를 기반으로한 클래스 생성 코드

Human 인터페이스

여기서 Human 인터페이스를 2개 선언했습니다. 그것은 @TargetAnocation을 적용한 부분과 적용하지 않은 부분을 비교해서 결과를 확인하기 위해서입니다.

public interface Human {
    public void run();
    public void walk();
}
public interface Human {
    @TargetAnotation
    public void run();
    @TargetAnotation
    public void walk();
}

Person 구현체

public class Person implements Human {
    @Override
    public void run() {
        System.out.println("달리고 있다.");
    }

    @Override
    public void walk() {
        System.out.println("걷고 있다.");
    }

}

MyProxyHandler (부가기능 구현체)

public class MyProxyHandler implements InvocationHandler {
    private final Object target;

    public MyProxyHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = new Object();
        if(method.isAnnotationPresent(TargetAnotation.class)){
            System.out.println("========측정 시작==========");
            long startTime = System.nanoTime();

            result = method.invoke(target, args);

            long endTime = System.nanoTime();
            long resultTime = endTime - startTime;
            System.out.println("로깅 :"+ resultTime + " ns");
            System.out.println("===========측정 종료===========");
            return result;
        }
        return method.invoke(target, args);
    }
}

부가기능 구현체에서는 얼마나 시간이 걸렸는지 로깅 로직을 작성했습니다.저는 TargetAnnocation.class 어노테이션이 붙은 메소드에만 부가 기능을 추가하려고 조건문을 작성해 주었습니다. 부가 기능 구현체에서는 필드로 Object를 가지고 있습니다. 이는 데코레이터 패턴으로 서비스로직을 담당하는 객체에 부가기능을 추가 하기 위해서입니다.

실행 클래스

public class JavaReflection {
    public static void main(String[] args) throws Exception {
        Person p = new Person();

        Human person = (Human) Proxy.newProxyInstance(
                Human.class.getClassLoader(),
                new Class[]{Human.class},
                new MyProxyHandler(p));

        person.run();
        person.walk();
    }
}

Proxy를 생성하기 위해서 생성자에 클래스 로더, 서비스로직 인터페이스, 부가기능 구현체를 통해 동적 프록시를 생성할 수 있습니다.

실행 결과

어노테이션 적용

어노테이션 미적용

문제점

동적 프록시가 프록시의 단점들을 해결해주는건 맞습니다. 하지만 인터페이스 기반으로 객체를 다루기 때문에 특정 클래스의 특정 메소드만 로깅을 하고싶을 때 부분적으로 걸어줄 수 없습니다. 또한 구현체로 받게되면 ClassCastException이 발생하게 됩니다.

그래서 이러한 문제점들이 발생하여 Spring에서는 CGLIB를 활용해서 클래스 기반의 동적 프록시를 생성하고 해결합니다.

반응형