다이나믹 프록시

사전 지식

  • java Class loading (실행과정)
  • java 리플렉션

프록시 패턴

proxy pattern

  1. 프록시와 리얼 서브젝트가 같은 인터페이스를 상속받고 클라이언트는 인터페이스를 프록시 타입으로 선언해서 사용한다.
  2. 프록시에서는 앞뒤로 부가 가능을 추가하고 리얼 서브젝트를 호출 한다.
  • 결과적으로 리얼 서브젝트는 제공하는 핵심기능을 유지하며 코드 변경 없이 부가적인 기능(트렌젝션, 접근 제어, 로깅)을 제공할 수 있다.

다이나믹 프록시

  • 테스트 코드 실행을 위해서 junit4를 사용할 것이다.
  1. 프록시 패턴 인터페이스가 필요하듯 인터페이스를 추가한다

    public interface SomethingService {
    
        @MorningGreetings
        public void printName(String name);
    
        public void printinformation(String information);
    }
    
  2. 특정한 메소드에만 프록시 기능을 추가하고 싶기 떄문에 어노테이션을 추가하자

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MorningGreetings {}
    
  3. real 서브젝트를 만들자

    public class SomethingServiceImpl implements SomethingService {
    
        @Override
        public void printName(String name) {
            System.out.println(name);
        }
    
        @Override
        public void printinformation(String information) {
            System.out.println(information);
        }
    }
    
  4. 리얼 서브젝트를 감싸는 프록시 테스트할 코드를 작성하자

    public class SomethingServiceProxyTest {
    
        public SomethingService somethingService
                = (SomethingService) Proxy.newProxyInstance(SomethingService.class.getClassLoader(),
                    new Class[]{SomethingService.class},
                new InvocationHandler() {
                    SomethingService somethingService = new SomethingServiceImpl();
    
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
    
                        MorningGreetings greetingAnocation = method.getAnnotation(MorningGreetings.class);
    
                        if (greetingAnocation != null) {
                            System.out.print("Hi,");
                            Object realInvoke = method.invoke(somethingService, objects);
                            System.out.println("good morning");
                            return realInvoke;
                        }
    
                        return method.invoke(somethingService, objects);
                    }
                });
    
        @Test
        public void TestPrintName() {
            somethingService.printName("dobby");
            somethingService.printinformation("dobby is cute");
        }
    }
    
    • java.lang.reflect.Proxy라는 API를 이용해서 프락시를 만들었다.
    • 결과 값은…
      Hi,dobby
      good morning
      dobby is cute
      

java 기본 API로는 class 기반의 프록시를 못만들어준다.

  • 무조건 interface가 필요
    • class로 하게 되면 “is not an interface"라는 에러메시지가 뜬다.
  • class 기반 프록시를 하고 싶다면 추가 라이브러리가 필요하다.

cglib (추천)

  • https://github.com/cglib/cglib/wiki
  • spring 내부에서도 사용하는 라이브러리
  • 단 하위 호환성이 좋지 않아서 각 프로젝트별로 가지고 있는 것이 좋음

ByteBuddy

  • https://bytebuddy.net/#/
  • 바이트 코드 조작 뿐 아니라 런타임(다이나믹) 프록시를 만들 때도 사용가능
  • 서브클래스를 만드는 방법
    • private 생성자만 있는 경우는 사용 못함
    • final class인 경우는 사용 못함

다이나믹 프록시를 사용하는 곳

  • 스프링 데이터 JPA
  • 스프링 AOP
  • Mockito
  • 하이버네이트 lazy initialzation
  • … 등등
comments powered by Disqus