[Java] 프록시 패턴 정리

Kai·2023년 12월 27일
0

디자인 패턴

목록 보기
4/5

☕ 개요


프록시 패턴은 말 그대로 클라이언트와 서버 사이에 프록시 역할을 하는 무엇인가를 두고 기능을 수행하는 것을 의미한다.

클라이언트의 입장에서는 프록시가 있는지 없는지도 모르고, 그냥 서버에 무언가를 요청하게 되고, 프록시가 이 요청에 대해서 제어를 하게 된다.
즉, 프록시가 클라이언트의 서버에 대한 접근을 제어하게 된다.

예시 코드로 자세히 알아보자.


🧐 프록시 패턴 예시


1. 가정 : 캐싱 기능을 구현하자.

프록시 패턴의 대표적인 예시가 캐싱 기능인데, 우리는 지금 캐싱 기능을 구현해야하는 상황에 처했다고 가정해보도록 하겠다.

2. Server 인터페이스 만들기

public interface Server {
    String run();
}

먼저 아주 간단한 Server 인터페이스를 만들어주자.

3. 구현 클래스 만들기

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RealServer implements Server {
    @Override
    public String run() {
        log.info("서버 호출");
        return "Data";
    }
}

위에서 만든 Server 인터페이스의 구현 클래스를 만들어보자. 이게 실제 서버 역할을 할 클래스 이다.

4. 프록시 클래스 만들기

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class CacheServer implements Server {

    private final Server server;
    private String cacheValue;

    @Override
    public String run() {
        log.info("프록시 서버 호출");
        return cacheValue == null ? server.run() : cacheValue;
    }
}

Server 인터페이스를 상속해서 캐시 서버 역할을 할 클래스를 만들어주자.
run()메서드에서 return하는 부분을 보면, cacheValue가 있으면 cacheValue를 반환하고, 없는 경우에만 실제 서버에 접근해서 데이터를 가져오도록 처리하였다.

💡 여기서 중요한 점은 실제 서버 클래스와 캐시 서버 클래스 모두 Server 인터페이스를 상속해서 만드는 것이다.
이렇게 해야 클라이언트에서는 Server를 호출하도록 해서, 클라이언트 스스로가 캐시 서버를 호출할지 실제 서버를 호출할지를 결정하는 것이 아닌, 캐시 서버가 실제 서버에 대한 접근을 제어할 수 있게 되는 것이다.

5. 클라이언트 클래스 만들기

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class SomeClient {

    private final Server server;

    public void request() {
        server.run();
    }

}

서버의 기능을 호출하는 기능을 갖고 있는 클라이언트 역할의 클래스도 만들어주자.
여기까지 만들면 필요한 것들은 모두 준비가 완료된다.

6. 테스트 코드로 동작 확인하기

import hello.springaop.pattern.pureproxy.proxy.code.CacheServer;
import hello.springaop.pattern.pureproxy.proxy.code.SomeClient;
import hello.springaop.pattern.pureproxy.proxy.code.RealServer;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class ProxyPatternTest {

    @Test
    @DisplayName("프록시 미적용")
    public void noProxy() throws Exception {
        RealServer server = new RealServer();
        SomeClient client = new SomeClient(server);

        client.request();
        client.request();
        client.request();
    }

    @Test
    @DisplayName("프록시 적용")
    public void proxy() throws Exception {
        RealServer realServer = new RealServer();
        CacheServer cacheServer = new CacheServer(realServer);
        SomeClient client = new SomeClient(cacheServer);

        client.request();
        client.request();
        client.request();
    }

}

이렇게 프록시 적용 여부에 따른 서로 다른 테스트 코드 2종류를 작성해주고, 한번 실행해보자.

프록시를 적용하지 않은 코드의 결과는 당연하게도 3번의 요청 모두 실제 서버가 호출이 되었다.

반면에, 프록시가 적용된 코드는 캐싱된 값이 없는 첫 요청에 대해서만 실제 서버에 접근하고, 그 이후의 요청에 대해서는 캐싱된 결과를 반환하기 때문에 실제 서버에는 접근하지 않는 것을 확인할 수 있다.


☕ 마무리


이번 글에서는 프록시 패턴에 대해서 알아보았다.
스프링 뿐만 아니라 매우 많은 곳에서 애용되고 있는 패턴 중에 하나이다.
스프링에서는 정석적인 프록시 패턴을 사용한다기 보다는 바이트 코드를 조작하는 기술을 적용해서, 프록시를 손수 적용하지 않고 간편하게 적용할 수 있는 기능들을 제공한다.

이 기술들에 대해서는 추후에 다른 글들을 통해서 소개하도록 하겠다. ㅎㅎ

그럼 이번 글은 이만 마치도록 하겠다 😊


🙏 참고


0개의 댓글