Proxy 패턴

뾰족머리삼돌이·2023년 12월 22일
0

디자인패턴

목록 보기
5/21

애플리케이션을 작성하다보면 특정 인스턴스의 동작 전후에 작업을 끼워넣고 싶은 상황이 생긴다.
( 사전동작 ) - 실제 인스턴스의 동작 - ( 사후동작 ) 의 형태로 작업을 동작시키기 위해서는 메서드 등을 이용하여 인스턴스 호출 전후에 동작을 작성하면 된다. 그러나, 다수의 클라이언트에서 동일한 전후동작을 수행시키기 위해서는 별도의 과정을 거쳐야한다.

이러한 상황에서 사용되는 디자인패턴이 프록시 패턴이다.
프록시 패턴은 클라이언트를 대신하여 실제 인스턴스를 호출하는 역할을 맡는다.

클라이언트에게 실행명령을 받으면 프록시 내에서 별도의 동작처리를 수행하고 실제 인스턴스를 호출하는 형식으로 구성된다.
이를통해, 호출하는 클라이언트의 자격을 검사하거나 로그를 찍는 등의 공통적인 작업을 수행할 수 있다.

클래스 구조도에서 확인할 수 있듯이, 프록시는 실제 서비스와 동일한 인터페이스를 구현한다.
클라이언트는 인터페이스 타입의 필드를 사용하기때문에 실제 서비스가 아닌 프록시를 대신 사용하게 된다.

인터페이스를 통해 묶여있기 때문에 손쉽게 프록시를 교체하는 것이 가능하고,
동일한 인터페이스를 상속하기만 한다면 실제 동작할 서비스를 변경하는 것도 가능하다.

예를들어, 클라이언트가 요구하는 작업이 가벼운 경우 실제 인스턴스의 동작대신 작업을 처리하거나,
보다 가벼운 인스턴스를 대신 호출하는 것이 가능하다.


프록시에는 동적 프록시라는게 존재한다.
말 그대로 프록시 클래스를 사전에 생성해놓는게 아닌, 작업의 흐름에 따라 동적으로 생성시키는 것이다.

이와 관련해서는 JVM에서 지원하는 Dynamic Proxy나 CGLIB 등을 확인해보면 된다.

클래스 기반과 인터페이스 기반

Spring AOP과 관련하여 학습을 진행하다보면 AOP구현에 있어서 동적프록시로 Dynamic Proxy와 CGLIB, AspectJ 를 접하게 된다.

이 중에서 Dynamic Proxy와 CGLIB를 알아보자

Dynamic Proxy

Dynamic Proxy는 인터페이스 기반으로 동작하는 동적프록시 구현 기술이다.
리플렉션을 이용하여 런타임시에 동적으로 프록시를 생성한다.

public interface Animal {
    void eat();
    void walk();
}
public class Parrot implements Animal{
    @Override
    public void eat() {
        System.out.println("앵무새가 음식을 먹어요.");
    }

    @Override
    public void walk() {
        System.out.println("앵무새가 걸어가요.");
    }
}
public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("고양이가 음식을 먹어요.");
    }

    @Override
    public void walk() {
        System.out.println("고양이가 걸어가요.");
    }
}

Animal 인터페이스를 구현하는 ParrotCat 클래스를 구성하고, InvocationHandler을 구현하는 프록시 핸들러를 작성한다.

public class AnimalProxyHandler implements InvocationHandler {

    Object target;

    public AnimalProxyHandler(Object target){
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("== before Proxy ==");

        System.out.println(method.getName());
        Object result = method.invoke(target, args);

        System.out.println("== after Proxy");
        return result;
    }
}
public class Main {

    public static void main(String[] args) {
        Object obj = Proxy.newProxyInstance(Animal.class.getClassLoader(),
                new Class[]{Animal.class},
                new AnimalProxyHandler(new Parrot()));

        Animal animal = (Animal) obj;

        animal.eat();
        animal.walk();
    }
}

작성한 핸들러를 Proxy.newProxyInstance의 인수로 전달함으로써 프록시 인스턴스를 얻을 수 있다.

핸들러의 생성자로 넘겨준 인스턴스를 대상으로 동작하는 프록시를 작성할 수 있으며,
인터페이스 타입을 통해 각 메서드를 실행시킬 수 있다.

CGLIB 예시

CGLIB은 클래스기반으로 바이트코드를 조작하여 동작한다
즉, 바이트코드를 조작하여 실제 대상 클래스를 상속하는 클래스를 런타임에 생성하고, 이를 바탕으로 동작한다.

바이트코드 조작에 ASM 라이브러리가 사용된다.

때문에 메서드 선언시 final이나 private를 사용한 경우 프록시를 이용할 수 없다 ( 상속 불가능 )

Dynamic Proxy에서와 같이 InvocationHandler를 이용하는 방식( 리플렉션 사용 ),
그리고 MethodInterceptor를 이용하는 방식( ASM 사용 )이 있다.

1. InvocationHandler

public class AnimalProxyCGLibHandler implements InvocationHandler {

    Object target;

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

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("== before Proxy ==");

        System.out.println(method.getName());
        Object result = method.invoke(target, objects);
        System.out.println("== after Proxy ==");

        return result;
    }
}
public class App
{
    public static void main( String[] args )
    {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Cat.class);
        enhancer.setCallback(new AnimalProxyCGLibHandler(new Cat()));

        Cat obj = ( Cat ) enhancer.create();// 프록시 생성
        obj.eat();
        obj.walk();
    }
}

2. MethodInterceptor

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("== before log ==");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("== after log ==");
        return result;
    }
}
public class App
{
    public static void main( String[] args )
    {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Cat.class);
        enhancer.setCallback(new LogInterceptor());

        Cat obj = ( Cat ) enhancer.create();// 프록시 생성
        obj.eat();
        obj.walk();
    }
}


출처

0개의 댓글

관련 채용 정보