Dynamic Proxy란 (with Java)

이우길·2022년 3월 13일
1

Back To Java

목록 보기
4/4
post-thumbnail

Dynamic Proxy에 대하여...

이전 글에서 Proxy 객체를 직접 생성하고, Proxy 객체를 직접 생성할 때 겪을 수 있는 불편함들에 대하여 간단히 알아보았다.

그 불편함들을 개선하기 위해 이번 글에서는 Dynamic Proxy에 대해 알아보기를 원한다.


Goal

Dynamic Proxy에 대한 개념을 이해하고 구현하는 방법 study


Dynamic Proxy란?

Dynamic Proxy란 이전과 같이 Proxy 객체를 직접 생성을 하는 것이 아니라 Runtime(애플리케이션이 실행되는 중)에 Interface를 구현하는 Class or 인스턴스를 만들어내는 것을 이야기 한다.

Java java.lang.reflect.Proxy package에서 제공해주는 API를 이용하여 Dynamic Proxy를 이용할 수 있다.

API에서 제공해주는 Syntax는 다음과 같다.

public static Object newProxyInstance(
    ClassLoader loader, // 1
    Class<?>[] interfaces, // 2
    InvocationHandler h // 3
) throws IllegalArgumentException
  1. Proxy객체 정의하기 위한 Class Loader를 지정한다. (Proxy 객체가 구현할 Interface에 Class Loader를 얻어오는 것이 일반적)

  2. newProxyInstance()를 통해 생성 될 Proxy 객체가 구현할 Interface를 정의한다.

  3. 메소드 호출을 디스패치하기 위한 호출 핸들러. (디스패치: 어떤 메소드를 호출할 것인가를 결정하여 그것을 실행하는 과정을 이야기함.)

위에 정의된 대로 매개변수를 지정하여 API를 호출하면 return 되는 값은 다음과 같다고 문서에서 정의하고 있다.

Returns: a proxy instance with the specified invocation handler of a proxy class that is defined by the specified class loader and that implements the specified interfaces

직역하자면 지정된 Class Loader에 의해 정의된 Interface를 구현한 Proxy 객체의 InvocationHandler를 가지고 있는 Object타입의 Proxy인스턴스라는 것이다.


예제 코드

위에서는 API의 스팩에 대하여 알아봤으니 한번 코드로 작성해보자.

Java에서 제공하는 Dynamic Proxy는 Interface 기반이기 때문에 Interface를 정의한다.

public interface BookService {
    void printTitle(Book book);
}

해당 Interface를 구현하는 구현 Class를 정의한다.

public class BookServiceImpl implements BookService{

    @Override
    public void printTitle(Book book) {
        System.out.println("book.getTitle() = " + book.getTitle());
    }
}

newProxyInstance()를 이용하여 Interface기반의 Proxy 인스턴스를 생성한다.

 public static void main(String[] args) {
        // interface 기반으로 해당 구현체의 인스턴스를 생성하였다. 
        // (pure 자바에서 제공하는 reflect.Proxy는 Class 기반의 Proxy를 만들지 못한다.)
        BookService bookService = (BookService) Proxy.newProxyInstance(
        	BookService.class.getClassLoader(),
            new Class[]{BookService.class},
            (proxy, method, arguments) -> {
            	BookService realSubject = new BookServiceImpl();
  		          System.out.println("prev method invoke");
                  Object invoke = method.invoke(realSubject, arguments);
                  System.out.println("after method invoke");
                  return invoke;
        	});
        
		// class com.sun.proxy.$Proxy0
        System.out.println("bookService.getClass() = " + bookService.getClass());

        Book book = new Book("foobar");
        bookService.printTitle(book);

        // output
        // prev method invoke
        // bookService.getClass() = foobar
        // after method invoke
    }

위의 예제 코드와 같이 java.lang.reflect.Proxy에서 제공해주는 API를 이용하면 런타임에도 해당 Interface를 구현하는 Proxy객체의 인스턴스를 생성할 수 있다.


java.lang.reflect.Proxy를 통해 Proxy 인스턴스를 생성할 때 주의점

java.lang.reflect.Proxy에서 제공하는 newProxyInstance()를 이용할 때 Class기반으로는 Proxy 객체를 생성할 수 없다는 것이다.

위의 예제코드를 조금 변경하여 실행해보면 다음과 같은 Exception 만날 수 있다.

// Exception in thread "main" java.lang.IllegalArgumentException:
// me.leewoooo.dynamicproxy.BookServiceImpl is not an interface
BookServiceImpl bookService = (BookServiceImpl) Proxy.newProxyInstance(
    BookServiceImpl.class.getClassLoader(),
    new Class[]{BookServiceImpl.class},
    (proxy, method, arguments) -> {
        //...
    }
)

Error Message만 봐도 해당 API를 어떻게 사용해야 하는지 명백하게 알려주고 있다.

의문점 및 답변

Spring을 이용할 때 Interface를 구현하지 않은 Class들 또한 Proxy 객체로 Bean에 등록되는데 이건 어떻게 동작을 하는 것일까?

추 후 AOP를 공부할 때 더 자세하게 다루겠지만 Spring은 Bean을 등록할 때 Spring AOP를 이용하여 등록을 하는데

Bean으로 등록하려는 기본적으로 객체가 Interface를 하나라도 구현하고 있으면 java.lang.reflect.Proxy를 이용하고 Interface를 구현하고 있지 않으면 CGLIB라는 외부 라이브러리를 사용한다.


마무리

이번 글을 토대로 Proxy PatternDynamic Proxy에 대해 알아보았다. Spring은 내부적으로 Proxy기술을 정말 많이 사용하고 있다.(Spring AOP, Spring Data JPA 등등...)

Spring에서는 Bean을 등록할 때 Singleton을 유지하기 위해 Proxy기법을 이용해 Proxy객체를 Bean으로 등록하는 것이고, Spring Data JPA에서는 JpaRepository만 상속 받아도 해당 구현체를 만들어 Bean으로 등록해 주는 것도 Proxy 기법을 이용한 것이다. (이 외 사용되는 곳이 더 많음.)

이와 같이 Spring의 여러곳에서 전반적으로 Proxy 기법은 많이 사용된다.

참조

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html

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

https://gmoon92.github.io/spring/aop/2019/04/20/jdk-dynamic-proxy-and-cglib.html

profile
leewoooo

0개의 댓글