스프링 프레임워크(Spring Framework) 톺아보기 - 프록시(프록시 패턴과 데코레이터 패턴)

Janek·2023년 2월 21일
0

Spring 톺아보기

목록 보기
8/10
post-thumbnail

해당 포스팅은 인프런에서 제공하는 김영한 님의 '스프링 핵심원리 고급편'을 수강한 후 정리한 글입니다. 유료 강의를 정리한 내용이기에 제공되는 예제나 몇몇 내용들은 제외하였고, 정리한 내용을 바탕으로 글 작성자인 저의 언어로 다시 작성한 글이기에 서술이 부족하거나 잘못된 내용이 있을 수 있습니다. 그렇기에 해당 글은 개념에 대한 참고 정도만 해주시고, 강의를 통해 학습하시기를 추천합니다.

프록시

클라이언트와 서버의 관계에서 클라이언트는 서버를 직접 호출하고, 처리 결과를 직접 받는 직접 호출이 일반적이다. 그러나 클라이언트에서 서버에 직접 요청하지 않고 어떤 대리자(Proxy)를 통해서 간접적으로 할 수도 있다.

대리자를 통해 호출할 경우 대리자가 캐싱과 같은 접근 제어부가 기능을 수행할 수 있다. 또한 대리자를 통해 다른 대리자를 호출하는 것도 가능하다.

중요한 것은 클라이언트는 자신이 보낸 요청이 대리자에게 보낸 것인지, 서버에 보낸 것인지 몰라야 한다는 것이다. 즉 서버와 프록시는 같은 객체를 사용해야 하며, 서버 객체를 프록시 객체로 교체해도 클라이언트 코드를 변경하지 않고 동작이 가능해야 한다.


위의 그림은 프록시의 개념을 도표화 한 것이다. 이를 코드로 구현한다면 아래와 같다.

public class Client {
	
    pivate Subject subject;
    
    public Client(Subject subject) {
    	this.subject = subject;
    }
    
    public void execute() {
    	subject.doSomething();
    }
    
}

public interface Subject {
	void doSomething();
}

public class RealSubject implements Subject {
	@Override
    public void doSomething() {
    	...
    }
}

public class Proxy implements Subject {

	@Object
    public void doSomething() {
        somethingElse();
        ...
    }
    
    private void somethingElse() {
    	try {
        	Thread.sleep(millis);
        } catch (InterruptedException e) {
        	e.printStackTrace();
        }
    }

}

클라이언트(Client)는 서버 인터페이스(Subject)에만 의존하며, 해당 인터페이스를 서버(RealSubject)와 프록시(Proxy)가 같이 사용한다. 런타임에 클라이언트 객체에 DI를 사용해서 서버 객체가 아닌 프록시 객체로 의존관계를 변경해도 클라이언트 코드를 전혀 변경하지 않을 수 있으며, 클라이언트는 인터페이스만을 의존하기 때문에 변경 사실 조차 알 수 없다.

프록시의 주요 기능

프록시를 통해 할 수 있는 일은 접근 제어부가 기능 추가로 구분할 수 있다. 이를 상세하게 보면 아래와 같다.

  • 접근 제어
    • 권한에 따른 접근 차단
    • 캐싱
    • 지연 로딩
  • 부가 기능 추가
    • 기존 서버가 제공하는 기능에 추가적인 기능 수행
    • 요청 값, 응답 값 변형
    • 로깅

GOF 디자인 패턴에서는 이 둘을 의도에 따라 접근 제어가 목적이면 프록시 패턴, 새로운 기능 추가가 목적이면 데코레이터 패턴으로 구분한다.

구체 클래스 기반 프록시

Java의 다형성은 인터페이스를 구현하든 클래스를 상속하든 상위 타입만 맞으면 적용된다. 그렇기에 인터페이스가 아닌 구체 클래스를 사용해서도 프록시를 생성할 수 있다.

public class Client {
	
    pivate Subject subject;
    
    public Client(Subject subject) {
    	this.subject = subject;
    }
    
    public void execute() {
    	subject.doSomething();
    }
    
}

public class Subject {
	public void doSomething() {
    	...
    }
}

public class RealSubject extends Subject {
	@Override
    public void doSomething() {
    	...
    }
}

public class Proxy extends Subject {

	@Object
    public void doSomething() {
        somethingElse();
        ...
    }
    
    private void somethingElse() {
    	try {
        	Thread.sleep(millis);
        } catch (InterruptedException e) {
        	e.printStackTrace();
        }
    }

}

구현이 아닌 상속이라는 것 말고는 기존 인터페이스 기반 프록시와 차이가 없는 것을 확인할 수 있다. 다만 Java의 기본 문법으로 인해 클래스를 생성할 때마다 항상 super()를 통해 부모 클래스의 생성자를 호출해야 한다는 점과 final 키워드가 붙은 클래스나 메서드는 상속과 오버라이딩이 불가능하다는 단점이 있다.

이렇듯 인터페이스 기반의 프록시는 상속이라는 제약에서 자유롭고, 역할과 구현을 명확하게 나눌 수 있어 보다 좋은 선택이 된다.

profile
만들고 나누며, 세상을 이롭게 하고 싶습니다.

0개의 댓글