【问题标题】:Spring aspects intercept annotated interfaceSpring切面拦截注解接口
【发布时间】:2017-10-04 15:03:25
【问题描述】:

编辑

鉴于它们实现的接口上存在注释,如何拦截 Spring bean 上的任何一种方法?

我添加了这个,因为它更准确地描述了试图解决的实际问题。以下是我解决问题的尝试。然而,完全不同的方法是可以接受的。

原始问题

给定以下类

@Timed
public static interface TimedInterface {
        
        
        public void interfaceMethod();
}
    
public static class TimedInterfaceImplementation implements TimedInterface {

        @Override
        public void interfaceMethod() {
            //NO-OP
        }
        
}

@Advise的哪些实现可以通过检测@Timed注解来拦截对interfaceMethod的方法调用。

我当前版本的spring aspect是

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class TimingAspect {

    private TimerContext timerContext;

    public TimingAspect(TimerContext ctx) {
        this.timerContext = ctx;
    }

    @Pointcut(value = "@within(timed)")
    public void beanAnnotatedWithTimer(Timed timed) {}
    
    @Pointcut("execution(public * *(..))")
    public void publicMethod() {}
    
    @Pointcut("publicMethod() && beanAnnotatedWithTimer(timed)")
    public void publicMethodInsideAClassMarkedWithAtTimer(Timed timed) {}
    
    @Around(value = "execution(public * *+.*(..))"
            + " && @annotation(timed)", argNames = "timed")
    public Object aroundAnnotatedMethod(final ProceedingJoinPoint joinPoint, Timed timed) throws Throwable {
        return timerContext.runThrowable(joinPoint.getSignature().getName(), joinPoint::proceed);
    }
    
    
    @Around(value = "publicMethodInsideAClassMarkedWithAtTimer(timed)", argNames="timed")
    public Object aroundAnnotatedClass(final ProceedingJoinPoint joinPoint, Timed timed) throws Throwable {
        return timerContext.runThrowable(joinPoint.getSignature().getName(), joinPoint::proceed);
    } 
    
    /**
     * This is here to ensure that the correct annotation is imported.
     * 
     * It allows for refactoring. I'm not expecting this method to actually get
     * called anywhere.
     * 
     * @return The fully qualified name of the {@link Timer} annotation.
     */
    public String annotationName() {
        return Timed.class.getName();
    }

}

这适用于具体注释的类(类型注释)。 这适用于带有类的注释方法(方法注释)。

但是我想改变它以适用于所有实现带注释接口的方法。

请注意,我不介意从 aspectj 风格的建议切换到任何其他风格。但是,spring 创建了一些没有具体类的 bean,我需要我的 Timing 代码来拦截它们。

Emulate annotation inheritance for interfaces and methods with AspectJ 不起作用,因为这意味着将处理器写入每个接口。

Spring aspect call on custom annotation on interface method 表示这是不可能的。但是我知道 spring 会为 @Transactional 以及其他注释执行此操作。

