-
Spring Core - 프록시 패턴(예제1,예제2,Cache Proxy)FrameWork/Spring&Spring-boot 2024. 3. 8. 13:43
프록시 패턴 - 예제 코드1
테스트 코드에 Lombok 적용하기
테스트코드에 Lombok을 사용하려면 build.gradle에 테스트 lombok을 사용할 수 있도록 의존관계를 추가 해야 한다.
build.gradle에 추가
dependencies { //테스트에서 lombok 사용 testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' }
이렇게 해야 테스트코드에서 @Slfj4 같은 애노테이션이 작동한다.
프록시 패턴 - 예제 코드 작성
프록시 패턴을 이해하기 위해 예제 코드를 작성해보자. 먼저 프록시 패턴을 도입하기 전 코드를 아주 단순하게 만들어 보자
Subject
public interface Subject { String operation(); }
예제에서 Subject 인터페이스는 단순히 operation()메서드 하나만 가지고 있다.
RealSubject
@Slf4j public class RealSubject implements Subject{ @Override public String operation() { log.info("실제 객체 호출"); sleep(1000); return "data"; } private void sleep(int mills) { try { Thread.sleep(mills); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
RealSubject는 Subject 인터페이스를 구현했다. operation()은 데이터 조회를 시뮬레이션 하기 위해 1초 쉬도록했다. 예를 들어서 데이터를 DB나 외부에서 조회하는 1초가 거린다고 생각하면 된다. 호출할 때 마다 시스템에 큰 부하를 주는 데이터 조회라고 가정하자.
ProxyPatternTest
public class ProxyPatterTest { @Test void noProxyTest(){ RealSubject realSubject = new RealSubject(); ProxyPatternClient client = new ProxyPatternClient(realSubject); client.execute(); client.execute(); client.execute(); } }
테스트 코드에서는 client.execute()를 3번 호출한다. 데이터를 조회하는데 1초가 소모되므로 총 3초의 시간이 걸린다.
실행 결과
client.execute()를 3번 호출하면 다음과같이 처리된다.
- client → realSubject 를 호출해서 값을 조회한다. (1초)
- clien → realSubject 를 호출해서 값을 조회한다. (1초)
- client → realSubject 를 호출해서 값을 조회한다. (1초)
그런데 이 데이터가 한번 조회하면 변하지 않는 데이터라면 어딘가에 보관해두고 이미 조회한 데이터를 사용하는 것이 성능상 좋다. 이런 것을 캐시라고 한다.
프록시 패턴의 주요 기능은 접근 제어이고, 캐시도 접근 자체를 제어하는 기능 중 하나이다.
이미 개발된 로직을 전혀 수정하지 않고, 프록시 객체를 통해서 캐시를 적용해보자.
프록시 패턴 - 예제 코드2
프록시 패턴을 적용하자
CacheProxy
Slf4j public class CacheProxy implements Subject{ private Subject target; //proxy입장에 호출해야 되는 대상을 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; } }
앞서 설명한 것 처럼 프록시도 실제 객체(realSubject)와 그 모양이 같아야 하기 때문에 Subject 인터페이스 구현해야 한다.
- private Subject target : 클라이언트가 프록시를 호출하면 프록시가 최종적으로 실제 객체를 호출해야 한다. 따라서 CacheProxy 클래스 내부에 실제 객체의 참조를 가지고 있어야 한다. 이렇게 프록시가 호출하는 대상을 target이라 한다.
- opearation() : 구현한 코드를 보면 cacheValue에 값이 없으면 실제 객체(target)를 호출해서 값을 구한다. 그리고 구한 값을 cacheValue에 저장하고 반환한다. 만약 cacheValue에 값이 있으면 실제 객체를 전혀 호출하지 않고, 캐시 값을 그대로 반환한다. 따라서 처음 조회 이후에는 캐시(cacheValue)에서 매우 빠르게 데이터를 조회할 수 있다.
ProxyPatternTest
package com.core.springproxy.pureproxy.proxy; import com.core.springproxy.pureproxy.proxy.code.CacheProxy; import com.core.springproxy.pureproxy.proxy.code.ProxyPatternClient; import com.core.springproxy.pureproxy.proxy.code.RealSubject; import com.core.springproxy.pureproxy.proxy.code.Subject; import org.junit.jupiter.api.Test; public class ProxyPatternTest { @Test void noProxyTest(){ RealSubject realSubject = new RealSubject(); ProxyPatternClient client = new ProxyPatternClient(realSubject); client.execute(); client.execute(); client.execute(); } @Test void cacheProxyTest(){ Subject realSubject =new RealSubject(); Subject cacheProxy = new CacheProxy(realSubject); ProxyPatternClient client = new ProxyPatternClient(cacheProxy); client.execute(); //메모리에서 바로 리턴 client.execute(); client.execute(); } }
cacheProxyTest()
realSubject와 cacheProxy를 생성하고 둘을 연결한다. 결과적으로 cacheProxy가 realSubject를 참조하는 런타임 객체 의존관계가 완성된다. 그리고 마지막으로 client에 realSubject가 아닌 cacheProxy를 주입한다. 이 과정을 통해서
client → cacheProxy → realSubject 런타임 객체 의존 관계가 완성된다.
cacheProxyTest()는 client.execute()을 총 3번 호출한다. 이번에는 클라이언트가 실제 realSubject를 호출하는 것이 아니라 cacheProxy를 호출하게 된다.
실행 결과
client.execute()을 3번 호출하면 다음과 같이 처리된다.
- client의 cacheProxy호출 → cacheProxy에 캐시 값이 없다. → realSubject를 호출, 결과를 캐시에 저장(1초)
- client의 cacheProxy호출 → cacheProxy에 캐시 값이 있다. → chacheProxy에서 즉시 반환(0초)
- client의 cacheProxy호출 → cacheProxy에 캐시 값이 있다. → chacheProxy에서 즉시 반환(0초)
결과적으로 캐시 프록시를 도입하기 전에는 3초가 걸렸지만, 캐시 프록시 도입 이후에는 최초에 한번만 1초가 걸리고, 이후에는 거의 즉시 반환한다.(메모리에 저장된 데이터 반환)
정리
프록시 패턴의 핵심은 RealSubject 코드와 클라이언트 코드를 전혀 변경하지 않고, 프록시를 도입해서 접근 제어를 했다는 점이다. 그리고 클라이언트 코드의 변경 없이 자유롭게 프록시를 넣고 뺄 수 있다. 실제 클라이언트 입장에서는 프록시 객체가 주입되었는지, 실제 객체가 주입되었는지 알지 못한다.
[출처 - 스프링 핵심원리-고급편, 저 김영한]
스프링 핵심 원리 - 고급편 강의 - 인프런
스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 📢 수강
www.inflearn.com
'FrameWork > Spring&Spring-boot' 카테고리의 다른 글
Spring Core - 인터페이스 기반 프록시(V1) (0) 2024.03.11 Spring Core - 데코레이터 패턴 (예제1, 예제2, 예제3) (0) 2024.03.11 Spring Core - 프록시 패턴, 데코레이터 패턴 - 소개(요구사항 추가) (0) 2024.03.08 Spring Core - 프록시 패턴(예제 프로젝트) (0) 2024.03.08 Spring Core - 템플릿 콜백 패턴(시작, 적용, 정리) (0) 2024.03.07