본문 바로가기
Spring/Advanced

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

by JHyun0302 2023. 8. 14.
728x90

요구사항

  • 원본 코드를 전혀 수정하지 않고, 로그 추적기를 적용해라.
  • 특정 메서드는 로그를 출력하지 않는 기능
  • 다음과 같은 다양한 케이스에 적용할 수 있어야 한다.
            v1 - 인터페이스가 있는 구현 클래스에 적용
            
    v2 - 인터페이스가 없는 구체 클래스에 적용
            
    v3 - 컴포넌트 스캔 대상에 기능 적용

 

 

해결 방안 : 프록시(Proxy)

 

 

 

 

 

 

 

 


프록시, 프록시 패턴, 데코레이터 패턴 - 소개

 

 

 

 

대리자 프록시(Proxy) 도입

 

 

 

 

★ 대리자 프록시 도입

  1. 접근 제어, 캐싱
  2. 부가 기능 추가
  3. 프록시 체인

 

 

 

 

 

 

 

 

 

 

서버와 프록시가 같은 인터페이스 사용

 

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

 

 

 

 

 

 

 

 

 

★ 런타임(애플리케이션 실행 시점)에 클라이언트 객체에 DI를 사용해서 Client -> Server 에서 Client -> Proxy 로 객체 의존관계를 변경해도 클라이언트 코드를 전혀 변경하지 않아도 된다.

 

 

 

 

 

 

 

 

프록시의 주요 기능

1. 접근 제어

       권한에 따른 접근 차단 캐싱
       지연 로딩

 

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

           ex) 요청 값이나, 응답 값을 중간에 변형한다.
           ex) 실행 시간을 측정해서 추가 로그를 남긴다.

 

 

 

 

[GOF] : 둘 다 프록시 패턴 사용하지만 의도가 다르다!

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

 

 

 

 

 

 

 

 

 


프록시 패턴 - 예제 코드1(적용 전)

 

 

 

 

 

 

 

 

RealSubject

@Slf4j
public class RealSubject implements Subject {
    @Override
    public String operation() {
        log.info("실제 객체 호출"); sleep(1000);
        return "data";
    }
    
    private void sleep(int millis) {
        try {
        	Thread.sleep(millis);
        } catch (InterruptedException e) {
        	e.printStackTrace();
        }
    } 
}

 

 

 

@Test
void noProxyTest() {
    RealSubject realSubject = new RealSubject();
    ProxyPatternClient client = new ProxyPatternClient(realSubject);
    client.execute();
    client.execute();
    client.execute();
}

 

 

 

 


프록시 패턴 - 예제 코드2(적용 후)

 

 

 

 

 

 

 

 

 

 

 

CacheProxy

@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;
    }
}

 

 

 

 

@Test
void cacheProxyTest() {
    Subject realSubject = new RealSubject();
    Subject cacheProxy = new CacheProxy(realSubject);
    ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
    client.execute();
    client.execute();
    client.execute();
}

 

 

 

 

 

 

★ 정리

→ 프록시 패턴의 핵심은 RealSubject 코드와 클라이언트 코드를 전혀 변경하지 않고, 프록시를 도입해서 접근 제어를 했다는 점

 

 

 

 

 

 

 

 


데코레이터 패턴 - 예제 코드1 (적용 전)

 

 

 

 

 

 

 

 

RealComponent

@Slf4j
public class RealComponent implements Component {
    @Override
    public String operation() {
    	log.info("RealComponent 실행");
    	return "data";
    }
}

 

 

 

@Test
void noDecorator() {
    Component realComponent = new RealComponent();
    DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
    client.execute();
}

 

 

 

 

 

 


데코레이터 패턴 - 예제 코드2 (부가 기능 추가)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

MessageDecorator

@Slf4j
public class MessageDecorator implements Component {
    
    private Component component;
    
    public MessageDecorator(Component component) {
    	this.component = component;
    }
    
    @Override
    public String operation() {
        log.info("MessageDecorator 실행");
        String result = component.operation();
        String decoResult = "*****" + result + "*****"; 
        log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후={}", result, decoResult);
        return decoResult;
    }
}

 

 

 

 

 

 

@Test
void decorator1() {
    Component realComponent = new RealComponent();
    Component messageDecorator = new MessageDecorator(realComponent);
    DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
    client.execute();
}

 

 

 

 

 

 

 


데코레이터 패턴 - 예제 코드3 (프록시 체인)

 

 

 

실행 시간을 측정하는 데코레이터

 

 

 

 

 

 

 

 

 

TimeDecorator

@Slf4j
public class TimeDecorator implements Component {
    
    private Component component;
    
    public TimeDecorator(Component component) {
    	this.component = component;
    }
    
    @Override
    public String operation() {
        log.info("TimeDecorator 실행");
        long startTime = System.currentTimeMillis();
        
        String result = component.operation();
        
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime; 
        log.info("TimeDecorator 종료 resultTime={}ms", resultTime); 
        return result;
    } 
}

 

 

 

 

 

