2

クラスがデータに触れる前に、データに対して簡単なロジックを実行するために、クラスのメソッドにバインドされた MethodInterceptor があります。ただし、クラス自体は、インターセプトされた独自のメソッドのいくつかを呼び出しますが、その時点でそのロジックを再実行する必要はありません。

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 内の bar の呼び出しが受け入れられないようにする方法はありますか?

4

1 に答える 1

3

正直なところ、最も簡単な解決策は、クラス内から同じクラスの別の public/annotated メソッドを呼び出さないことで、問題を単純に回避することです。

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

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

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

何らかの理由でそれができない場合は、このアプローチを検討してください。AspectJ のような表現力の高い AOP ライブラリを使用すると、ポイントカットをより柔軟に定義できます。

Guice では、ポイントカットは、Guice によってインスタンス化されたインスタンスに属しているアノテーションを持つ単なるメソッドです。したがって、このロジックはインターセプター自体に移動する必要があります。

これを行うための 1 つのアプローチは、 a を使用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 {
    }

}
于 2012-09-12T18:18:56.103 に答える