티스토리 뷰

디자인 패턴

12. 프록시 패턴

jin-park 2022. 2. 10. 19:50

1.  프록시 패턴

  • 특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 패턴.
  • 초기화지연, 접근 제어, 로깅, 캐싱등 다양하게 사용 가능함.

다이어그램

2.  패턴 적용 전 코드

  • 아래와 같이 게임을 시작하는 메서드가 존재함.
  • 게임을 시작하는 메서드를 수정하지 않고 프록시 패턴을 적용하여 메서드가 실행되는 소요시간을 계산하려함.
public class GameService {
    public void gameStart() {
        System.out.println("이 자리에 오신 여러분 진심으로 환영합니다.");
    }
}
public class Client {
    public static void main(String[] args) {
        GameService gameService = new GameService();
        gameService.gameStart();
    }
}

3.  패턴 적용 후 코드

3.1.  상속을 이용한 방식

  • 프록시 패턴을 사용하고자 하는 클래스가 인터페이스를 사용하지 않았다면 상속을 이용하여 적용한다.
public class GameServiceProxy extends GameService {
    @Override
    public void gameStart() {
        long start = System.currentTimeMillis();
        super.gameStart();
        System.out.println(System.currentTimeMillis() - start);
    }
}
public class ProxyClient {
    public static void main(String[] args) {
        GameService gameService = new GameServiceProxy();
        gameService.gameStart();
    }
}

3.2.  인터페이스를 이용한 방식

  • 인터페이스를 이용하여 구현하면 유연한 개발이 가능해짐. (활용도가 높아짐)
public interface GameService {
    void gameStart();
}
public class DefaultGameService implements GameService {
    @Override
    public void gameStart() {
        System.out.println("이 자리에 오신 여러분들 환영합니다.");
    }
}
public class GameServiceProxy implements GameService {
 
    private final GameService gameService;
 
    public GameServiceProxy(GameService gameService) {
        this.gameService = gameService;
    }
 
    @Override
    public void gameStart() {
        long start = System.currentTimeMillis();
        this.gameService.gameStart();
        System.out.println(System.currentTimeMillis() - start);
    }
}
public class ProxyClient {
    public static void main(String[] args) {
        GameService gameService = new GameServiceProxy(new DefaultGameService());
        gameService.gameStart();
    }
}

4.  장점 및 단점

4.1.  장점

  • 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있다.
  • 기존 코드가 해야 할 일만 남겨 둘수 있다.
  • 기능 추가 및 초기화 지연 등으로 다양하게 활용할 수 있다.

4.2.  단점

  • 코드가 복잡해진다.

5.  실 사용 예시

5.1.  자바 - Dynamic Proxy

5.1.1.  프록시 실제 구현시 문제점

  • 인터페이스를 직접 구현해야한다.
  • 인터페이스의 모든 구현체를 구현(override)해야 한다. (특정 메서드만 프록시 사용 불가)
  • 프록시 클래스 내에 중복이 발생 할 수 있다. (메서드 마다 하는일이 비슷할 시 중복 발생 가능)

5.1.2.  Dynamic Proxy

  • 런타임 시점에 인터페이스를 구현하는 클래스 또는 인스턴스를 만드는 기술을 의미한다.
  • 그러나 Dynamic Proxy의 경우 인터페이스를 통해야만 구현이 가능하다.
public class ProxyClient {
    public static void main(String[] args) {
        ProxyClient proxyClient = new ProxyClient();
        GameService gameService = proxyClient.dynamicProxy();
        gameService.gameStart();
    }
 
    public GameService dynamicProxy() {
        GameService gameServiceProxy = getGameServiceProxy(new DefaultGameService());
        return gameServiceProxy;
    }
 
    public GameService getGameServiceProxy(GameService target) {
        return (GameService) Proxy.newProxyInstance(
            this.getClass().getClassLoader(), // 프록시를 정의할 클래스 로더
            new Class[] { GameService.class }, // 프록시 클래스가 구현하고자하는 인터페이스 목록
            (proxy, method, args) -> { // 프록시의 메서드가 호출될때 처리되는 구현부
                System.out.println("Hello Dynamic Proxy");
                method.invoke(target, args);
                return null;
            }
        );
    }
}

5.1.3.  CGLib (Code Generator Library)

  • 클래스 상속을 통하여 Proxy를 동적으로 구현해 주므로 인터페이스를 사용하지 않고 Proxy를 사용할 수 있게 해준다.
  • Spring AOP, Hibernate 등에서 CGLib를 사용한다.
  • 그러나, 상속을 이용한 Proxy이므로 상속이 불가(final, private 생성자, abstract)한 클래스의 경우에는 사용이 불가하다.
<dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
</dependency>
public class ProxyClient {
    public static void main(String[] args) {
        ProxyClient proxyClient = new ProxyClient();
        GameService cgLibProxyGameService = proxyClient.cgLibProxy();
        cgLibProxyGameService.gameStart();
    }
 
    public GameService cgLibProxy() {
        MethodInterceptor interceptor = new MethodInterceptor() {
            final GameService gameService = new DefaultGameService();
 
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("game start 이전");
                Object invoke = method.invoke(gameService, objects);
                System.out.println("game start 이후");
 
                return invoke;
            }
        };
 
        GameService proxyGameService = (GameService) Enhancer.create(GameService.class, interceptor);
        return proxyGameService;
    }
}

'디자인 패턴' 카테고리의 다른 글

14. 커맨드 패턴  (0) 2022.02.10
13. 책임연쇄패턴  (0) 2022.02.10
11. 플라이 웨이트 패턴  (0) 2022.02.10
10. 퍼사드 패턴  (0) 2022.02.10
09. 데코레이터 패턴  (0) 2022.02.10
댓글