프록시 패턴(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을 사용함