[Spring] 프록시(feat. proxy server)란 무엇일까?

Suntory·2022년 3월 12일
2

스프링 스터디에서 나온 주제 중에 프록시란 용어가 자주 등장했다. 사실 프록시는 네트워크에서도 자주 듣던 단어인데 스프링에서도 쓰이는 모양이다. 이렇게 자주 들리는 용어라는 것은 포괄적인 의미의 추상적인 단어라고 생각해 전반적인 의미 파악부터 하고 스프링의 그것을 조사해보기로 했다.

Proxy의 사전적 의미

영영사전에 Proxy를 검색해보면 다음과 같다.

authority given to a person to act for someone else, such as by voting for them in an election, or the person who this authority is given to

대략 누군가를 대신하여 뭔가를 수행하는 권한 자체 또는 그 권한을 받은 주체를 뜻한다.
Geeksforgeeks는 어떨까?

Proxy is type of a tool or application or program or system, which helps to protect the information of its users and computers.

의미만 들어도 대충 Proxy server는 무슨 일을 할 지 추측이 된다.
클라이언트가 요청을 하면 중간에서 서버 대신 뭔가를 수행해주는 서버일 것 같다.
그럼 맞는지 알아보자.

Proxy server

프록시 서버는 client과 server 간에 주고받는 네트워크 요청과 응답 사이에 위치하는 서버이다. 여러가지 이유로 사용되는데, 캐싱 또는 사용자 보호의 이유로 쓰인다.

캐싱의 경우에는 고속 인터넷 연결이 가능한 건물 상에 프록시 서버를 설치하고, 사용자 요청이 들어오면 캐싱돼있는 페이지 중에 있는지 확인하고 없으면 요청해서 받아오거나 이전 버전이라면 업데이트해서 보여주는 역할을 한다. 초기에는 이 역할로 활용되었지만 요즘은 캐시서버를 많이 사용하지 않는 추세라서 잘 쓰이지 않다가 두 번째 이유로 쓰이고 있다.

두 번째 이유는 사용자 개인정보 보호를 위해 쓰이는데, 사용자는 직접 서버에 요청하는 것이 아니라 프록시 서버에 요청을 보낸다. 그럼 프록시 서버는 사용자의 IP address가 아닌 새 공인 아이피를 생성해서 서버에 요청을 보낸다. 그럼 서버는 사용자의 IP를 알지 못한 채 프록시 서버의 요청에 응답해줄 뿐이다. 프록시 서버는 이 응답을 받아 원래 요청을 했던 client 의 IP로 응답을 전달해준다. 즉, 사용자와 서버 사이에서 중계를 해주는 것이다.

proxy server의 장점

  • IP 주소를 가릴 수 있다.
  • 로딩에 걸리는 시간을 줄일 수 있을 지도 모른다.
  • 제한된 웹사이트를 우회하여 접속할 수 있다.
  • 캐싱을 이용하여 시스템의 성능을 올린다.

proxy server의 단점

  • 캐싱하는 과정에서 proxy 서버 관리자에게 쉽게 개인정보가 노출될 위험이 있다.
  • TLS/SSL을 사용하면 데이터와 정보가 유출될 수 있다.

그럼 Spring의 Proxy는?

위의 Proxy의 정의와 사용 예를 보고 유추해보건대, Spring의 Proxy도 뭔가의 사이에서 뭔가를 수행해주는 역할이라고 생각할 수 있다. 어떤 것들 사이에 동작하는 지를 알기 위해서는 AOP를 먼저 공부해야할 것 같다.

프록시 패턴

프록시 객체는 원래 객체를 감싸고 있는 같은 타입의 객체이다. 프록시 객체가 원래 객체를 감싸서 client의 요청을 처리하기 하는 패턴이다. 쓰는 이유는 접근을 제어하고 싶거나, 부가 기능을 추가하고 싶을 때 주로 사용한다.

아래와 같은 코드를 보자.

public interface Image {
	void displayImage();
}
public class RealImage implements Image {

    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName);
    }

    @Override
    public void displayImage() {
        System.out.println("Displaying " + fileName);
    
    }
}
public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void displayImage() {
        if (realImage == null) {
            realImage = new Real_Image(fileName);
        }
        realImage.displayImage();
    }
}
public class Main {
    public static void main(String[] args) {
        Image image1 = new Proxy_Image("test1.png");
        Image image2 = new Proxy_Image("test2.png");

        image1.displayImage();
        System.out.println();
        image2.displayImage();
    }
}

상대적으로 오래 걸리는 이미지 로딩 전에 로딩 텍스트를 먼저 출력할 수 있도록 프록시 객체가 흐름을 제어하고 있는 모습이다.

AOP란?

AOP는 Aspect Oriented Programming이다. 관점 지향 프로그래밍으로 번역되며 중점적인 역할은 핵심 기능들 사이에 공유하는 관심사항을 삽입하여 코드의 중복을 줄이는 방식의 패러다임입니다.

간단히 예를 들면, 우리가 만든 프로그램에 모든 메소드 호출 시마다 로그를 찍고 싶습니다. 사실 로그를 찍는 것은 핵심 모듈이 아니기에 개발한 코드에는 로그를 찍는 기능은 개발이 되어 있지 않을 것입니다. 그럼 무수한 메소드에 직접 로그를 입력하는 함수를 짜야 하는데, 매우 고통스러운 일입니다.

