Наследование аннотации для АОП в GUICE - PullRequest
1 голос
/ 08 мая 2020

Я использую 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 () подклассом не было аннотировано.

Я могу go обойти это?

1 Ответ

1 голос
/ 08 мая 2020

Это известная и старая проблема , и вряд ли она скоро будет исправлена.

Существует несколько способов избежать этого, но ни один из них не является стандартным. Отсюда следует тот, который имеет наибольшие достоинства: 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 (я пытался решить любую отсутствующую проблему generi c, если она сохраняется , отредактируйте этот ответ, чтобы исправить это):

/**
 * 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();
    }
  }
}
...