프록시 패턴과 데코레이터 패턴

ParkIsComing·2023년 12월 10일

Spring

목록 보기
16/21

인프런 강의 [스프링 핵심 원리 - 고급편]을 수강하고 정리한 내용을 바탕으로 작성하였습니다.

프록시

프록시는 다양한 역할을 수행할 수 있다.

  1. 접근 제어
    1) 캐싱
    2) 권한에 따른 접근 차단
    3) 지연 로딩
  2. 부가 기능 추가
  3. 프록시 체인 (예시 : client -> proxy1 -> proxy2 -> server)

🤔그렇다면 아무 객체나 프록시가 될 수 있을까?

아니다.
객체가 프록시로 거듭나려면, 클라이너트는 서버에게 요청을 한 것인지 프록시에게 요청을 한 것인지 몰라야 한다. 달리 말하면 클라이언트가 요청하는 대상을 서버 객체에서 프록시 객체로 변경해도 클라이언트는 몰라야 한다는 것이다.

DI를 사용하여 런타임에 client는 proxy를 의존하고, proxy는 실제 객체를 의존하도록 프록시를 적용할 수 있다.

GOF 디자인 패턴과 프록시

GOF 디자인 패턴에서는 의도(intent)에 따라서 프록시 패턴과 데코레이터 패턴으로 구분한다.

  • 프록시 패턴: 접근 제어가 목적
  • 데코레이터 패턴: 새로운 기능 추가가 목적

프록시 패턴

위와 같은 Subject 인터페이스를 이용하여 실제 객체(RealSubject)와 프록시(CacheProxy)를 구현하였다.


⬆️ RealSubject.java

⬆️ CacheProxy.java

클라이언트는 단순히 Subject를 의존한다.

클라이언트는 Subject를 의존할 뿐이지만 프록시와 실제 객체 모두 Subject 인터페이스의 구현체이기 때문에 중간에 프록시를 적용할 수 있게 되는 것이다.

데코레이터 패턴


위와 같은 간단한 Component 인터페이스를 이용하여 실제 컴포넌트(RealComponent)와 데코레이터(TimeDecorator, MessageDecorator)를 구현하였다.

클라이언트 코드는 단순히 Component를 의존한다.

이처럼 클라이언트는 Component를 의존할 뿐이지만 데코레이터와 실제 객체 모두 Component의 구현체이기 때문에 프록시를 적용할 수 있게 된다.

프록시 패턴 vs 데코레이터 패턴

위의 글을 읽다보면 느낄 수 있듯이 둘이 상당히 유사하다. GOF 디자인 패턴에서 둘의 구분하는 기준은 의도라는 점을 잊지 말자.

프록시 적용

  • 원본 코드를 변경하지 않고 v1, v2 애플리케이션에 LogTrace 기능을 적용해본다.
  • 실체 객체 대신 프록시를 스프링 빈에 등록한다.
  • v1은 인터페이스 기반 프록시, v2는 구체 클래스 기반 프록시 적용

1. 인터페이스 기반 프록시

  • 인터페이스를 만들어야 하는 번거로움이 있지만, 인터페이스만 같으면 모든 곳에 프록시를 적용할 수 있다는 장점을 가진다.
  • 역할과 구현을 나눌 수 있다. 하지만 실제 프로젝트에서 구현을 변경할 일이 거의 없는 클래스도 많기 때문에 구현을 변경할 가능성이 있을 때 적용하는 것이 효율적이다.

2. 구체 클래스 기반 프록시

  • 단점
    • 부모 클래스의 생성자를 호출해야 한다.
    • 클래스에 final 키워드가 붙으면 상속이 불가능하다.
    • 메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다.

추가) 자식 클래스와 생성자

  • 자식 클래스는 자신의 생성자를 호출하기 전에 부모클래스의 생성자를 호출해야 한다. 이때 해당 생성자에 매개변수가 있을 경우 super()을 사용해서 명시적으로 호출해야 한다.

❌ 에러 발생 상황

OrderServiceConcreteProxyOrderServiceV2 를 상속한다.

OrderServiceConcreteProxy.java에서 다음과 같은 에러가 발생하였다.

There is no default constructor available in 'hello.proxy.app.v2.OrderServiceV2

OrderServiceV2.java 코드이다.

🤔 에러가 발생한 이유

  • 자식클래스 생성자에서 super() 부분을 생략하면 기본 생성자가 호출된다.
  • 하지만 부모클래스인 OrderServiceV2에는 기본 생성자가 없기 때문에 파라미터를 넣어서 super() 메서드를 호출해야 한다.

💡 해결

OrderServiceConcreteProxy는 프록시로서의 기능만 하고, 부모 객체의 기능을 사용하지 않기 때문에 super(null);을 추가하여 에러를 해결한다.

추가) 빈 등록 메서드는 private이거나 final일 수 없다.

❌ 에러 발생 상황

컴포넌트를 빈에 등록하는 과정에서 @Bean 메서드를 private으로 작성하는 오타가 났었다.

🤔 에러가 발생한 이유

  • 스프링은 @Configuration 클래스의 CGLIB 프록시를 생성하고, @Bean 메서드의 호출을 가로채 하나의 Object만 반환하도록 한다.
  • 이때 CGLIB는 상속을 기반으로 프록시를 생성한다.
  • 그런데 private이나 final은 상속이 불가능(=오버라이딩 불가능)하기 때문에, @Bean 메서드에 사용할 수 없다.

💡 해결

public으로 수정한다.

0개의 댓글