반응형

다이나믹 프록시란?

  • 런타임에 특정 인터페이스들을 구현하는 클래스 또는 인스턴스를 만드는 기술

 

다이나믹 프록시 구현

서브젝트

public interface BookService {

    void rent(Book book);

    void returnBook(Book book);
    
}

 리얼 서브젝트

public class DefaultBookService implements BookService{

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

    @Override
    public void returnBook(Book book) {
        System.out.println("return: " + book.getTitle());
    }

}

 

프록시 인스턴스 만들기

  • Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
    • 첫 번째 인자로 서브젝트의 클래스로더를 준다.
    • 두 번째 인자는 Class 타입의 배열을 주어야한다. (인터페이스의 목록)
      • 이 프록시 인스턴스가 어떤 인터페이스 타입의 구현체이냐를 알려주어야한다.
    • 세 번째 인자는 InvocationHandler. 이 프록시의 어떤 매소드가 호출이 될 때 그 메소드 호출을 어떻게 처리할 것인지를 의미한다. 기존 프록시 객체에 추가한 부가적인 기능을 여기서 구현하면 된다. 기존 프록시 객체를 대체하므로 리얼 서브젝트를 여기서 가지고 있어야 한다.
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
        new InvocationHandler() {
            BookService bookService = new DefaultBookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("rent")) {
                    System.out.println("aaaa");
                    Object invoke = method.invoke(bookService, args);
                    System.out.println("bbbb");
                    return invoke;
                }

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

 

@Test
public void di(){
  Book book = new Book();
  book.setTitle("spring");
  bookService.rent(book);
  bookService.returnBook(book);
  Assert.assertNotNull(bookService);
}

현재 상황에서는 rent, returnBook 메소드 호출 시, 프록시의 부가적인 기능이 수행된다.

rent : aaaa rent: spring bbbb

returnBook : aaaa return: spring bbbb

 

특정 메소드(rent)에만 부가적인 기능을 수행하려면 다음과 같이 처리하면 된다.

BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
            new InvocationHandler() {
                BookService bookService = new DefaultBookService();

                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //Object proxy : 여기서 생성한 프록시를 의미
                    //Method method : 프록시로 어떤 메소드 호출이 들어왔을 떄, 그 메소드 호출을 어떻게 처리할 것인지 결정필요. 여기서 method는 프록시를 호출한 메소드를 의미한다.
                    if (method.getName().equals("rent")){
                        //부가적인 기능 수행
                        System.out.println("aaaa");

                        //원래 있던 기능 수행
                        Object invoke = method.invoke(bookService, args);

                        //부가적인 기능 수행
                        System.out.println("bbbb");
                        return invoke;
                    }
                    return method.invoke(bookService, args);
                }
            });
@Test
public void di(){
  Book book = new Book();
  book.setTitle("spring");
  bookService.rent(book);
  bookService.returnBook(book);
  Assert.assertNotNull(bookService);
}

조건을 설정하여 메소드 명이 rent인 것만 부가적인 기능을 수행하도록 할 수 있다.

rent : aaaa rent: spring bbbb

returnBook : return: spring

 

다이나믹 프록시는 프록시 클래스를 매번 만들어야하는 수고는 덜어지지만 메소드가 많아지고 특정한 것만 수행하도록 하려면 조건이 추가되고 혼잡해진다. 이는 유연한 구조가 아니며 이러한 구조를 뜯어 고친게 스프링 AOP이다.

 

또한 자바 다이나믹 프록시의 가장 큰 제약사항은 클래스 기반의 프록시는 만들지 못한다. 반드시 인터페이스 타입의 .class 가 인자로 전달되어야 한다.

public class DefaultBookService implements BookService{

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

    @Override
    public void returnBook(Book book) {
        System.out.println("return: " + book.getTitle());
    }

}

아래의 코드는 클래스 기반의 .class 를 인자로 전달하여 에러가 발생한다.

DefaultBookService bookService = (DefaultBookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{DefaultBookService.class},
            new InvocationHandler() {
                DefaultBookService bookService = new DefaultBookService();

                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //Object proxy : 여기서 생성한 프록시를 의미
                    //Method method : 프록시로 어떤 메소드 호출이 들어왔을 떄, 그 메소드 호출을 어떻게 처리할 것인지 결정필요. 여기서 method는 프록시를 호출한 메소드를 의미한다.
                    if (method.getName().equals("rent")){
                        //부가적인 기능 수행
                        System.out.println("aaaa");

                        //원래 있던 기능 수행
                        Object invoke = method.invoke(bookService, args);

                        //부가적인 기능 수행
                        System.out.println("bbbb");
                        return invoke;
                    }
                    return method.invoke(bookService, args);
                }
            });

[참고자료]

더 자바, 코드를 조작하는 다양한 방법, 백기선

Dynamic Proxy Classes

Class Proxy

반응형

+ Recent posts