또한 객체지향의 관점에서 핵심 로직이 이런 부수적인 기능을 책임져야 한다는 것은 관심사의 분리에 어긋난다고 생각됩니다. 그래서 스프링은 AOP라는 이름으로 부수적인 기능을 책임져줄 기능을 만들었다고 생각합니다. 이는 의존관계 설정을 도맡아 해주는 IoC 기법인 DI와 유사해보입니다.

AOP는 핵심 모듈 사이에 필요한 기능을 삽입하여 적절한 타이밍에 호출되도록 해주는 기능입니다.
이를 구현할 때 스프링은 대상 빈을 프록시로 감싸는 방법을 활용합니다.

프록시

어떠한 클래스(빈)가 AOP 대상이면 원본 클래스 대신 프록시가 감싸진 클래스가 자동으로 만들어져 프록시 클래스가 빈에 등록이 됩니다. 이렇게 빈에 등록된 프록시 클래스는 원본 클래스가 호출되면 자동으로 바꿔서 사용해줍니다. 이 방식을 통해 자연스럽게 OCP를 만족합니다.

관련 용어

  • Advice: Joinpoint에서 실행되어야 하는 코드, 공통 관심사, 횡단 관점에 해당한다. 언제 어떤 부수적인 기능을 핵심 로직에 적용할지를 담고 있다.
  • Target: 핵심 로직을 담고 있는 객체에 해당한다. Advice의 적용 대상이며 비지니스 로직을 수행하는 클래스나 프록시 객체가 될 수도 있다.
  • Joinpoint: Advice가 적용가능한 지점을 의미하며, 메소드를 호출하는 시점, 예외가 발생하는 시점과 같이 애플리케이션을 실행할 때 특정 작업이 실행되는 시점을 의미한다. 이 때, 프록시를 활용했으므로 객체 내부의 호출 없이 이뤄지는 필드 수정 등의 joinpoint는 활용할 수 없다.

  • Pointcut: 가능한 joinpoint 중 실제 Advice가 적용된 joinpoint를 의미한다. 스프링에서는 AspectJ 등을 통해 Target과 Advice가 결합 될 때 둘 사이의 결합 규칙(Advice가 실행된 Target의 특정 메소드 등을 지정)을 정의할 수 있다.

  • Aspect: Advice과 Pointcut을 합쳐서 하나의 Aspect라고 한다. AOP에서 말하는 Aspect는 한마디로 어떤 관심사를 어떤 지점에 넣을 것인지를 말한다고 보면 된다.

  • Weaving: AOP에서 joinpoint들을 Advice로 감싸는 과정이다. 이 작업을 도와주는 것이 AspectJ등의 AOP 툴이 하는 역할이다.

Weaving의 종류

이 joinpoint를 advice로 감싸는 weaving에는 그 시점에 따라 종류가 나뉜다.
크게 말하면 컴파일 타임, 클래스 로드 타임, 런타임의 3가지이다.

  1. Compile-time Weaving
    AspectJ에 내장된 AJC(AspectJ Compiler)를 통해 java 파일을 컴파일하면서 바이트 코드를 조작한다. 즉, joinpoint에 해당하는 바이트코드에 Advise 코드를 직접 삽입하여 위빙을 수행하는 방식이다. 장점이라면 역시 컴파일 결과가 바로 적용되어 나오므로 성능이 가장 좋을 것이다. 다만 컴파일 과정에서 각종boilerplate code를 추가해주는 최고존엄 라이브러리 Lombok과 동시 사용 시 충돌이 발생할 과정이 매우 높다. (거의 같이 사용이 불가능하다고 한다)
  1. Load-time Weaving
    JVM의 클래스 로더가 클래스를 로딩하는 순간에 advice 코드를 삽입하는 방식이다. 컴파일 시간은 상대적으로 CTW보다 짧지만 오브젝트가 메모리에 올라가는 과정에서 위빙이 일어나기 때문에 런타임 시에는 CTW가 느리다.

  2. Run-time Weaving
    Spring의 AOP가 채택한 방식으로, 스프링은 Target에 해당하는 객체를 빈 컨테이너에 등록할 때 프록시 클래스를 만들어 감싼 형태로 등록하게 된다. 빈이 생성된 이후 빈 후처리기를 통해 해당 빈이 프록시 적용 대상인지 파악하고 적용 대상이면 프록시 생성기를 통해 프록시를 생성한다. 그리고 어드바이저를 연결하여 컨테이너에 등록한다. 실제 오브젝트의 코드에는 전혀 변형이 없으며, Pointcut에 해당하는 부분에 프록시가 끼어들어 advice를 수행하게 된다.

참고사이트

AOP(관점 지향 프로그래밍)
AOP(4) - AspectJ
Load Time Weaving 적용기 - Spring LTW
프록시 패턴

profile
천천히, 하지만 꾸준히 그리고 열심히

2개의 댓글

comment-user-thumbnail
2022년 3월 13일

와우... 대단하시네요 산 교수님 👍 잘 보고 갑니다~

1개의 답글