问题
I'm using Guice and AspectJ, and I am trying to do some AOP to measure the execution time of certain methods.
I have this annotation which will be used to annotate all the methods I need to measure:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MyStopWatch {
}
I have this method interceptor:
public class MyInterceptor implements org.aopalliance.intercept.MethodInterceptor {
private final Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
final org.apache.commons.lang3.time.StopWatch stopWatch = org.apache.commons.lang3.time.StopWatch.createStarted();
final Object returnedObject = invocation.proceed();
stopWatch.stop();
logger.info("[STOPWATCH] Method: {} - Time: {}.", invocation.getMethod().getName(), stopWatch.getTime());
return returnedObject;
}
}
I have this interface
public interface MySuperClass {
@MyStopWatch
default void test() {
System.out.println("Hello, world");
}
}
Then I have this subclass which inherits from MySuperClass:
public class MySubClass implements MySuperClass {
}
And finally, I have this bind:
public class MyAOPModule extends AbstractModule {
@Override
protected void configure() {
bindInterceptor(
Matchers.any(),
Matchers.annotatedWith(MyStopWatch.class),
new MyInterceptor()
);
}
}
I initialize my Guice module like this:
public static void main(String[] args) throws Throwable {
Injector injector = createInjector(new MyAOPModule());
injector.getInstance(MySubClass.class).test();
}
My problem is that there is nothing being logged, as if the subclass's execution of the test() method was not annotated.
Any way I can go around this?
回答1:
This is a known and old issue and is unlikely to be fixed soon.
There exist several ways to avoid this, but none are standard. The one with the most merit comes from here: https://codingcraftsman.wordpress.com/2018/11/11/google-guice-aop-with-interface-annotations/
// the configure override of a Guice Abstract Module
@Override
protected void configure() {
bindInterceptor(any(),
// an instance of the matcher looking for HTTP methods
new HasOrInheritsAnnotation(GET.class, PATCH.class, PUT.class, POST.class, DELETE.class),
// the method interceptor to bind with
new MyInterceptor());
}
And the HasOrInheritsAnnotation
class (I tried to resolve any missing generic issue, if any persists, please edit this answer to fix it):
/**
* Matcher for methods which either have, or override a method which has a given
* annotation type from the set.
*/
public class HasOrInheritsAnnotation extends AbstractMatcher<Method> {
private final Set<Class<? extends Annotation>> annotationTypes;
/**
* Construct with the types of annotations required
* @param annotationTypes which types of annotation make this matcher match
*/
@SafeVarArgs
public HasOrInheritsAnnotation(Class<? extends Annotation>... annotationTypes) {
this.annotationTypes = ImmutableSet.copyOf(annotationTypes);
}
@Override
public boolean matches(Method method) {
// join this method in a stream with the super methods to fine
// one which has the required annotation
return Stream.concat(
Stream.of(method),
getSuperMethods(method))
.anyMatch(this::hasARequiredAnnotation);
}
/**
* Do any of the annotations on the method appear in our search list?
* @param method the method to inspect
* @return true if any of the annotations are found
*/
private boolean hasARequiredAnnotation(Method method) {
return Arrays.stream(method.getDeclaredAnnotations())
.map(Annotation::annotationType)
.anyMatch(annotationTypes::contains);
}
/**
* Provide a stream of methods from all super types of this
* @param method the method to search with
* @return all methods that are overridden by <code>method</code>
*/
private Stream<Method> getSuperMethods(Method method) {
return streamOfParentTypes(method.getDeclaringClass())
.map(clazz -> getSuperMethod(method, clazz))
.filter(Optional::isPresent)
.map(Optional::get);
}
/**
* A stream of every parent type in the class hierarchy
* @param declaringClass the class to read
* @return a stream of the immediate parent types and their parents
*/
private static Stream<Class<?>> streamOfParentTypes(Class<?> declaringClass) {
return Stream.of(
// all interfaces to the declaring class
Arrays.stream(declaringClass.getInterfaces()),
// all the parent types of those interfaces
Arrays.stream(declaringClass.getInterfaces())
.flatMap(HasOrInheritsAnnotation::streamOfParentTypes),
// the super class of the declaring class
Optional.ofNullable(declaringClass.getSuperclass())
.map(Stream::of)
.orElse(Stream.empty()),
// any parents of the super class
Optional.ofNullable(declaringClass.getSuperclass())
.map(HasOrInheritsAnnotation::streamOfParentTypes)
.orElse(Stream.empty()))
// turn from a stream of streams into a single stream of types
.flatMap(Function.identity());
}
/**
* Search for the given method in a super class. In other words, which
* is the super class method that the given method overrides?
* @param method the method to match
* @param superType the type to scan
* @return an optional with the super type's method in, or empty if not found
*/
private static Optional<Method> getSuperMethod(Method method, Class superType) {
try {
return Optional.of(superType.getMethod(method.getName(), method.getParameterTypes()));
} catch (NoSuchMethodException e) {
// this exception means the method doesn't exist in the superclass
return Optional.empty();
}
}
}
来源:https://stackoverflow.com/questions/61671183/inheriting-annotation-for-aop-in-guice