본문 바로가기

Java/Java

(JAVA) 클래스의 프록시 생성

cglib의 사용

다음과 같이 스프링, 하이버네이트 에도 Enhacer 클래스 내부에도 내장되어 있다.

 

pom.xml 추가

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

 

Enhancer의 활용

BookService bookService = (BookService) Enhancer.create(BookService.class, handler);

 

MethodInterceptor를 활용하여 handler 생성

MethodInterceptor handler = new MethodInterceptor() {
    BookService bookService = new BookService();

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return method.invoke(bookService, args);
    }
};

 

https://github.com/cglib/cglib/wiki

 

cglib/cglib

cglib - Byte Code Generation Library is high level API to generate and transform Java byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept f...

github.com


ByteBuddy의 활용

 

Proxy 클래스 생성 : ByteBuddy를 활용하여 BookService의 subClass를 만든다.

Class<? extends BookService> proxyClass = new ByteBuddy().subclass(BookService.class)
        .make().load(BookService.class.getClassLoader()).getLoaded();

 

Proxy클래스의 인스턴스 생성

BookService bookService = proxyClass.getConstructor(null).newInstance();

 

실행결과

 

특정 작업을 하고 싶을 때(with InvocationHandlerAdapter)

 

        Class<? extends BookService> proxyClass = new ByteBuddy().subclass(BookService.class)
                /*********************************************
                 * 여기에 
                 * 특정 작업을 진행 한다.
                 *********************************************/
                .make().load(BookService.class.getClassLoader()).getLoaded();
        BookService bookService = proxyClass.getConstructor(null).newInstance();

 

  • 가공하고 싶은 method를 intercept 하여 자바의 프록시패턴과 유사하게 InvocationHandler()를 활용한다.
  • return으로 invoke(instance, 인자)를 리턴한다.
  • return 하기 이전에 특정 작업을 실행하고 싶다면 invoke 전후로 작업을 수행하면 된다.

(아래 코드의 aaaaa , bbbbb 출력과 같이)

Class<? extends BookService> proxyClass = new ByteBuddy().subclass(BookService.class)

        .method(named("rent")).intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
            BookService bookService = new BookService();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("aaaaa");
                Object invoke = method.invoke(bookService,args);
                System.out.println("bbbbb");

                return invoke;
            }
        }))

        .make().load(BookService.class.getClassLoader()).getLoaded();

 

실행결과(rent 메서드에만 가공되었고 returnBook 메서드에는 특정가공작업없이 원래의 작업이 출력되어있는 것을 볼 수 있다.)

 

문제점

상속을 활용하는 방법이기 때문에 특정한 면에서는 제한이 따른다.

가령

 

1. 상수를 사용하는 경우

public  final class BookService {

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

}

final 사용 실행 결과

2. 생성자의 접근제어자가 private 한 경우

public  class BookService {

    private BookService() {

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

}

디폴트 메서드 private 사용 결과

subClass를 만들 때 부모인 BookService의 디폴트 생성자의 접근제어자가 private으로 되어있다면

자식의 인스턴스가 생성될 때 부모의 인스턴스도 같이 올라가게 되는데 부모의 디폴트 생성자가 private이라면 부모의 인스턴스를 생성할 수 없기에 에러가 발생하는 것이다.

 

서브 클래스를 만드는 방법의 단점

  • 상속을 사용하지 못하는 경우 프록시를 만들 수 없다.

    • Private 생성자만 있는 경우

    • Final 클래스인 경우

  • 인터페이스가 있을 때는 인터페이스의 프록시를 만들어 사용할 것.


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