다이나믹 프록시가 뭔데??

임종혁·2024년 4월 18일

토비의 스프링을 학습 중 6장 aop 를 만나면서 다이나믹 프록시를 만났다.
이 다이나믹 프록시가 무엇인지 알아 볼 것이다.

우선 프록시란
우리가 흔히 아는 프록시는 대리인
즉 자신 대신 기능을 제공하는 객체라 알고 있다.

간단한 프록시객체 코드를 보면

public interface Hello {
    String sayHello( );
    String sayHi( );
    String sayThankYou( );
}

다음과 같은 인터페이스를 확장 하여야 한다.

public class HelloTarget implements  Hello{
    @Override
    public String sayHello( ) {
        return "Hello";
    }

    @Override
    public String sayHi() {
        return "hi";
    }

    @Override
    public String sayThankYou( ) {
        return "thankyou";
    }
}

다음은 실제로 사용할 객체이며 앞선 Hello 를 확장 하였다.

허나 요구사항으로 Hellotarget 속 모든 메서드 들을 uppercase 하고 싶다.

그렇다면 프록시를 구현하여 모든 메서드속 문구를 uppercase를 해주면 된다.

public class HelloUppercase implements  Hello{

    Hello helloTarget;

    public HelloUppercase(Hello helloTarget ) {
        this.helloTarget = helloTarget;
    }

    @Override
    public String sayHello( ) {
        return helloTarget.sayHello().toUpperCase();
    }

    @Override
    public String sayHi( ) {
        return helloTarget.sayHi().toUpperCase();
    }

    @Override
    public String sayThankYou( ) {
        return helloTarget.sayThankYou().toUpperCase();
    }
}

이처럼 proxy 는 실제 객체는 자신의 기능만 집중하고 그 이외의 부가 기능을 제공 할 수 있으며
client 는 실제 객체와 통신하고 있다 생각할 수 있게 한다(interface) 로 구현하기 때문에

허나 이런 정적 프록시는 단점이 존재한다.

  • 인터페이스에 따른 모든 메서드를 구현해야함
  • 프록시 마다 중복 코드가 발생

한가지 간단한 예시를 들면

public class HelloUppercase implements  Bye{

    Bye byeTarget;

    public HelloUppercase( Bye byeTarget ) {
        this.byeTarget = byeTarget;
    }

    @Override
    public String sayHello( ) {
        return helloTarget.sayBye().toUpperCase();
    }

    @Override
    public String sayHi( ) {
        return helloTarget.sayBy().toUpperCase();
    }

    @Override
    public String sayThankYou( ) {
        return helloTarget.sayB().toUpperCase();
    }
}

다음과 같이 같은 기능이 추가되지만 다른 interface를 target을 사용하는 프록시일 경우에도 프록시 코드를 짜야해 중복이 발생 된다.

이런 정적 프록시의 단점을 해결하기 위해 동적 프록시를 사용한다.

우선 동적 프록시를 구현하기 위해서는 invocationHandler 라는 인터페이스를 확장 해야한다.

invocationHandler 는

다음과 같은 invoke 라는 메서드가 주어진다.

이 메소드는 Proxy.nexProxyInstance에 의해 동적으로 생성된 프록시 객체의 메소드가 호출되면 동작하는 메서드 이다.

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

public class HelloDynamic implements InvocationHandler {

    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = method.invoke(target,args);
        if(ret instanceof  String){
            return ((String) ret).toUpperCase();
        }
        return ret;
    }
}

다음과 같이 구현을 할 수 있으며

@Test
    public void dynamicTest(){
        Hello proxy = (Hello)Proxy.newProxyInstance(

                getClass().getClassLoader(),
                new Class[]{Hello.class},
                new HelloDynamic(new HelloTarget())
        );

        Assertions.assertThat(proxy.sayHello()).isEqualTo("HELLO");
        Assertions.assertThat(proxy.sayHi()).isEqualTo("HI");
        Assertions.assertThat(proxy.sayThankYou()).isEqualTo("THANKYOU");


    }

다음과 같이 proxy를 만들어 사용할 수 있다.
newProxyInstance() 는 런타임 시점에서 프록시를 생성해 주며
인자로는
프록시를 올릴 클래스 로더, 구현할 인터페이스(target), 앞서 구현한 InvocationHandler 가 필요하다.

0개의 댓글