2012-09-12 2 views
2

클래스에있는 메서드에 바인딩 된 MethodInterceptor를 사용하여 클래스에 접촉하기 전에 데이터에서 간단한 논리를 수행 할 수 있습니다. 그러나 클래스 자체는 인터셉트 된 메소드 중 일부를 호출하지만 그 시점에서 더 이상 해당 로직을 다시 실행할 필요가 없습니다.Google Guice의 조건부 일치

public class MyModule extends AbstractModule { 
    @Override 
    public void configure() { 
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(MyAnnotation.class), new MyInterceptor()); 
    } 
} 

public class MyInterceptor implements MethodInterceptor { 
    @Override 
    public Object invoke(MethodInvocation invocation) throws Throwable { 
    // logic 
    } 
} 

public MyClass { 
    @MyAnnotation 
    void foo() { 
    bar(); 
    } 

    @MyAnnotation 
    void bar() { 
    } 
} 

foo 내에서 전화를 걸 수있는 방법이 저해되지 않는 방법이 있습니까?

답변

3

는 솔직히 말해서, 가장 쉬운 해결책은 단순히 결코 클래스에서 같은 클래스의 다른 공용/주석 메소드를 호출하지하여 문제를 방지하는 것입니다

public class MyClass { 
    @MyAnnotation 
    public void foo() { 
    doBar(); 
    } 

    @MyAnnotation 
    public void bar() { 
    doBar(); 
    } 

    private void doBar() { 
    //doesn't go through interceptor 
    } 
} 

옵션이 아니다 어떤 이유로하는 경우, 그러면이 접근법을 살펴볼 것입니다. AspectJ와 같이 표현이 풍부한 AOP 라이브러리는 pointcut를 정의 할 수있는 유연성을 제공한다.

Guice에서 포인트 컷은 단순히 Guice에 의해 인스턴스화 된 인스턴스에 속한 주석이있는 메소드입니다. 따라서이 논리는 인터셉터 자체로 옮겨 져야합니다.

이렇게하는 한 가지 방법은 ThreadLocal을 사용하여 인터셉터에 대한 항목을 추적하는 것입니다.

public abstract class NonReentrantMethodInterceptor implements MethodInterceptor { 

    private final ThreadLocal<Deque<Object>> callStack = new ThreadLocal<>(); 

    @Override 
    public final Object invoke(MethodInvocation invocation) throws Throwable { 
     Deque<Object> callStack = this.callStack.get(); 
     if (callStack == null) { 
      callStack = new LinkedList<>(); 
      this.callStack.set(callStack); 
     } 

     try { 
      return invokeIfNotReentrant(callStack, invocation); 
     } finally { 
      if (callStack.isEmpty()) { 
       this.callStack.remove(); 
      } 
     } 
    } 

    private final Object invokeIfNotReentrant(Deque<Object> callStack, MethodInvocation invocation) throws Throwable { 
     Object target = invocation.getThis(); 
     if (callStack.isEmpty() || callStack.peek() != target) { 
      //not being called on the same object as the last call 
      callStack.push(target); 
      try { 
       return doInvoke(invocation); 
      } finally { 
       callStack.pop(); 
      } 
     } else { 
      return invocation.proceed(); 
     } 
    } 

    protected abstract Object doInvoke(MethodInvocation invocation) throws Throwable; 
} 

이것은 인터셉터를 호출 의 스택을 추적 할 스레드 로컬 스택을 사용하여 다음과 같이 뭔가를 확장하는 것은 시작이 될 수 있습니다. 이 인터셉터로의 마지막 호출이 동일한 객체를 대상으로했을 때 proceed()을 호출하고 인터셉터를 우회합니다. 이것이 인터셉터에 처음으로 호출되거나 마지막 호출이 같은 오브젝트를 대상으로하지 않으면 인터셉터를 적용합니다.

그러면 인터셉터가 활성화되었을 때 적용하려는 실제 논리는 doInvoke()이됩니다.

사용 예제 :

public class NonReentrantTester { 

    public static void main(String[] args) { 
     Injector injector = Guice.createInjector(new Module()); 
     MyClass instance = injector.getInstance(MyClass.class); 
     instance.foo(); 
    } 

    static class Module extends AbstractModule { 

     @Override 
     protected void configure() { 
      bindInterceptor(Matchers.any(), Matchers.annotatedWith(PrintsFirstInvocation.class), 
        new PrintsFirstInvocationInterceptor()); 
     } 
    } 

    public static class MyClass { 
     @PrintsFirstInvocation 
     void foo() { 
      bar(); 
     } 

     @PrintsFirstInvocation 
     void bar() { 
     } 
    } 


    public static class PrintsFirstInvocationInterceptor extends NonReentrantMethodInterceptor { 

     @Override 
     protected Object doInvoke(MethodInvocation invocation) throws Throwable { 
      System.out.println(invocation.getMethod()); 
      return invocation.proceed(); 
     } 
    } 

    @BindingAnnotation 
    @Target({FIELD, PARAMETER, METHOD}) 
    @Retention(RUNTIME) 
    public @interface PrintsFirstInvocation { 
    } 

} 
+0

와우, 아주 깔끔한입니다. 다행히도 첫 번째 (그리고보다 우아한) 솔루션은 쉽게 사용할 수 있지만, 내가 제안한 것이 다소 깔끔하다고 생각합니다. –

+0

음, 방법은 내 응용 프로그램은 내가 첫 번째 요청 @Override 공공 개체 호출 (MethodInvocation을 호출)의 Throwable { 가 발생합니다 this.inProcess.get을 필터링하는 걱정 때문에 아마 비슷한 일을 멀리 얻을 수있는 구조입니다(), invocation.getMethod()); 정수 inProcess = this.inProcess.get(); { if (inProcess == null) { this.inProcess.set (1); 시도하십시오. return doInvoke (invocation); } else { this.inProcess.set (inProcess + 1); return invocation.proceed(); } } finally { this.inProcess.set (inProcess); } } –

+0

여기서 개인 최종 ThreadLocal inProcess = new ThreadLocal (); –

관련 문제