스터디/Spring

[SPRING 고급] 프록시 패턴과 데코레이터 패턴

혜유우 2024. 12. 24. 02:30

📍 프록시

객체에서 프록시가 되려면, 클라이언트는 서버에게 요청을 한 것인지, 프록시에게 요청을 한 것인지 조차 몰라야 한다.

서버와 프록시는 같은 인터페이스를 사용해야 한다. 그리고 클라이언트가 사용하는 서버 객체를 프록시 객체로 변경해도 클라이언트 코드를 변경하지 않고 동작할 수 있어야 한다.

런타임(애플리케이션 실행 시점)에 클라이언트 객체에 DI를 사용해서 client->server에서 client->proxy로 객체 의존관계를 변경해도 클라이언트 코드를 전혀 변경하지 않아도 된다. 클라이언트 입장에서는 변경 사실 조차 모른다.

DI를 사용하면 클라이언트 코드의 변경 없이 유연하게 프록시를 주입할 수 있다.

프록시의 주요 기능

1. 접근 제어

-권한에 따른 접근 차단

-캐싱

-지연 로딩

2. 부가 기능 추가

-원래 서버가 제공하는 기능에 더해서 부가 기능을 수행한다

ex. 요청 값이나, 응답 값을 중간에 변형한다.

ex. 실행 시간을 측정해서 추가 로그를 남긴다.

GOF 디자인 패턴
둘다 프록시를 사용하는 방법이지만 의도에 따라 프록시 패턴과 데코레이터 패턴으로 구분한다
✔️프록시 패턴: 접근 제어가 목적
✔️데코레이터 패턴: 새로운 기능 추가가 목적

cf. 프록시라는 개념은 클라이언트 서버라는 큰 개념안에서 자연스럽게 발생한다. 프록시는 객체안에서의 개념도 있고, 웹 서버에서의 프록시도 있다. 객체안에서 객체로 구현되어 있는가, 웹 서버로 구현되어 있는가 처럼 규모의 차이가 있을 뿐 근본적인 역할은 같다.

 

 

@Slf4j
 public class CacheProxy implements Subject {
 private Subject target;
     private String cacheValue;
     public CacheProxy(Subject target) {
         this.target = target;
}
     @Override
     public String operation() {
log.info("프록시 호출");
if (cacheValue == null) {
             cacheValue = target.operation();
         }
         return cacheValue;
     }
}

클라이언트가 프록시를 호출하면 프록시가 최종적으로 실제 객체를 호출해야 한다. 따라서 내부에 실제 객체의 참조를 가지고 있어야 한다.

이렇게 프록시가 호출하는 대상을 target이라 한다.

cacheValue에 값이 없으면 실제 객체(target)를 호출해서 값을 구한다. 그리고 구한 값을 cacheValue에 저장하고 반환한다. 만약 cacheValue에 값이 있으면 실제 객체를 전혀 호출하지 않고, 캐시 값을 그대로 반환한다. 따라서 처음 조회 이후에는 캐시(cacheValue)에서 매우 빠르게 데이터를 조회할 수 있다.

 

프록시 패턴의 핵심은 클라이언트 코드, 서버 코드를 변경하지 않고 프록시를 도입해서 접근 제어를 한다는 점이다.

그리고 클라이언트 코드의 변경 없이 자유롭게 프록시를 넣고 뺄 수 있다. 실제 클라이언트 입장에서는 프록시 객체가 주입되었는지, 실제 객체가 주입되었는지 알지 못한다.

 

📍데코레이터 패턴

원래 서버가 제공하는 기능에 더해서 부가 기능을 수행한다.

ex. 요청 값이나 응답 값을 중간에 변형한다

ex. 실행 시간을 측정해서 추가 로그를 남긴다

 

 

프록시 패턴의 의도: 다른 개체에 대한 접근을 제어하기 위해 대리자를 제공

데코레이터 패턴의 의도: 객체에 추가 책임(기능)을 동적으로 추가하고 기능 확장을 위한 유연한 대안 제공

 

 

-스프링 컨테이너에 프록시 객체가 등록된다. 스프링 컨테이너는 이제 실제 객체가 아니라 프록시 객체를 스프링 빈으로 관리한다.

-이제 실제 객체는 스프링 컨테이너와는 상관이 없다. 실제 객체는 프록시 객체를 통해서 참조될 뿐이다.

-프록시 객체는 스프링 컨테이너가 관리하고 자바 힙 메모리에도 올라간다. 반면에 실제 객체는 자바 힙 메모리에는 올라가지만 스프링 컨테이너가 관리하지 않는다

cf. 인터페이스가 없어도 클래스 기반의 프록시가 잘 적용된 것을 확인할 수 있다.

 

클래스 기반 프록시의 단점

-자바 기본 문법에 의해 자식 클래스를 생성할 때는 항상 super()로 부모 클래스의 생성자를 호출해야 한다. 이 부분을 생략하면 기본 생성자가 호출된다. 그런

-프록시는 부모 객체의 기능을 사용하지 않기 때문에 super(null)을 입력해도 된다

-인터페이스 기반 프록시는 이런 고민을 하지 않아도 된다.

 

인터페이스 기반 프록시 VS 클래스 기반 프록시

-인터페이스가 없어도 클래스 기반으로 프록시를 생성할 수 있다

-클래스 기반 프록시는 해당 클래스에만 적용할 수 있다. 인터페이스 기반 프록시는 인터페이스만 같으면 모든 곳에 적용할 수 있다.

-클래스 기반 프록시는 상속을 사용하지 않기 때문에 몇 가지 제약이 있다.

 1) 부모 클래스의 생성자를 호출해야 한다

 2) 클래스에 final 키워드가 붙으면 상속이 불가능하다.

 3) 메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다.