≣ 목차
프록시란?
프록시는 두 가지 개념으로 존재합니다. 하나는 객체 안에서 사용되는 프록시이고, 다른 하나는 웹 서버에서 사용되는 프록시입니다. 세부적인 내용은 다르지만, 근본적으로 대리자 역할은 동일합니다. 즉, 프록시는 어떤 대상을 대신해서 작업을 수행하는 역할을 한다는 점에서 공통점이 있습니다.
- 프록시 객체: 실제 객체에 대한 대리자로서 클라이언트의 요청을 대신 처리합니다.
- 웹 서버 프록시: 네트워크 요청을 중계하여 보안, 캐싱, 접근 제어 등의 기능을 제공합니다.
프록시 객체의 역할
프록시 객체는 실제 객체에 대한 대리자로서, 클라이언트의 요청을 대신 처리합니다. 중요한 점은 클라이언트가 서버에게 요청을 한 것인지, 프록시에게 요청을 한 것인지조차 몰라야 한다는 것입니다.
프록시를 사용한 디자인 패턴
프록시를 사용한 디자인 패턴은 의도(intent)에 따라 두 가지로 구분됩니다.
- 프록시 패턴: 주로 접근 제어를 목적으로 하며, 접근 제어, 캐싱, 지연 로딩 등의 기능을 제공
- 데코레이터 패턴: 새로운 기능을 추가하는 것을 목적으로 하며, 기존 객체에 추가적인 기능을 동적으로 부여
프록시 객체의 조건
앞에서 프록시 개념에서 중요하게 설명했던 클라이언트가 서버에게 요청을 한 것인지, 프록시에게 요청을 한 것인지 모르게 하려면 몇 가지 조건을 만족해야 합니다.
- 인터페이스 일치: 프록시 객체는 실제 객체와 동일한 인터페이스를 구현해야 합니다. 그러면 클라이언트는 프록시 객체를 실제 객체처럼 사용하며, 호출되는 대상이 프록시인지 실제 객체인지 알 수 없습니다.
- 대상 객체의 참조: 프록시 객체는 실제 객체에 대한 참조를 가지고 있어야 하며, 이를 통해 실제 객체의 메서드를 호출하거나 접근할 수 있습니다. 프록시는 이 참조를 활용해 필요한 경우 실제 객체의 기능을 대신합니다.
프록시 패턴 예제 - 캐싱 예제
간단하게 프록시 패턴을 적용하는 코드를 만들어보겠습니다!!
클래스 의존 관계
객체 의존 관계
위 사진들은 프록시 패턴을 적용한 클래스와 객체 간의 관계를 보여줍니다. 프록시 객체 조건이 되는 프록시 객체와 원래 객체는 같은 인터페이스를 사용하며, 클라이언트가 요청을 하면 프록시 객체가 실제 객체의 메소드를 대신 호출합니다.
공통 인터페이스
public interface Subject {
String operation();
}
실제 구현체
@Slf4j
public class RealSubject implements Subject {
@Override
public String operation() {
log.info("실제 객체(RealSubject) 호출");
sleep(1000);
return "data";
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
실제 객체로 operation() 메서드를 호출하면 1초간 대기 후 "data"를 반환하며, 로깅을 통해 메서드 호출을 기록합니다.
클라이언트
public class ProxyPatternClient {
private Subject subject;
public ProxyPatternClient(Subject subject) {
this.subject = subject;
}
public void execute() {
subject.operation();
}
}
공통 인터페이스인 Subject를 주입받아 operation()을 실행합니다. Subject의 operation()메소드를 호출하여 실제 객체와 프록시 객체의 차이를 인식하지 못하게 메서드를 호출할 수 있습니다.
프록시 객체(구현체)
@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;
}
}
프록시 객체로써 첫 호출에는 실제 객체의 메소드를 호출하고 이후에는 프록시 객체의 메서드를 실행해 저장된 값을 반환합니다.
프록시 패턴 사용
public class ProxyPatternTest {
@Test
void noProxyTest() {
// 1. 실제 구현체 생성
RealSubject realSubject = new RealSubject();
// 2. 실제 객체를 클라이언트에 주입하여 실제 객체를 사용
ProxyPatternClient client = new ProxyPatternClient(realSubject);
// 3. 클라이언트는 실제 객체를 바로 호출
client.execute();
client.execute();
client.execute();
// 실제 객체를 직접 호출하기 때문에, 각 호출마다 1초의 지연이 발생
}
@Test
void cacheProxyTest() {
// 1. 실제 구현체 생성
Subject realSubject = new RealSubject();
// 2. 실제 객체를 주입받아 프록시 객체를 생성 !!이때 실제 객체의 참조값을 target에 저장
Subject cacheProxy = new CacheProxy(realSubject);
// 3. 클라이어언트에 프록시 객체 주입
ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
// 4. 클라이언트는 실제 객체의 참조 값을 받은 프록시 객체를 호출
client.execute();
client.execute();
client.execute();
// 첫 번째 호출 시 프록시 객체는 실제 객체의 메서드를 호출하고 해당 값을 cacheValue에 저장
// 이후 호출에서는 프록시 객체의 저장된 캐싱 된 값을 반환
}
}
해당 로직에서는 캐시를 갱신하거나 무효화하는 로직이 없습니다.!!!
데코레이터 패턴 예제 - 부가 기능 추가
이번 예제에서는 두 가지 데코레이터를 사용하여 메시지 포맷팅과 실행 시간을 측정하는 기능을 추가해보겠습니다.
공통 인터페이스
public interface Component {
String operation();
}
실제 구현체
@Slf4j
public class RealComponent implements Component {
@Override
public String operation() {
log.info("RealComponent 실행");
return "ok";
}
}
RealComponent는 Component의 실제 구현체입니다.
클라이언트
@Slf4j
public class DecoratorPatternClient {
private Component component;
public DecoratorPatternClient(Component component) {
this.component = component;
}
public void execute() {
String result = component.operation();
log.info("result={}", result);
}
}
데코레이터 패턴 객체 - 부가 기능 추가
@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;
}
}
@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;
}
}
- MessageDecorator는 문자열을 꾸미는 데코레이터로, operation() 메서드가 호출되면 기존 결과에 별표(*****)를 추가하여 반환합니다.
- TimeDecorator는 operation()의 실행 시간을 측정하는 데코레이터로, 시작 시간과 종료 시간을 기록하고 결과를 로그에 출력합니다.
데코레이터 패턴 사용
@Slf4j
public class DecoratorPatternTest {
// 데코레이터 패턴 적용 전
@Test
void noDecorator() {
Component realComponent = new RealComponent();
DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
client.execute();
}
// 데코레이터 패턴 적용 후
@Test
void decorator() {
Component realComponent = new RealComponent();
Component messageDecorator = new MessageDecorator(realComponent);
Component timeDecorator = new TimeDecorator(messageDecorator);
DecoratorPatternClient client = new DecoratorPatternClient(timeDecorator);
client.execute();
}
}
MessageDecorator는 메시지에 포맷팅을 추가하고, TimeDecorator는 메서드 실행 시간을 측정합니다. 이렇게 여러 데코레이터를 서로 연결하여 체인(chain)을 형성할 수 있습니다.(프록시 패턴도 가능)
실행 순서
Client → TimeDecorator
TimeDecorator → MessageDecorator
MessageDecorator → RealComponent
실행이 끝나면 결과가 다시 역순으로 돌아옵니다
RealComponent → MessageDecorator (결과: "ok" → "*****ok*****")
MessageDecorator → TimeDecorator
TimeDecorator → Client (실행 시간 기록 후 결과 반환)
프록시, 데코레이터 패턴의 차이점
두 패턴의 동작 원리가 비슷해 차이점의 대해 의문점을 가질 수 있지만, 그 차이는 사용 목적에 있습니다. 프록시 패턴은 실제 객체의 반환 값을 cacheValue에 저장하여, 이후의 호출에서는 저장된 값을 반환함으로써 접근을 제어의 초점을 맞춥니다. 반면, 데코레이터 패턴은 기존 객체의 기능에 부가적인 기능을 추가하여 원래 기능을 확장하는 데 중점을 둡니다.
참고
스프링 핵심 원리 고급편 - 김영한
'JAVA > JAVA' 카테고리의 다른 글
JDK 동적 프록시와 CGLIB (0) | 2024.09.18 |
---|---|
자바 리플렉션(Reflection) 간단 정리 (1) | 2024.09.17 |
전략(Strategy) 패턴 정리 (1) | 2024.09.11 |
템플릿 메소드(Template Method) 패턴 정리 (4) | 2024.09.10 |
[Java] 쓰레드 로컬(ThreadLocal) (1) | 2024.09.08 |