ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CS] Proxy Pattern
    Computer Science/CS 2024. 4. 9. 16:07
    728x90

    프로그래밍에서 프록시 패턴은 소프트웨어 설계 패턴이다.

     

    프록시 패턴은 대상 원본 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어하는 행동 패턴이다.

     

    객체 지향 프로그래밍에 접목해보면 클라이언트가 대상 객체를 직접 쓰는 것이 아니라 프록시(대리인)을 거쳐서 쓰는 코드 패턴이라고 보면 된다.

    따라서 대상 객체(subject)의 메소드를 직접 실행하는 것이 아닌, 대상 객체에 접근하기 전에 프록시 객체의 메서드를 접근한 후 추가적인 로직을 처리한뒤 접근하게 된다.

     

    구체적으로 인터페이스를 사용하고 실행시킬 클래스에 대한 객체가 들어갈 자리에 대리자 객체를 대신 투입한다.

    클라이언트 쪽에서 실제 실행시킬 클래스에 대한 객체를 통해  메서드를 호출하고 반환 값을 받는지, 대리자 객체를 통해 메서드를 호출하고 반환 값을 받는지 전혀 모르게 처리하는 것이다.

     

    중요한 것은 흐름제어만 할 뿐, 결과 값을 조작하거나 변경시키면 안된다.

     

    대리자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.

    -> 대리자는 실제 서비스에 대한 참조 변수를 갖는다(합성)

    -> 대리자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 값을 클라이언트에게 돌려준다.

    -> 대리자는 실제 서비스의 메서드 호출 전후에도 별도의 로직을 수행할 수도 있다

    https://refactoring.guru/design-patterns/proxy

    프록시 패턴을 구현하기 위해서 대상 객체와 프록시 객체를 하나로 묶어주는 인터페이스를 정의해야 한다.

    (1) 서비스 인터페이스를 선언합니다.  

    (2) 서비스는 비즈니스 로직을 제공하는 클래스이다.

    (3) Proxy 클래스에는 서비스 객체를 가리키는 참조 필드가 있다. 프록시는 처리를 완료한 후 요청을 서비스 객체에 전달한다. (일반적으로 프록시는 서비스 개체의 전체 수명 주기를 관리한다.)

    (4) 클라이언트는 동일한 인터페이스를 통해 서비스와 프록시 모두에서 작업해야 한다. 

     

    개체(Object)에 대한 엑세스를 제어하려는 이유는 무엇일까?

     

     

    예를 들어 프록시 패턴은 3rd-party 유튜브 통합 라이브러리에 지연 초기화 및 캐싱을 도입하는데 어떻게 도움이 되는지를 보자.

    https://refactoring.guru/design-patterns/proxy

    라이브러리에서 비디오 다운로드 클래스를 제공한다. 하지만 이는 매우 비효율적이다.

    클라이언트 애플리케이션이 동일한 동영상을 여러 번 요청하면 라이브러리는 반복해서 다운로드하게 된다.

     

    프록시 클래스는 원래 다운로더와 동일한 인터페이스를 구현하고 모든 작업을 위임한다. 그러나 다운로드한 파일을 추적하고 앱이 동일한 동영상을 여러번 요청할 때 캐시된 결과를 반환한다. 

    처음 다운로드한 파일을 캐싱하여 재사용하게 된다.

     

    간단한 코드로 구현해 보자.

    (1) 프록시 클래스와 서비스 클래스를 묶기 위한 인터페이스를 생성한다.

    public interface ServiceImpl {
        public void callService();
    }

     

    (2) 서비스 클래스를 생성한다.

    public class Service implements ServiceImpl{
    
        private String from;
        public Service(String from){
            saveFrom(from + "-> Service");
        }
    
        private void saveFrom(String from){
            this.from = from;
        }
    
        @Override
        public void callService() {
            System.out.printf("%s done!", from);
        }
    }

     

    (3) 프록시 클래스를 생성한다. 

         프록시 클래스에는 Service 인터페이스 객체를 가지고 있다.

         Service인터페이스 객체를 통해 Service클래스 객체를 생성하여 Service클래스의 메소드가 수행될 수 있도록 한다.

    public class Proxy implements ServiceImpl{
    
        private ServiceImpl service;
        private String from;
        public Proxy(String from){
            this.from = from + "-> Proxy ";
        }
        @Override
        public void callService() {
            service = new Service(from);
            service.callService();
        }
    }

     

    (4) 확인을 위한 client 클래스를 생성하여 프록시 클래스에 구현된 서비스 메소드를 호출한다. 

    public class client {
        public static void main(String[] args) {
            ServiceImpl service = new Proxy("client");
            service.callService();
        }
    }

     

    client -> Proxy -> Service done!

    위의 메시지가 콘솔에 나오는 것을 확인할 수 있다.

     

    (참고로 String 객체를 "="사용해서 값을 추가하면 기존 객체에 추가되는 것이아니라 기존 객체는 버려지고 새로운 객체를 생성하여 추가한 문자열이 저장되는 로직이고, 메모리 효율성이나 성능에는 좋지 않은 코드 작성 방법이지만 간단한 작성을 확인하기 위해 사용하였다.

    메모리 할당 측면에서도  String pool을 사용하여 값이 같다면 메모리를 공유하여 같은 주소값을 바라보게 하는 구조로 다른 변수가 하나의 주소 값을 공유하여 값이 의도하지 않게 변경될 수 있어 유의해야 한다.  )

     

     

    바로 객체를 사용하지 않고 프록시를 통해 이용하는 방식을 사용하는 이유는 무엇일까?

    대상 클래스가 민감한 정보를 가지고 있거나

    인스턴스화 하기에 무겁거나

    기능을 추가하고 싶은데

    원본 객체를 수정할 수 없는 상황일 때 극복하기 위해서이다.

     

    프록시 패턴을 사용하면 다음과 같은 효과를 가질 수 있다.

    ● 보안(Security)

    프록시는 클라이언트가 작업을 수행할 수 있는 권한이 있는지 확인하고 검사 결과가 긍정적인 경우에만 요청을 대상으로 전달한다.

    ● 캐싱(Caching)

    프록시가 캐싱된 데이터가 없는 경우에만 작업이 실행되도록 한다.

    ● 데이터 유효성 검사(Data validation)

    프록시가 입력을 대상으로 전달하기 전에 유효성을 검사한다.

    ● 지연 초기화(Lazy initialization)

    대상의 생성 비용이 비싸다면 프록시는 그것을 필요로 할때까지 연기할 수 있다.

    ● 로깅 (Logging)

    프록시는 메소드 호출과 상대 매개 변수를 인터셉트하고 이를 기록한다.

    ● 원격 객체(Remote objects) 

    프록시는 원경 위치에 있는 객체를 가져와서 로컬처럼 보이게 할 수 있다.

     

    패던 장점

    ● 개방 폐쇠 원치(OCP)준수

    기존 대상 객체의 코드를 변경하지 않고 새로운 기능을 추가할 수 있다.

    ● 단일 책임 원칙(SRP)준수

    대상 객체는 자신의 기능에만 집중하고, 그 이외 부가 기능을 제공하는 역할을 프록시 객체에 위임하여 다중 책임을 회피할 수 있다.

     

    원래 하려던 기능을 수행하며 그 외의 부가적인 작업(로깅, 인증, 네트워크 통신 등)을 수행하는데 유용하다

    클라이언트는 객체를 신경쓰지 않고, 서비스 객체를 제어하거나 생명 주기를 관리할 수 있다.

     

    패턴 단점

    ● 많은 프록시 클래스를 도입해야 하므로 코드의 복잡도가 증가한다.

    ● 프록시 클래스 자체에 들어가는 자원이 많다면 서비스로부터 응답이 늦어질 수 있다.

     

     

    Spring Framework

    스프링 AOP

    스프링 프레임워크에서는 내부적으로 프록시 기술을 많이 사용하고 있다. (AOP, JPA 등)

    스프링에서는 Bean을 등록할 때 Singleton을 유지하기 위해 Dynamic Proxy기법을 이용해 프록시 객체를 Bean으로 등록한다. 또한 Bean으로 등록하려는 객체가 Interface를 하나라도 구현하고 있으면 JDK를 이용하고, 구현하고 있지 않으면 내장된 CGLIB라이브러리를 이용한다.

     

    Dynamic Proxy

    개발자가 직접 프록시 패턴을 구현해도 되지만, 자바 JDK에서는 별도로 프록시 객체 구현 기능을 지원한다.

    이를 동적 프록시 기법이라고 부른다.

    동적 프록시는 개발자가 직접 프록시 객체를 생성하는 것이 아닌, 애플리케이션 실행 도중 java.lang.reflect.Proxy패키지에서 제공해주는 API를 이용하여 동적으로 프록시 인스턴스를 만들어 등록하는 방법으로, 자바의 Reflection API기법을 응용한 연장선의 개념이다.

    별도의 프록시 클래스 정의없이 런타임으로 프록시 객체를 동적으로 생성해 이용할 수 있다는 장점이 있다.

     

     

     

     

     

     

    참고

     

    https://en.wikipedia.org/wiki/Proxy_pattern

    https://refactoring.guru/design-patterns/proxy

    https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%94%84%EB%A1%9D%EC%8B%9CProxy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

     

    https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EB%88%84%EA%B5%AC%EB%82%98-%EC%89%BD%EA%B2%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-Dynamic-Proxy-%EB%8B%A4%EB%A3%A8%EA%B8%B0

     

     

     

     

     

    728x90

    'Computer Science > CS' 카테고리의 다른 글

    [CS] DispatcherServlet, filter, interceptor  (0) 2024.04.29
    [CS] Load balancing  (0) 2024.04.10
    [CS] JSP HTTP URL  (0) 2024.04.08
    [CS] polling push pull  (0) 2024.04.02
    [CS] Design Pattern  (0) 2024.03.20

    댓글

© 2022. code-space ALL RIGHTS RESERVED.