본문 바로가기

Java/Java

(JAVA) 다이나믹 프록시

다이나믹 프록시란?

런타임 시점에(컴파일 시점이 아닌) 특정 인터페이스들을 구현하는 클래스 혹은 인스턴스를 만드는 기술

https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html

 

Dynamic Proxy Classes

Dynamic Proxy Classes Contents Introduction Dynamic Proxy API Serialization Examples A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of th

docs.oracle.com


이전의 코드는 컴파일 타임에 인스턴스가 결정되어 버린다.

public class BookServiceTest {

    BookService bookService = new BookServiceProxy(new RealSubjectBookService());
    

    @Test
    public void di() {
        Book book = new Book();
        book.setTitle("Toby Spring");
        bookService.rent(book);

    }

}

런타임 시에 결정 나도록 변경해보자 (With Proxy)

 

Proxy.newProxyInstance 사용

 

Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)

 

Proxy.newProxyInstance

(구현할 대상의 클래스 로더 , 클래스 배열 선언 -> 인터페이스 목록 : 어떤 인터페이스 타입의 구현체 인가를 선언, InvocationHandler())

 

    //BookService bookService = new BookServiceProxy(new RealSubjectBookService());
    BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    return null;
                }
            });

 

InvocationHandler 구현 (이제 BookServiceProxy는 필요 없음)

 

BookServiceProxy에서 했던 기능을 InvocationHandler에서 구현해보자

BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
        new InvocationHandler() {
            BookService bookService = new RealSubjectBookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("11111");
                
                Object invoke = method.invoke(bookService, args);
                
                System.out.println("22222");
                
                
                return invoke;
            }
        });

실행결과

 

직접 리얼 타입 서브젝트 인스턴스를 생성하였음.

이렇게 된다면 bookService의 인터페이스에서 또 다른 메서드를 추가했을 때 똑같은 내용이 중복된다.

@Test
public void di() {
    Book book = new Book();
    book.setTitle("Toby Spring");
    bookService.rent(book);

    System.out.println();
    
    bookService.returnBook(book);

}

중복 발생


invoke에서 분기 처리

BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
        new InvocationHandler() {
            BookService bookService = new RealSubjectBookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName() == "rent") {
                    System.out.println("11111");

                    Object invoke = method.invoke(bookService, args);

                    System.out.println("22222");


                    return invoke;
                }
                return method.invoke(bookService, args);
            }
        });

더 이상 앞뒤로 rent에 해당되는 코드가 붙어서 나오지 않는것을 확인할 수 있다.


결과 

이와 같이 구현하면

이전의 코드와 같이 프록시 클래스를 매번 생성하는 것에 대한 부담은 없지만 

InvocationHandler의 유연함이 떨어진다는 단점이 따른다.

 

이러한 단점을 보완하여 스프링 AOP에서는 해당 구조를 스프링이 정의한 인터페이스로 정의하였다.

그래서 스프링 AOP를 Proxy 기반 AOP라고 불리는 것이다.

 

또한 자바의 Proxy의 가장 큰 문제점은 클래스 타입으로 인터페이스를 선언하지 않으면 안 된다는 커다란 단점 또한 존재한다.

(클래스 타입으로 적용 불가!)

 

인터페이스인 BookService에서 클래스인 RealSubjectBookService로 타입 변환을 했을 때

/*인터페이스인 BookService에서 클래스인 RealSubjectBookService로 타입 변환을 했을때*/

RealSubjectBookService bookService = (RealSubjectBookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{RealSubjectBookService.class},
        new InvocationHandler() {
            RealSubjectBookService bookService = new RealSubjectBookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName() == "rent") {
                    System.out.println("11111");

                    Object invoke = method.invoke(bookService, args);

                    System.out.println("22222");


                    return invoke;
                }
                return method.invoke(bookService, args);
            }
        });

오류 발생(인터페이스가 아니다!)

 

 

현재 인터페이스가 없고 클래스 밖에 없다면 다이내믹 프록시를 구현하지 못하는 것일까?

YES!

하지만 동적으로 프록시를 만드는 또 다른 방법이 있다.

 

https://itmore.tistory.com/entry/%EC%9E%90%EB%B0%94-%EB%A1%9C%EB%93%9C%ED%83%80%EC%9E%84-%EB%A1%9C%EB%94%A9-%EB%B0%8F-%EB%9F%B0%ED%83%80%EC%9E%84-%EB%A1%9C%EB%94%A9-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0ClassLoader

 

자바 로드타임 로딩 및 런타임 로딩 이해하기(ClassLoader)

JVM과 Class Loader의 이해 JVM(Java Virtual Machine)은 Machine 즉 하나의 작은 컴퓨터 머신으로 자체적인 명령 집합을 가지고 있으며 Memory 영역을 나누어 알아서 관리를 하고 있다는 근거에서 이러한 이름을..

itmore.tistory.com


Code Link

https://github.com/mike6321/PURE_JAVA/tree/master/TheJava/proxy-pattern

 

mike6321/PURE_JAVA

Contribute to mike6321/PURE_JAVA development by creating an account on GitHub.

github.com