Наследование аннотации для АОП в GUICE
Я использую Guice и AspectJ, и я пытаюсь выполнить АОП, чтобы измерить время выполнения определенных методов.
У меня есть эта аннотация, которая будет использоваться для аннотирования всех методов, которые мне нужно измерить:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MyStopWatch {
}
У меня есть такой метод-перехватчик:
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;
}
}
У меня есть этот интерфейс
public interface MySuperClass {
@MyStopWatch
default void test() {
System.out.println("Hello, world");
}
}
Затем у меня есть подкласс, наследуемый от MySuperClass:
public class MySubClass implements MySuperClass {
}
И напоследок вот такая привязка:
public class MyAOPModule extends AbstractModule {
@Override
protected void configure() {
bindInterceptor(
Matchers.any(),
Matchers.annotatedWith(MyStopWatch.class),
new MyInterceptor()
);
}
}
Я инициализирую свой модуль Guice следующим образом:
public static void main(String[] args) throws Throwable {
Injector injector = createInjector(new MyAOPModule());
injector.getInstance(MySubClass.class).test();
}
Моя проблема в том, что ничего не регистрируется, как будто выполнение метода test() подклассом не было аннотировано.
Как я могу это обойти?
1 ответ
Это известная и старая проблема, и вряд ли она скоро будет исправлена.
Существует несколько способов избежать этого, но ни один из них не является стандартным. Отсюда следует тот, у кого больше всего достоинств: 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());
}
И HasOrInheritsAnnotation
class (я попытался решить любую отсутствующую общую проблему, если она не устранена, отредактируйте этот ответ, чтобы исправить это):
/**
* 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();
}
}
}