관리 메뉴

개발그래머

Design Pattern[2] 프록시 패턴 본문

Java

Design Pattern[2] 프록시 패턴

임요환 2023. 4. 20. 19:36

프록시 패턴(Proxy Pattern)

  • 특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 패턴
  • 클래스의 사용을 중간 대리인(proxy)을 거쳐 사용함
  • 접근 제어, 초기화 지연, lazy loading, logging, cache 등을 사용하여 성능적인 이점을 취함

장점

  • 기존 코드 변경 없이 새로운 기능 추가 가능(OCP)
  • 기존 코드는 해야 하는 일만 유지(SRP)
  • 기능 추가 및 초기화 지연 등으로 다양하게 활용

단점

  • 코드 복잡도 증가

예제 코드

public class GameService {
    public void startGame() {
        System.out.println("이 자리에 오신 여러분을 진심으로 환영합니다.");
    }
}

public class Client {
    public static void main(String[] args) {
        GameService gameService = new GameSerivce();
        gameService.startGame();
    }
}

상속

public class GameService {
    public void startGame() {
        System.out.println("이 자리에 오신 여러분을 진심으로 환영합니다.");
    }
}

public class GameServiceProxy extends GameService {

    @Override
    public void startGame() {
        long before = System.currentTimeMillis();
        super.startGame();
        System.out.println(System.currentTimeMillis() - before);
    }
}

public class Client {
    public static void main(String[] args) {
        GameService gameService = new GameSerivceProxy();
        gameService.startGame();
    }
}
  • GameService 클래스 자체가 사용이 되어지고 있는 경우에는 상속을 이용한 방법을 사용해야 함
  • 상속은 제약이 많음(하나만 상속가능, final 클래스일 시 상속 못함)
  • 가급적이면 인터페이스로 재설계해야함 -> 유연성 증가

인터페이스

public interface GameService {
    void startGame();
}

public class DefaultGameService implements GameService {

    @Override
    public void startGame() {
        System.out.println("이 자리에 오신 여러분을 진심으로 환영합니다.");
    }
}

public class GameServiceProxy implements GameService {
    private GameService gameService;

    public GameServiceProxy(GameService gameService) {
        this.gameService = gameService;
    }

    @Override
    public void startGame() {
        long before = System.currentTimeMillis();
        gameService,startGame();
        System.out.println(System.currentTimeMillis() - before);
    }
}

public class Client {
    public static void main(String[] args) {
        GameService gameService = new GameSerivceProxy(new DefaultGameService());
        gameService.startGame();
    }
}
  • DefaultGameProxy에서는 원래 코드를 유지하고 부가적인 코드는 GameServiceProxy에서 작성
public class GameServiceProxy implements GameService {
    private GameService gameService;

    @Override
    public void startGame() {
        long before = System.currentTimeMillis();
        // lazy initialize
        if(this.gameService == null) {
            this.gameService = new DefaultGameService();
        }
        //접근제어 코드 삽입 가능
        //...
        gameService,startGame();
        System.out.println(System.currentTimeMillis() - before);
    }
}

public class Client {
    public static void main(String[] args) {
        GameService gameService = new GameSerivceProxy();
        gameService.startGame();
    }
}
  • lazy initialize 등 다양한 기능 적용 가능
  • return 타입이 있는 경우 캐시 적용 가능

실무에서 사용하는 프록시 예제

  • 다이나믹 프록시, java.lang.reflect.Proxy
  • 스프링 AOP

다이나믹 프록시

public class ProxyInJava {
    public static void main(String[] args) {

    }

    private void dynamicProxy() {
        GameService gameServiceProxy = getGameServiceProxy(new DefaultGameService());
        gameServiceProxy.startGame();
    }

    private GameService getGameServiceProxy(GameService target) {
        return (GameService) Proxy.newProxyInstance(this.getClass().getClassLoader(), 
                new class[]{GameService.class}, (proxy, method, args) -> { // invocationHandler()의 invoke 구현
            System.out.println("Before Hello dynamic proxy");
            method.invoke(target, args);
            System.out.println("After Hello dynamic proxy");
            return null;
        });
    }
}

스프링 AOP

@Aspect
@Component
public calss PerfAspect {

    @Around("bean(gameService)")
    public void timestamp(ProceedingJoinPoint point) throws Throwable {
        long before = System.currentTimeMillis();
        point.proceed();
        System.out.println(System.currentTimeMillis() - before);
    }
}
  • 스프링 AOP를 적용하려면 스프링 빈으로 등록되어야 함
  • 적용될 클래스도 스프링 빈으로 등록되어야 함
  • 스프링이 구동될 때 다이나믹 프록시를 사용해서 내부적으로 프록시 빈을 만들어서 등록해줌
  • 클래스를 프록시로 만들 경우에는 CGLIB을 사용함