2016-10-27 8 views
2

Java에서는 InvocationHandler 구현을 사용하여 동적 프록시를 만들 수 있습니다. JVM 최적화에도 불구하고 리플렉션을 사용하면 항상 메소드를 호출하는 오버 헤드가 발생합니다.ByteBuddy를 사용하여 동적 프록시를 만드는 방법

이 문제를 해결하기 위해 런타임시 프록시 클래스를 만들기 위해 ByteBuddy를 사용하려고했지만 문서가이 부분에서 충분히 명확하지 않은 것 같습니다.

메소드 호출을 일부 클래스 인스턴스로 전달하기 위해 MethodCallProxy을 어떻게 만듭니 까?

편집 : 내가 RPC 시스템을 구축하고

:

더 나은 내 문제를 명확히하기 위해, 나는 내가 무엇을 달성하고자하는의 예를 제공하고 있습니다. 메소드 호출의 각면에서, 나는 (두 호출자/피 호출자가 JVM 하에서 실행될 때) 계약을 정의하는 인터페이스를 가지고있다.

@Contract 
interface ISomeService { 
    fun someMethod(arg0: String, arg1: SomePojo): PojoResult 
} 

호출 사이트에서 모든 메소드 호출을 가로 채어 호출 수신자에게 전달하는 프록시를 주입합니다.

ByteBuddy() 
    .subclass(Any::class.java) 
    .implement(serviceClass) 

    // Service contract method delegation 
    .method(isDeclaredBy(serviceClass)).intercept(
     MethodDelegation 
      .to(ServiceProxyInterceptor()) 
      .filter(not(isDeclaredBy(Any::class.java))) 
    ) 

    .make() 
    .load(this) 
    .loaded as Class<T> 

는 그리고, 마지막으로, 호출자에서, 나는 호출 매개 변수를 비 정렬 화 및 서비스 구현에 전달에 대한 책임 여러 핸들러, 각 서비스 방법 하나를 가지고있다.

@Service 
class SomeServiceImpl { 
    fun someMethod(arg0: String, arg1: SomePojo): PojoResult { 
     // ... 
    } 
} 

나는 코드 생성을 사용하여이 문제를 해결할 수 있지만 결과 jar 파일이 매우 큰이 될 수 있습니다. 따라서이 핸들러의 제네릭 버전을 만들고 각각의 인스턴스에서 모든 메서드 호출을 가로 채어 ISomeService에 가로 채고 SomeServiceImpl에 전달하는 프록시를 연결하려고합니다.

답변

3

Byte Buddy에서 프록시 클래스를 만드는 방법은 여러 가지가 있습니다. 정확한 방법은 사용 사례에 따라 다릅니다. 가장 쉬운 방법은 InvocationHandlerAdapter을 사용하는 것입니다. 당신이 SomeClass에 대한 프록시를 만들 것을 감안할 때, 당신은 사용을 만들 수 있습니다 : 당신이 다른 인스턴스에 위임와 프록시를 만들려면

Class<? extends SomeClass> proxy = new ByteBuddy() 
    .subclass(SomeClass.class) 
    .method(ElementMatchers.any()) 
    .intercept(InvocationHandlerAdapter.of(invocationHandler)) 
    .make() 
    .load(SomeClass.class.getClassLoader()); 

, 당신은 추가로 필드를 정의합니다. 이는 다음의 지시에 의해 수행 할 수 있습니다 : 이제 클래스와 캐스트를 인스턴스화 할 수

interface HandlerSetter { 
    InvocationHandler getHandler(); 
    void setHandler(InvocationHandler handler); 
} 

Class<? extends SomeClass> proxy = new ByteBuddy() 
    .subclass(SomeClass.class) 
    .defineField("handler", InvocationHandler.class, Visibility.PUBLIC) 
    .implement(HandlerSetter.class) 
    .intercept(FieldAccessor.ofField("handler")) 
    .method(ElementMatchers.any()) 
    .intercept(InvocationHandlerAdapter.toField("handler")) 
    .make() 
    .load(SomeClass.class.getClassLoader()); 

:

Class<? extends SomeClass> proxy = new ByteBuddy() 
    .subclass(SomeClass.class) 
    .defineField("handler", InvocationHandler.class, Visibility.PUBLIC) 
    .method(ElementMatchers.any()) 
    .intercept(InvocationHandlerAdapter.toField("handler")) 
    .make() 
    .load(SomeClass.class.getClassLoader()); 

당신은 반사를 통해 또는 예를 들어 같은 세터 인터페이스를 구현하여 위의 필드를 설정합니다 핸들러를 설정하기위한 인터페이스의 클래스

InvocationHandler 이외의 다른 방법으로 프록시를 만들 수 있습니다. 한 가지 방법은보다 유연하고 종종 더 빠르며 필요할 때 수퍼 메서드를 호출 할 수있는 MethodDelegation을 사용하는 것입니다. 전달 계측은 MethodCall 또는 Forwarding 계측을 사용하여 적용 할 수도 있습니다. 각각의 클래스 javadoc에서 자세한 정보를 찾을 수 있습니다.

+0

물론이 질문의 특정 부분을 다루기 위해, ByteBuddy가 생성 한 프록시가 InvokeHandler를 호출하는 것이 JRE가'java.lang.reflect.Proxy'를 호출하는 데 더 효율적이라고 가정 할 이유가 없습니다. InvocationHandler' ... – Holger

+0

차이점은 대부분 JVM이 인터페이스 만 지원하는 반면 Byte Buddy는 클래스를 계측 할 수 있다는 것입니다. 필자는 성능면에서 코드 생성의 이점을 리플렉션에 비해 나타냈다 고 생각합니다. 그러나 후자는 의심 스럽다. 대부분의 경우, 반사 반등이 충분하다는 것을 알 수 있습니다. –

+0

ByteBuddy와 Reflection의 차이점을 알고 있지만, 문제가 전혀 문제가되지 않는다는 것을 알 수 있습니다. 언급 된 유일한 동기는 Reflection의 청구 된 오버 헤드입니다. 성능 문제가있는 시나리오가있을 수 있지만'InvocationHandler'를 고수하면 코드 생성이 잘못된 끝을 처리합니다. 예를 들어 기본 유형의 복싱과 인수를 배열에 채우는 것이 계속 발생합니다. – Holger