@Test
void decorator2() {
    Component realComponent = new RealComponent();
    Component messageDecorator = new MessageDecorator(realComponent);
    Component timeDecorator = new TimeDecorator(messageDecorator);
    DecoratorPatternClient client = new DecoratorPatternClient(timeDecorator);
    client.execute();
}

 

 

 

 

 

 

 

 


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

 

 

GOF 데코레이터 패턴

 

  • Decorator 기능에 일부 중복이 있다.
  • 꾸며주는 역할을 하는 Decorator 들은 스스로 존재할 수 없다.
  • 내부에 호출 대상인 component 를 가지고 있어야 한다.
  • component 를 항상 호출해야 한다. 

 

 

 

★ 의도(intent)

프록시 패턴의 의도: 다른 개체에 대한 접근을 제어하기 위해 대리자를 제공
데코레이터 패턴의 의도
: 객체에 추가 책임(기능)을 동적으로 추가하고, 기능 확장을 위한 유연한 대안 제공

 

 

 

 

 

 

 

 


인터페이스 기반 프록시 - 적용

 

 

 

인터페이스 기반 프록시 의존 관계 추가

 

 

인터페이스 기반 프록시 런타임 객체 의존 관계

 

 

 

InterfaceProxyConfig

@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);
    }
}

 

프록시를 생성하고 프록시를 실제 스프링 빈 대신 등록한다. 실제 객체는 스프링 빈으로 등록하지 않는다.

 

 

 

 

 

프록시 적용 후 스프링 컨테이너

 

  • 실제 객체는 스프링 컨테이너와는 상관이 없다. 실제 객체는 프록시 객체를 통해서 참조될 뿐이다.
  • 프록시 객체는 스프링 컨테이너가 관리하고 자바 힙 메모리에도 올라간다.
  • 반면에 실제 객체는 자바 힙 메모리에는 올라가지만 스프링 컨테이너가 관리하지는 않는다.

 

 

 

 

 

 

 

 


구체 클래스 기반 프록시 -  예시 + 적용

 

 

클래스 의존 관계 - 프록시 도입

 

 

 

 

런타임 객체 의존 관계 - 프록시 도입

 

 

 

 

※ 참고 : 클래스 기반 프록시의 단점

  • super(null) : 자바 기본 문법에 의해 자식 클래스를 생성할 때는 항상 super() 로 부모 클래스의 생성자를 호출해야 한다. 이 부분을 생략하면 기본 생성자가 호출된다. 그런데 부모 클래스인 OrderServiceV2 는 기본 생성자가 없고, 생성자에서 파라미터 1개를 필수로 받는다. 따라서 파라미터를 넣어서 super(..) 를 호출해야 한다.
  • 프록시는 부모 객체의 기능을 사용하지 않기 때문에 super(null) 을 입력해도 된다.
  • 인터페이스 기반 프록시는 이런 고민을 하지 않아도 된다.

 

 

 

 

 

ConcreteProxyConfig

@Configuration
public class ConcreteProxyConfig {
    @Bean
    public OrderControllerV2 orderControllerV2(LogTrace logTrace) {
        OrderControllerV2 controllerImpl = new OrderControllerV2(orderServiceV2(logTrace));
        return new OrderControllerConcreteProxy(controllerImpl, logTrace);
    }
    
    @Bean
    public OrderServiceV2 orderServiceV2(LogTrace logTrace) {
        OrderServiceV2 serviceImpl = new OrderServiceV2(orderRepositoryV2(logTrace));
        return new OrderServiceConcreteProxy(serviceImpl, logTrace);
    }
    
    @Bean
    public OrderRepositoryV2 orderRepositoryV2(LogTrace logTrace) {
        OrderRepositoryV2 repositoryImpl = new OrderRepositoryV2();
        return new OrderRepositoryConcreteProxy(repositoryImpl, logTrace);
    }
}

 

 

 

 

 

 

 

 

 

 


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

 

 

 

 

 

  • 인터페이스 기반 프록시는 인터페이스만 같으면 모든 곳에 적용 가능
    인터페이스 기반 프록시 단점
        
    1. 인터페이스 필요
        2. 캐스팅 단점

  • 클래스 기반 프록시는 해당 클래스에만 적용할 수 있다.
    클래스 기반 프록시 단점
        1. 부모 클래스의 생성자를 호출해야 한다. (super())
        2. 클래스에 final 키워드가 붙으면 상속이 불가능
        3. 메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다.
 

 

 

 

 

 

 

 


 

 

문제 : 프록시 클래스를 쓸 때마다 각각 만들어야함.

 

해결 방안 : 동적 프록시 기술

 

 

 

반응형

'Spring > Advanced' 카테고리의 다른 글

스프링이 지원하는 프록시  (0) 2023.08.17
동적 프록시 기술  (0) 2023.08.16
템플릿 메서드 패턴과 콜백 패턴  (0) 2023.08.13
쓰레드 로컬 - ThreadLocal  (0) 2023.08.12
예제 만들기  (0) 2023.08.12