如果在它们实现的接口上存在注释,那么如何拦截spring bean上的任何方法?
我已添加此内容,因为它更准确地描述了试图解决的实际问题。以下是我尝试解决问题的方法。然而,完全不同的方法是可以接受的。
鉴于以下课程
@Timed
public static interface TimedInterface {
public void interfaceMethod();
}
public static class TimedInterfaceImplementation implements TimedInterface {
@Override
public void interfaceMethod() {
//NO-OP
}
}
@Advise
的哪些实现可以通过检测interfaceMethod
注释来拦截@Timed
的方法调用。
我当前版本的弹簧方面是
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样式建议切换到任何其他。然而,春天创建的一些bean没有具体的类我需要我的Timing代码来拦截。
Emulate annotation inheritance for interfaces and methods with AspectJ不起作用,因为它意味着将处理器写入每个接口。
Spring aspect call on custom annotation on interface method声明这是不可能的。但是我知道spring会为@Transactional
和其他注释做这件事。
答案 0 :(得分:2)
使用 PointcutAdvisor 对拦截带注释的接口上的方法调用进行是可能的。
可能通过切入点表达式但我无法使其工作,因为类不从接口继承类型级别注释。
解决方案是实现一个抽象切入点顾问程序,并将其作为bean添加到spring应用程序上下文中。
这受到http://blog.javaforge.net/post/76125490725/spring-aop-method-interceptor-annotation
上的博客文章的启发注意:这个实现与某些内部类相关联,但是应该很容易通知使用自己的注释或做出不同的建议。
注意:此实现与spring相关联,但这就是重点。
注意:与所有Spring实现一样,这是基于代理的,所以它不适用于自我调用,它不适用于私有成员,而且它只会代理spring bean(因为它的做代理的框架
如果您需要快速获得答案,此实现应该更容易扫描读取。
如果您需要导入,请参阅完整的课程。
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;
};
}
}