【问题讨论】:

    标签: java spring-aop interceptor


    【解决方案1】:

    可以使用 PointcutAdvisor拦截带注释的接口上的方法调用。

    也许可以通过切入点表达式来完成,但我无法让它工作,因为类从接口继承类型级别的注释。

    解决方案是实现一个抽象切入点顾问并将其作为 bean 添加到 Spring 应用程序上下文中。

    这在很大程度上受到http://blog.javaforge.net/post/76125490725/spring-aop-method-interceptor-annotation 的博客文章的启发

    注意:此实现与一些内部类耦合,但应该易于泛化以使用自己的注释或执行不同的建议。

    注意:此实现与 spring 耦合,但这就是重点。

    注意: 与所有 spring 实现一样,这是基于代理的,因此它不适用于 self 调用,也不适用于私有成员,而且它只会代理 spring beans(作为它的做代理的框架)

    没有 cmets 的实现

    如果您需要快速获得答案,此实现应该更易于扫描阅读。

    如果您需要导入,请查看完整的课程。

    public class TimingAdvisor extends AbstractPointcutAdvisor {
    
        private static final long serialVersionUID = 1L;
    
        private final MethodInterceptor interceptor;
        private final StaticMethodMatcherPointcut pointcut = new TimingAnnotationOnClassOrInheritedInterfacePointcut();
    
        public TimingAdvisor(TimerContext timerContext) {
            super();
            this.interceptor = (MethodInvocation invocation) -> timerContext.runThrowable(invocation.getMethod().getName(),
                    invocation::proceed);
        }
    
        @Override
        public Pointcut getPointcut() {
            return this.pointcut;
        }
    
        @Override
        public Advice getAdvice() {
            return this.interceptor;
        }
    
        private final class TimingAnnotationOnClassOrInheritedInterfacePointcut extends StaticMethodMatcherPointcut {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                if (AnnotationUtils.findAnnotation(method, Timed.class) != null) {
                    return true;
                }
                return AnnotationUtils.findAnnotation(targetClass, Timed.class) != null;
            }
        }
    }
    

    实施

    import java.lang.reflect.Method;
    
    import org.aopalliance.aop.Advice;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    import org.springframework.aop.Pointcut;
    import org.springframework.aop.support.AbstractPointcutAdvisor;
    import org.springframework.aop.support.StaticMethodMatcherPointcut;
    import org.springframework.core.annotation.AnnotationUtils;
    
    /**
     * <p>
     * Intercepts all calls to beans with methods annotated with {@link Timed}.
     * </p>
     * 
     * <p>
     * The following use cases have been tested.
     * </p>
     * <ul>
     * <li>Nested invocation Timed bean invokes another TimedBean.</li>
     * <li>Annotated class.</li>
     * <li>Annotated method on a class.</li>
     * <li>Class implementing annotated interface.</li>
     * <li>Class implementing an Interface with an annotated method</li>
     * </ul>
     * 
     * <p>
     * Calls to timed methods will be passed though
     * {@link TimerContext#runThrowable(String, TimerContext.ThrowableSupplier)}
     * </p>
     * 
     * 
     * <strong>Important Notes and Limitations</strong>
     * 
     * <ul>
     * <li>This will only work with Spring beans as its using spring own advising
     * mechanism.</li>
     * <li>This will only work with public method invocations as with all of springs
     * proxies.</li>
     * <li>This will not work for self calls.</li>
     * </ul>
     * <p>
     * The limitations are described in further details in the <a href=
     * "https://docs.spring.io/spring/docs/3.2.4.RELEASE/spring-framework-reference/html/aop.html#aop-proxying">spring
     * manual</a>.
     * 
     */
    public class TimingAdvisor extends AbstractPointcutAdvisor {
    
        private static final long serialVersionUID = 1L;
    
        private final MethodInterceptor interceptor;
        private final StaticMethodMatcherPointcut pointcut = new TimingAnnotationOnClassOrInheritedInterfacePointcut();
    
        /**
         * Constructor.
         * 
         * @param timerContext
         *            The context where the timing will be run on.
         */
        public TimingAdvisor(TimerContext timerContext) {
            super();
            this.interceptor = (MethodInvocation invocation) -> timerContext.runThrowable(invocation.getMethod().getName(),
                    invocation::proceed);
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.aop.PointcutAdvisor#getPointcut()
         */
        @Override
        public Pointcut getPointcut() {
            return this.pointcut;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.aop.Advisor#getAdvice()
         */
        @Override
        public Advice getAdvice() {
            return this.interceptor;
        }
    
        /**
         * A matcher that matches:
         * <ul>
         * <li>A method on a class annotated with Timed.</li>
         * <li>A method on a class extending another class annotated with
         * Timed.</li>
         * <li>A method on a class implementing an interface annotated with
         * Timed.</li>
         * <li>A method implementing a method in a interface annotated with
         * Timed.</li>
         * </ul>
         * 
         * <p>
         * <strong>Note:</strong> this uses springs utils to find the annotation and will not be
         * portable outside the spring environment.
         * </p>
         */
        private final class TimingAnnotationOnClassOrInheritedInterfacePointcut extends StaticMethodMatcherPointcut {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                if (AnnotationUtils.findAnnotation(method, Timed.class) != null) {
                    return true;
                }
                return AnnotationUtils.findAnnotation(targetClass, Timed.class) != null;
            }
        }
    }
    

    测试用例

    请注意,此测试用例准确地测试了所需的结果,并且特定于我正在运行的应用程序的需求。根据我的具体需要,所需的实现是向guage service 提交时间。

    import static org.assertj.core.api.Assertions.assertThat;
    import static org.assertj.core.api.Assertions.assertThatThrownBy;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.actuate.metrics.GaugeService;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @RunWith(SpringRunner.class)
    @ContextConfiguration(classes = TimerContextTest.ContextConfig.class)
    public class TimerContextTest {
    
        @Autowired
        private TimedClassA timedClass;
    
        @Autowired
        private RecordingGaugeService gaugeService;
    
        @Autowired
        private ClassWithTimedMethod partiallyTimed;
        
        @Autowired
        private TimedInterface timedInterface;
        
        @Autowired
        private PartiallyTimedInterface partiallyTimedInterface;
    
        @Before
        public void setup() {
            gaugeService.clear();
        }
    
        @Test
        public void mustRetainHirachy() {
            timedClass.outer();
            assertThat(gaugeService.entries()).hasSize(2).contains("timer.outer", "timer.outer.inner");
        }
    
        @Test
        public void mustNotBeInvokedOnPrivateMethods() {
            timedClass.somethingPrivate();
            assertThat(gaugeService.entries()).isEmpty();
        }
        
    
        @Test
        public void mustBeInvokedForMethodsAnnotatedWithTimed() {
    
            String untimed = partiallyTimed.untimed();
            assertThat(untimed).isEqualTo("untimed result");
            assertThat(gaugeService.entries()).isEmpty();
    
            String timed = partiallyTimed.timed();
            assertThat(timed).isEqualTo("timed result");
            assertThat(gaugeService.entries()).containsExactly("timer.timed");
    
            assertThatThrownBy(() -> {
                partiallyTimed.timedExceptionThrower();
            }).hasMessage("timedExceptionThrower");
            assertThat(gaugeService.entries()).containsExactly("timer.timed", "timer.timedExceptionThrower");
    
        }
    
        @Test
        public void mustBeInvokedAsTopLevelMoreThanOnce() {
            partiallyTimed.timed();
            partiallyTimed.timed();
            assertThat(gaugeService.entries()).containsExactly("timer.timed", "timer.timed");
        }
        
        
        @Test
        public void mustTimeInterfaceImplementations() {
            timedInterface.interfaceMethod();
            assertThat(gaugeService.entries()).containsExactly("timer.interfaceMethod");
        }
        
        @Test
        public void mustTimeAnnotatedInterfaceMethods() {
            partiallyTimedInterface.timedMethod();
            partiallyTimedInterface.untimedMethod();
            partiallyTimedInterface.timedDefaultMethod();
            partiallyTimedInterface.untimedDefaultMethod();
            assertThat(gaugeService.entries()).containsExactly("timer.timedMethod", "timer.timedDefaultMethod");
        }
        
        //////////////////////////////
        // Configuration and Helpers
        //////////////////////////////
        @Configuration
        @EnableAspectJAutoProxy
        public static class ContextConfig {
    
            @Bean
            public GaugeService gaugeService() {
                return new RecordingGaugeService();
            }
    
            @Bean
            public TimerContext timerContext(GaugeService gaugeService) {
                return new TimerContext(gaugeService);
            }
    
            @Bean
            public TimedClassB inner() {
                return new TimedClassB();
            }
    
            @Bean
            public TimedClassA outer(TimedClassB inner) {
                return new TimedClassA(inner);
            }
    
            @Bean
            public TimingAdvisor timingAdvisor(TimerContext ctx) {
                return new TimingAdvisor(ctx);
            }
    
            @Bean
            public ClassWithTimedMethod partiallyTimed() {
                return new ClassWithTimedMethod();
            }
            
            @Bean
            public TimedInterface timedInterface() {
                return new TimedInterfaceImplementation();
            }
            
            @Bean
            public PartiallyTimedInterface partiallyTimedInterface() {
                return new ClassImplementingPartiallyTimedInterface();
            }
            
    
        }
    
        @Timed
        public static class TimedClassA {
    
            private TimedClassB inner;
    
            public TimedClassA(TimedClassB inner) {
                this.inner = inner;
            }
    
            public String outer() {
                return this.inner.inner();
            }
    
            private String somethingPrivate() {
                return "private";
            }
        }
    
        @Timed
        public static class TimedClassB {
    
            public String inner() {
                return "inner";
            }
        }
        
        @Timed
        public static interface TimedInterface {
            public void interfaceMethod();
        }
        
        
        public static class TimedInterfaceImplementation implements TimedInterface {
    
            @Override
            public void interfaceMethod() {
                //NO-OP
            }
            
        }
        
        public static interface PartiallyTimedInterface {
            @Timed public void timedMethod();
            public void untimedMethod();
            
            @Timed public default void timedDefaultMethod() {}
            public default void untimedDefaultMethod() {}
        }
        
        public static class ClassImplementingPartiallyTimedInterface implements PartiallyTimedInterface {
    
            @Override
            public void timedMethod() {
                // NO-OP
            }
    
            @Override
            public void untimedMethod() {
                // NO-OP
            }
            
        }
    
        public static class ClassWithTimedMethod {
    
            public String untimed() {
                return "untimed result";
            }
    
            @Timed
            public String timed() {
                return "timed result";
            }
    
            @Timed
            public String timedExceptionThrower() {
                throw new IllegalStateException("timedExceptionThrower");
            }
        }
    
        private static class RecordingGaugeService implements GaugeService {
    
            private List<String> recordedMetrics = new ArrayList<>();
    
            @Override
            public void submit(String metricName, double value) {
                this.recordedMetrics.add(metricName);
                System.out.println(metricName);
            }
    
            public void clear() {
                recordedMetrics = new ArrayList<>();
            }
    
            public List<String> entries() {
                return recordedMetrics;
            };
    
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多