Proxy란?
Client(요청자) 와 Server(제공자) 사이에 Proxy(대리인)을 의미한다.
이 Client와 Server는 굉장히 넓은 의미가 될 수 있는데,
여기서는 웹 브라우저 - 웹 서버의 개념이 아닌, 객체간의 호출자와 호출 당한 객체를 의미한다.
Proxy 도입 장점
우선, 직접 호출을 하지 않고 대리자를 하나 세웠다는 것 만으로 많은 것을 할 수 있다.
(이미 Spring에서 Proxy를 많이 사용한다.)
- 대상에게 접근을 제어하거나, 캐싱을 할 수 있다.
- 권한에 대한 접근 차단
- 캐싱
- 지연로딩 (client가 프록시를 가지고 놀다가 실제 요청이 들어오면 실 객체를 제공한다)
- 대상에게 추가적인 기능을 제공할 수 있다.
- 원래 서버가 제공하는 기능 + 알파
- ex) 요청값이나 응답 값을 중간에 변형
- ex) 실행 시간을 측정해 로그 남기기
- 대상에게 접근할 때, 대리자는 접근을 위임하여 2차 대리인을 세울 수 있다. (3,4,5차는 자유. 프록시 체인이라고 한다)
Proxy 조건
객체를 Proxy로 사용하기 위해서는 조건이 필요하다.
Interface를 같이 구현하고 있어야 한다. (대체 가능)
Client는 서버에 Interface만 의존하고, 구현체와 Proxy는 그 Interface를 사용하고 있다.
따라서 DI를 통해 대체 주입이 가능하다.
ex) Client - Interface - Server(impl Interface)
Client - Interface - Proxy(impl Interface) - Server(impl Interface)
Spring에서는 같은 구현체면 대체 DI가 가능하다.
그리고 대체된 기능을 그대로 수행할 수 있어야 한다. 클라이언트의 코드 수정 없이
Proxy를 사용한 디자인 패턴 2가지
프록시를 사용한 패턴은 크게 두 가지가 있다.
- Proxy Pattern
- Decorator Pattern
intent(의도)
두 디자인 패턴은 의도가 다르다.
Proxy Pattern
은 대리자를 통해 객체에 접근을 제어
하는 것이 목적이다.
Decorator Pattern
은 대리자들을 프록시체인으로 묶어 객체에 동적으로 기능을 추가
하는 것이 목적이다.
Proxy Pattern
접근을 제어하는 방식 (Cache로 동일한 데이터 return)
data를 가져올 때, 실 객체에서 계속 같은 요청을 하면 Resource가 소모되므로,
앞에 CacheProxy를 둬서 Cache에 있는지 확인하고, 없으면 실 객체에 요청을 보내고, Cache에 저장한다.
데이터 요청시 1초가 걸리는데, Cache에 있으면 0초가 걸린다.
3번 요청이 들어오면 2번은 Cache에서 가져오고, 1번은 실 객체에서 가져온다.
Proxy 도입으로 2초 절약이 된다.
CacheProxy 예제
예제 구조
- RealSubject.java(실제 작동 객체)
@Slf4j
public class RealSubject implements Subject {
//가상 데이터 조회 상황 (1초나 걸리는 과부화 상황)
@Override
public String operation() {
log.info("실제 객체 호출");
sleep(1000);
return "data";
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
- Subject.java(인터페이스)
public interface Subject {
String operation();
}
- CacheProxy.java(Proxy 객체)
@Slf4j
public class CacheProxy implements Subject {
private Subject target;
private String data;
public CacheProxy(Subject subject) {
this.target = subject;
}
@Override
public String operation() {
log.info("프록시 호출");
if (data == null) {
data = target.operation();
}
return data;
}
}
- Client.java(클라이언트)
실 데이터를 요청하는 클라이언트
public class ProxyPatternClient {
private Subject subject;
public ProxyPatternClient(Subject subject) {
this.subject = subject;
}
public void execute() {
subject.operation();
}
}
CacheProxy 실행
@Slf4j
public class ProxyPatternTest {
/**
* 실제 객체에서 변하지 않는 데이터를 계속 받는 상황.
* 프록시 패턴으로 Cache를 사용하여 접근을 제어해서, 이미 받은 데이터는 캐싱을 하자.
* 실제 객체의 데이터를 받는 시간이 1초라고 가정하자.
*/
@Test
void noProxyTest() {
RealSubject realSubject = new RealSubject();
ProxyPatternClient client = new ProxyPatternClient(realSubject);
client.execute();
client.execute();
client.execute();
}
/**
* 프록시 패턴을 사용해서, 이미 받은 데이터는 캐싱을 하자.
* 처음에는 CacheProxy가 RealSubject를 호출하고,
* 그 다음부터는 CacheProxy에 저장된 data를 호출한다.
* 그래서 1초만에 끝난다.(3초가 아닌)
*/
@Test
void cacheProxyTest() {
RealSubject realSubject = new RealSubject();
CacheProxy cacheProxy = new CacheProxy(realSubject);
ProxyPatternClient proxyPatternClient = new ProxyPatternClient(cacheProxy);
proxyPatternClient.execute();
proxyPatternClient.execute();
proxyPatternClient.execute();
}
}
Decorator Pattern
프록시로 부가기능을 추가하는 방식.
RealComponent를 실행시키는게 목표고, 그 앞에 Proxy체인으로 부가기능을 추가할 것이다.
Decorator Pattern 예제
예제 구조
- RealComponent.java(실제 작동 객체)
@Slf4j
public class RealComponent implements Component {
@Override
public String operation() {
log.info("Real Component 실행");
return "data";
}
}
Decorator들은 추상 클래스로 구현해줘서, 반복되는 코드를 묶어줬다.
- Decorator.java(Proxy 객체)
public abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public String operation() {
return component.operation();
}
}
- MessageDecorator.java
메세지 앞, 뒤로 *****를 붙여주는 기능
@Slf4j
public class MessageDecorator extends Decorator {
public MessageDecorator(Component component) {
super(component);
}
@Override
public String operation() {
log.info("MessageDecorator 실행");
//data -> ****** data ******로 추가
String resultData = super.operation();
String decoResult = "****** " + resultData + " ******";
log.info("MessageDecorator 꾸미기 적용 전={}, 적용 후={})",resultData, decoResult);
return decoResult;
}
}
- TimeDecorator.java
수행 시간을 측정하는 기능
@Slf4j
public class TimeDecorator extends Decorator {
public TimeDecorator(Component component) {
super(component);
}
@Override
public String operation() {
log.info("TimeDecorator 실행");
long startTime = System.currentTimeMillis();
String result = super.operation();
long endTime = System.currentTimeMillis();
log.info("실행 시간 : " + (endTime - startTime));
return result;
}
}
- DecoratorPatternClient.java
실제 객체를 호출하려는 client
@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);
}
}
- DecoratorPatternTest.java
@Slf4j
public class DecoratorPatternTest {
/**
* 데코레이션 패턴 사용 X
*/
@Test
void noDecorator() {
Component realComponent = new RealComponent();
DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
client.execute();
}
/**
* MessageDecorator만 붙인 상태
*/
@Test
void decorator1() {
Component realComponent = new RealComponent();
Component messageDecorator = new MessageDecorator(realComponent);
DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
client.execute();
}
/**
* Proxy 체인으로 MessageDecorator, LogDecorator를 붙인 상태
*/
@Test
void decorator2() {
Component realComponent = new RealComponent();
Component messageDecorator = new MessageDecorator(realComponent);
Component TimeDecorator = new TimeDecorator(messageDecorator);
DecoratorPatternClient client = new DecoratorPatternClient(TimeDecorator);
client.execute();
}
}
'개발언어 > JAVA' 카테고리의 다른 글
[Spring AOP] 1.JDK Dynamic Proxy/CGLIB로 인터페이스 or 클래스 기반의 프록시 객체를 쉽게 생성하는 방법 (0) | 2022.11.16 |
---|---|
[Reflection] 자바가 동적으로 Class, Method의 메타데이터를 획득하여 작동시키는 방법 (0) | 2022.11.16 |
[디자인 패턴] 템플릿 메서드 패턴, 전략 패턴 (0) | 2022.11.03 |
동시성 문제 해결(ThreadLocal) (0) | 2022.08.31 |
[모던자바인액션] 2. 스트림 정리 (0) | 2022.04.05 |