반응형

클래스 기반의 다이나믹 프록시

자바에서 기본으로 제공하는 Proxy를 통해서는 클래스 기반의 프록시를 생성할 수 없다.

다만 서브 클래스를 만들 수 있는 라이브러리를 사용하여 프록시를 만들 수 있다.

 

public class CarService {

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

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

 

CGlib

  • 스프링, 하이버네이트 등에서도 사용하는 라이브러리
  • 버전 호환성이 좋지 않아서 서로 다른 라이브러리 내부에 내장된 형태로 제공되기도 한다.
    • 스프링에서 의존성을 따로 추가하지 않고도 CGlib의 Enhancer 클래스 사용가능.
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

1. 클래스의 다이나믹 프록시 생성 방법(CGlib)

    @Test
    public void cglibDi(){
        //핸들러를 메소드 인터셉터로 생성.
        MethodInterceptor handler = new MethodInterceptor() {
            CarService carService = new CarService();
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                if(method.getName().equals("rent")){
                    System.out.println("aaaa");
                    Object invoke = method.invoke(carService, args);
                    System.out.println("bbbb");
                    return invoke;
                }
                return method.invoke(carService, args);
            }
        };

        //프록시 객체의 메소드가 호출될 때마다 부가적으로 어떤일을 처리해야하는지에 대한 핸들러를 넘겨준다.
        CarService carService = (CarService) Enhancer.create(CarService.class, handler);

        Book book = new Book();
        book.setTitle("spring");
        carService.rent(book);
        carService.returnBook(book);
    }

 

ByteBuddy

  • 스프링은 ByteBuddy의 버전관리를 해주기 때문에 따로 의존성에 버전 명시안해줘도 된다.
  • 바이트 코드 조작뿐 아니라 런타임(다이나믹) 프록시를 만들 때도 사용할 수 있다.
<dependency>
  <groupId>net.bytebuddy</groupId>
  <artifactId>byte-buddy</artifactId>
  <version>1.11.1</version>
</dependency>

2. 클래스의 다이나믹 프록시 생성 방법(Bytebuddy)

    @Test
    public void bytebuddyDi() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //ByteBuddy에서 특정 메소드 호출 시, 부가적인 기능 수행하도록 처리(rent라는 이름의 메서드는 가로채서 부가적인 기능 수행)
        Class<? extends CarService> proxyClass2 = new ByteBuddy().subclass(CarService.class)
                .method(named("rent")).intercept(InvocationHandlerAdapter.of(new InvocationHandler() {
                    CarService carService = new CarService();
                    @Override
                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("aaaa");
                        Object invoke = method.invoke(carService, args);
                        System.out.println("bbbb");
                        return invoke;
                     }
                 }))
                .make().load(CarService.class.getClassLoader()).getLoaded();

        CarService carService = proxyClass2.getConstructor(null).newInstance();


        Book book = new Book();
        book.setTitle("spring");
        carService.rent(book);
        carService.returnBook(book);
    }

 

서브 클래스를 만드는 방법의 단점
  • 클래스 상속을 사용하지 못하는 경우 프록시를 만들 수 없다.
    • Private 생성자만 있는 경우
    • Final 클래스인 경우
  • 인터페이스가 있을 때는 인터페이스의 프록시를 만들어 사용하자

[참고자료]

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

Interface MethodInterceptor - cglib

Class Enhancer - cglib

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

https://bytebuddy.net/#/

반응형

+ Recent posts