[Spring] 프록시 패턴과 데코레이터 패턴
메서드 실행 단위로 로그를 출력할 때 템플릿 메서드 패턴과 전략 패턴을 사용했지만, 기존 코드를 수정해야 한다는 문제점이 남아있다.
프록시를 사용하면 해당 문제를 해결할 수 있다.
네트워크에서의 프록시는 클라이언트와 서버 사이에서 중개자 역할을 수행한다.
클라이언트는 직접 서버와 통신하는 대신 프록시 서버를 통해서 실제 서버에 요청을 보내고, 프록시 서버는 해당 요청을 받아 실제 서버에 전달한다.
1. 캐싱 : 자주 요청되는 리소스를 캐시로 저장해 클라이언트에게 더 빠르게 응답한다.
2. 필터링 : 특정 서비스에 대한 접근을 차단하거나 제한한다.
3. 로드밸런싱 : 프록시 서버가 요청을 여러 서버에 분산시켜 서버 부하를 줄인다.
4. 보안 : 클라이언트의 실제 IP주소를 숨시고 프록시 IP주소를 사용해 보안을 강화한다.
자바 객체에서의 프록시는 디자인 패턴 중 하나인 프록시 패턴에서 사용되고, 실제 객체를 대신해서 해당 객체의 참조를 들고 있고 실제 객체에 대한 연산을 가로채거나 대신 수행해준다.
AOP 기법에서 사용되고, 트랜잭션 관리, 로깅, 보안 등에 활용된다.
자바 객체건 네트워크건 프록시의 근본적인 역할은 동일하고 규모의 차이가 있을 뿐이다.
게이트웨이와 프록시가 비슷하다고 생각할 수 있다.
게이트웨이는 네트워크와 네트워크를 연결하는 역할을 수행해 서로 다른 네트워크의 데이터를 주고받을 수 있도록 한다.
(CGI에서의 Gateway는 클라이언트와 서버 사이에서 HTTP요청을 받아서 처리해 동적 컨텐츠를 생성하는 역할을 수행한다)
자바 객체에서의 프록시에 대해 자세히 살펴보자.
클라이언트는 자신이 실제 서버에 요청하는지 프록시에 요청하는지 알 수 없다.
그저 서비스 인터페이스를 통해 요청하고 결과를 반환받을 뿐이다.
프록시는 서버 인터페이스를 똑같이 구현한 구현체여야 하고, DI를 통해 대체할 수 있어야 한다.
이를 통해 얻을 수 있는 이점은 다음과 같다.
1. 권한에 따른 접근 차단 : 클라이언트의 권한에 따라서 접근을 차단한다.
2. 캐싱 : 클라이언트의 요청을 프록시가 서버에 요청하지 않고 프록시 선에서 응답한다.
3. 지연 로딩 : 실제로 요청이 있을 때 서버에 요청한다.
4. 부가 기능 추가 : 원래 서버가 제공하는 기능에 추가적인 기능을 제공한다.
GOF 디자인패턴에서는 프록시 객체를 사용하는 패턴을 의도에 따라서 프록시 패턴과 데코레이터 패턴으로 구분한다.
프록시 패턴 : 접근 제어가 목적이다.
데코레이터 패턴 : 새로운 기능 추가가 목적이다.
데코레이터 패턴을 사용할 때 Decorator들은 항상 꾸며줄 대상인 Component를 가지고 있어야 한다.
따라서 GOF의 데코레이터 패턴에서는 중복되는 부분인 Component를 추상 클래스로 분리해 다이어그램에서 뭐가 Component고 Decorator인지 명확하게 구분한다.
프록시를 사용하는 데코레이터 패턴을 통해 기존 코드를 수정하지 않으면서 로그 출력 기능을 추가할 수 있다.
컨트롤러, 서비스, 리포지토리 계층을 Repository를 통해 구현하고 각 계층별로 실제 구현체와 프록시 구현체를 만든 후 프록시 구현체가 실제 구현체를 호출하도록 설계한다.
@Configuration
public class InterfaceProxyConfig {
@Bean
public OrderControllerV1 orderController(LogTrace logTrace) {
OrderControllerV1Impl controllerImpl = new OrderControllerV1Impl(orderService(logTrace));
return new OrderControllerInterfaceProxy(controllerImpl, logTrace);
}
@Bean
public OrderServiceV1 orderService(LogTrace logTrace) {
OrderServiceV1Impl serviceImpl = new OrderServiceV1Impl(orderRepository(logTrace));
return new OrderServiceInterfaceProxy(serviceImpl, logTrace);
}
@Bean
public OrderRepositoryV1 orderRepository(LogTrace logTrace) {
OrderRepositoryV1Impl repositoryImpl = new OrderRepositoryV1Impl();
return new OrderRepositoryInterfaceProxy(repositoryImpl, logTrace);
}
}
설계를 만족할 수 있도록 빈으로 등록해 줘야 한다. (logTrace도 빈으로 등록되어 있어야 매개변수로 주입받을 수 있다)
실제 구현체는 프록시를 호출하고 프록시는 실제 구현체를 호출해야 한다. 중간에 프록시를 하나씩 끼워넣는다고 생각하
자.
실제 구현체는 스프링 빈으로 등록되지 않고, 프록시는 내부에 실제 객체를 참조하고 있어 프록시를 통해 실제 구현체를 호출한다.
스프링은 기본적으로 빈을 싱글톤으로 관리하고, 프록시 객체가 싱글톤으로 관리되면 프록시 객체 내부에 있는 실제 구현체도 싱글톤으로 관리된다.
모두 자바 힙 메모리에 올라가는건 같지만 스프링 컨테이너는 프록시 객체만 관리한다.
사용할 때는 Interface를 빈으로 등록된 설정을 통해 주입받아서 사용하니 코드의 수정 없이 부가기능을 추가할 수 있다.
인터페이스를 통해 구현되지 않고 바로 클래스로 구현하는 경우도 비슷하게 진행할 수 있다.
해당 클래스를 상속하는 프록시 객체를 만들고 프록시 객체가 실제 객체를 호출하는 방식으로 설계한다.
Controller, Repository, Service 각 계층별로 해당 클래스를 상속받는 프록시 객체를 만들는 방식으로 진행되는데..
자식 객체를 만들 때 super로 부모 생성자를 호출해 줘야 한다.
클래스 기반 프록시는 해당 클래스에만 적용할 수 있고, 상속을 사용하기에 super로 부모의 생성자를 호출해 준다던지, final 키워드가 붙으면 상속이 제한된다던지.. 상속 관련 문제가 발생하면 사용할 수 없다.
인터페이스 기반 프록시는 상속에서 자유롭고 역할과 구현을 명확하게 나눌 수 있어 더 편리하지만 인터페이스를 도입해야된다는 단점이 있다.
변경이 자주 발생하는 경우 인터페이스를 도입하는 편이 합리적이지만 그렇지 않다면 인터페이스를 도입하는게 번거롭고 실용적이지 않을 수 있다.
프록시를 사용하면 프록시 클래스를 너무 많이 만들어야 한다는 단점이 있다.
프록시 클래스를 하나만 만들어서 모든 곳에 적용하기 위해 동적 프록시 기술이 도입됐다.
'Spring > Spring' 카테고리의 다른 글
[Spring] 프록시 팩토리 (0) | 2023.07.24 |
---|---|
[Spring] 동적 프록시 기술 (0) | 2023.07.23 |
[Spring] 템플릿 메서드 패턴과 전략 패턴 (0) | 2023.07.19 |
[Spring] 로그 추적기와 쓰레드 로컬 (0) | 2023.07.16 |
[Spring Basic] IoC와 DI 구조 (0) | 2023.06.14 |
댓글
이 글 공유하기
다른 글
-
[Spring] 프록시 팩토리
[Spring] 프록시 팩토리
2023.07.24 -
[Spring] 동적 프록시 기술
[Spring] 동적 프록시 기술
2023.07.23 -
[Spring] 템플릿 메서드 패턴과 전략 패턴
[Spring] 템플릿 메서드 패턴과 전략 패턴
2023.07.19 -
[Spring] 로그 추적기와 쓰레드 로컬
[Spring] 로그 추적기와 쓰레드 로컬
2023.07.16