ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 프록시 패턴이란?
    Java 2022. 4. 8. 02:50

    프록시 패턴이란?

    디자인 패턴 중 하나로, 어떤 클래스를 직접 사용하는 대신에 프록시 클래스를 통해 사용하는 것을 의미합니다.

    사장님에게 바로 보고하지 않고 비서에게 보고하면 비서가 사장님에게 보고하는 것을 떠올리면 좋습니다.

     

    프록시(Proxy)란?

    프록시란 프록시 서버, 프록시 객체, 프록시 패턴 등등 소프트웨어 기술에서 종종 등장하는 용어입니다.

    '대리', '중개'라는 의미를 가진 단어로써 예를 들어 프록시 서버라고 한다면 클라이언트와 서버 사이에 들어가 있어서 '클라이언트 -> 프록시 서버 -> 서버'처럼 중간에 대리인/중개인처럼 사용됩니다.

     

    따라서 프록시 패턴이란 인터페이스를 사용하고 실행시킬 클래스에 대해 객체가 들어갈 자리에 프록시(대리자) 객체를 투입하여 클라이언트는 실제 실행시킬 클래스에 대해 메서드를 호출하여 받는지/ 프록시 객체의 메소드를 호출해서 반환 값을 받는지 모르게 하는 것을 의미합니다.

    https://esoongan.tistory.com/180

    클라이언트는 Subject라는 인터페이스의 request() 메서드를 호출했을 때 RealSubject 클래스에서 해당 메서드가 호출되었는지 Proxy 클래스에서 해당 메서드가 호출되었는지 알 수 없습니다.

     

    프록시 패턴 사용법

    위의 예제처럼 실제로 코드를 구현해 보겠습니다.

     

    Subject 인터페이스

    public interface Subject {
        String request();
    }

    RealSubJect 클래스

    public class RealSubject implements Subject {
    
        @Override
        public String request() {
            return "HelloWorld";
        }
    }

    Proxy 클래스

    public class Proxy implements Subject {
    
        private final RealSubject realSubject = new RealSubject();	    
        
        @Override
        public String request() {
            return realSubject.request();  //프록시가 실제의 메소드를 호출한다.
        }
    
    }

    메인 클래스

    public class Main {
    
        public static void main(String[] args) {
            // Subject클래스의 메소드를 호출하는것이아닌 프록시클래스의 메소드를 호출한다.
            Subject subject = new Proxy();
            System.out.println(subject.request()); // 내부적으로 Subject의 메소드를 호출한다.
    
        }
    }

     

    프록시 패턴을 사용하는 이유는?

    굳이 불편하게 Proxy 클래스를 만들어서 거기에 멤버 변수로 RealSubJect 클래스를 만들고 메서드를 호출하는 이유가 뭘까요?

     

    1. 흐름을 제어할 수 있다.

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

     

    2. 실제 메서드가 호출되기 이전에 필요한 기능을 구현 객체 변경 없이 추가할 수 있다. (AOP가 떠오르네요)

    public class RealSubject implements Subject {
    
        @Override
        public String request() {
            return "HelloWorld";
        }
    }

    만약 request() 메서드의 수행 시간을 측정하고 싶은데 main 클래스 코드나 RealSubject 클래스를 수정하지 않고 측정하려면 프록시 패턴을 사용하여 해결할 수 있습니다.

    public class Proxy implements Subject {
    
        private final Subject realSubject = new RealSubject();
    
        @Override
        public String request() {
        	long before = System.currentTimeMillis();
        	String temp = realSubject.request(); //프록시가 실제의 메소드를 호출한다.
            System.out.println(System.currentTimeMillis() - before);
            return temp;  
        }
    
    }

    프록시 클래스에서 realSubject.request()를 수행하는데 이때 수행시간을 측정함으로써 main 클래스나 코드나 RealSubject 클래스를 수정하지 않고 request() 메서드의 수행시간을 측정할 수 있습니다.

     

     

    3. 캐싱을 사용할 수 있다.

    public class Proxy implements Subject {
    
    	private final subject realSubject;
    	String cache;
    
    	@Override
    	public String request() {
    		if(realSubject == null){
    			realSubject = new RealSubject();
    		}
    		if(cache = null){
    			cache = realSubject.request();
    			return cache;
    		}
    		return cache;        	
    	}
    
    }

    cache를 활용하여 값이 존재하지 않으면 값을 받아오고 값이 존재한다면 그냥 그 값을 그대로 반환함으로써 캐시로 사용할 수 있습니다.

     

    4. 생성 자원이 많이 드는 작업에 대해 백그라운드 처리

    public class Proxy implements Subject {
    
    	private final subject realSubject;
    
    	@Override
    	public String request() {
    		if(realSubject == null){
    			realSubject = new RealSubject();
    		}
    	return realSubject.request();  //프록시가 실제의 메소드를 호출한다.
    	}
    
    }

    실제로 사용되기 전에 미리 객체를 생성하기 않고 실제로 사용될 때 한번 생성함으로써 이점을 가질 수 있습니다.

     

    5. 실제 오브젝트를 요청하기 전이나 후에 인가 처리(보안/보호)

    public class Proxy implements Subject {
    
    	private final Subject realSubject = new RealSubject();
    
    	@Override
    	public String request() {
    		//실제 메서드를 호출하기 직전에 인증/인가 확인        
    		if(!isValid()){
    			return "CANT RUNNING";
    		}
    		return realSubject.request(); //프록시가 실제의 메소드를 호출한다.                
    	}
    
    }

    실제 메서드를 수행하기 전에 보안을 위하여 인증/인가를 확인하고 메서드를 수행할 수 있습니다.

     

    프록시 패턴의 단점/문제점

    • 로직이 난해해져 가독성이 떨어질 수 있습니다. (단순하게 코드가 복잡해짐)
    • 인터페이스를 직접 구현해야 합니다. (예를 들어 1가지 메서드를 확장하고 싶더라도 인터페이스를 모두 구현해야 하기 때문에 3개의 메서드를 구현해야 할 수 있습니다.)
    • 프록시 클래스 내에 중복이 발생합니다.

    https://live-everyday.tistory.com/217

    위의 코드는 세 개의 메서드 모두 리턴하는 문자열을 대문자로 바꾸어주는 똑같은 일을 합니다.

     

    하지만 다이나믹 프록시를 사용한다면 인터페이스를 직접 구현해야 하는 단점과 프록시 클래스 내에 중복이 발생하는 문제점을 해결할 수 있습니다.

     

    다이나믹 프록시

    인터페이스의 구현체로 프록시를 구현할 수도 있지만 자바에서 런타임시에 인스턴스를 동적으로 만들 수 있는 리플렉션이라는 클래스를 제공합니다.

     

    리플렉션의 Proxy 클래스가 동적으로 프록시 객체를 생성해 주므로 JDK Dynamic Proxy라는 이름이 붙여졌습니다.

     

    프록시 객체 생성 과정

    Object proxy = Proxy.newProxyInstance(ClassLoader       // 클래스로더
                                        , Class<?>[]        // 타겟의 인터페이스
                                        , InvocationHandler // 타겟의 정보가 포함된 Handler
                                                            );

     

    예제 코드

    public interface MyInterface {
        void execute(String message);
    }
    
    public class MyClass implements MyInterface{
        @Override
        public void execute(String message) {
            System.out.println(message);
        }
    }
    
    public class MyInvocationHandler implements InvocationHandler {
    
        private MyClass target;
    
        public MyInvocationHandler(MyClass target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before");
            method.invoke(target , args);
            System.out.println("After");
            return null;
        }
    }
    
    public class ProxyInJava {
        public static void main(String[] args) {
            MyClass myClass = new MyClass();
            MyInvocationHandler myInvocationHandler = new MyInvocationHandler(myClass);
            MyInterface myInterface = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),
                    new Class[]{MyInterface.class}
                    ,myInvocationHandler);
            myInterface.execute("JDK Dynamic Proxy"); // 프록시 객체
            myClass.execute("Simple");
    
            //출력
            //Before
            //JDK Dynamic Proxy
            //After
            //Simple
    
        }
    }

    프록시 객체로 실행하면 메서드와 before와 after가 수행되고 그냥 실행할 경우에는 메서드만 실행됩니다.

     

    InvocationHandler란?

    InvocationHandler는 invoke()라는 메서드 하나만 가지고 있는 인터페이스입니다.

    invoke() 메서드는 다이나믹하게 생성될 프록시의 어떤 메서드든 호출되었을 때 호출되는 메서드입니다.

    invoke() 메서드를 통해 확장된 기능을 구현할 수 있습니다.

     

    여기서는 myInterface에 execute() 메서드만 존재했지만 만약에 execute1(), execute2() 메서드가 추가로 존재했을 때 어떤 메서드를 호출하던 invoke() 메서드가 수행되며 확장된 기능이 구현됩니다.

     

     

    CGLib Proxy

    순수 Java JDK 라이브러리를 이용하지 않고 CGLIB라는 외부 라이브러리를 추가해야만 사용할 수 있습니다.

    CGLIB의 Enhancer 클래스를 바탕으로 Proxy를 생성하기 때문에 인터페이스가 없어도 Proxy를 생성할 수 있습니다.

    하지만 상속을 이용하기 때문에 final, private와 같이 상속에 대해 오버라이딩을 지원하지 않는 경우에는 적용할 수 없습니다.

    또한 바이트 코드를 조작해서 프록시 객체를 생성하므로 다이나믹 프록시보다 빠릅니다.

     

     

    CGLib를 사용하여 프록시를 생성할 때  크게 두가지 작업으로 수행합니다.

    • MethodInterceptor 방식(CGLib의 성능을 활용하기 위해서는 MethodInterceptor 일반적으로 사용)
    • InvocationHandler 방식(다이내믹 프록시와 마찬가지로 리플랙션 활용)

     

     

    어디에 쓰이나요?

    이러한 기능을 추상화시켜서 스프링에서는 AOP(Aspect Oriented Programming)이라는 개념으로 제공합니다.

    또한 JPA에서 지연 로딩할 때도 사용합니다.

     

     

    출처

    https://www.baeldung.com/java-proxy-pattern

    https://esoongan.tistory.com/180

     

    [Java] 프록시패턴 (Proxy Pattern)

    프록시 패턴이란? 디자인패턴중 하나로, 다른 무언가와 이어지는 인터페이스 역할을 하는 클래스를 의미한다. 먼저 프록시라는 용어를 이해하고 넘어가보자. 0. 프록시 라는 용어의 의미 소프트

    esoongan.tistory.com

    https://www.youtube.com/watch?v=tes_ekyB6U8(백기선님의 프록시 패턴 1부~4부)

    https://live-everyday.tistory.com/217

     

    자바를 통해 다이나믹 프록시(Dynamic Proxy)를 구현하는 방법

    지난 글에서 프록시란 무엇인지 알아보았고, 간단한 프록시 예제를 하나 살펴보았다. 프록시(Proxy)란 무엇인지 자바 코드로 보자 프록시(Proxy)란 무엇인지 자바 코드로 보자 Proxy Proxy의 사전적 정

    live-everyday.tistory.com

    https://steady-coding.tistory.com/608

     

    [Spring] AOP와 JDK Dynamic Proxy, CGLIB

    spring-study에서 스터디를 진행하고 있습니다. AOP란? AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍을 뜻한다. 관점 지향은 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나

    steady-coding.tistory.com

    https://velog.io/@suhongkim98/JDK-Dynamic-Proxy%EC%99%80-CGLib

     

    JDK Dynamic Proxy와 CGLib를 알아보자 #2

    Dynamic Proxy와 CGLib을 실습해보며 AOP를 이해해보았습니다.

    velog.io

     

    'Java' 카테고리의 다른 글

    객체지향 5원칙 : SOLID  (0) 2022.04.22
    [Java] 빌더 패턴  (0) 2022.04.09
    [Java] 리플렉션이란?  (0) 2022.04.07
    [Java] 직렬화(Seralize)란?  (0) 2022.03.24
    [Java] Integer.parseInt() vs Integer.valueOf()  (0) 2022.03.20

    댓글

Designed by Tistory.