【问题标题】:Conditional matching in Google GuiceGoogle Guice 中的条件匹配
【发布时间】:2012-09-13 02:32:45
【问题描述】:

我有一个 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 调用不被接收?

【问题讨论】:

    标签: java dependency-injection aop guice


    【解决方案1】:

    说实话,最简单的解决方案是通过从不从类中调用同一类的另一个公共/注释方法来简单地避免该问题:

    public class MyClass {
      @MyAnnotation
      public void foo() {
         doBar();
      }
    
      @MyAnnotation
      public void bar() {
         doBar();
      }
    
      private void doBar() {
         //doesn't go through interceptor
      }
    }
    

    如果由于某种原因这不是一个选项,那么您可能会考虑这种方法。更具表现力的 AOP 库(如 AspectJ)为您定义切入点提供了更高级别的灵活性。

    在 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 {
        }
    
    }
    

    【讨论】:

    • 哇,这很整洁。幸运的是,这两个第一个(也是更优雅的)解决方案很容易获得,但我确实认为你的建议相当简洁。
    • 好吧,我的应用程序的结构方式,我可能会做一些类似的事情,因为我只关心过滤第一个请求 @Override public Object invoke(MethodInvocation invocation) throws Throwable { this. inProcess.get(), invocation.getMethod());整数 inProcess = this.inProcess.get();尝试 { if (inProcess == null) { this.inProcess.set(1);返回doInvoke(调用); } else { this.inProcess.set(inProcess + 1);返回调用.proceed(); } } 最后 { this.inProcess.set(inProcess); } }
    • where private final ThreadLocal inProcess = new ThreadLocal();
    猜你喜欢
    • 2023-04-04
    • 2020-10-27
    • 2012-08-28
    • 2022-01-12
    • 2011-08-18
    • 2021-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多