Interface AopAdvisor

All Known Implementing Classes:
AopAdvisor.AnnotationAopAdvisor

public interface AopAdvisor
An advisor that combines a pointcut (when to apply) with advice (what to do).

Advisors determine which methods should be intercepted and provide the interceptor to apply to those methods.

Since:
6.2.0
Author:
wisdomme
  • Method Details

    • findMethodLevelAnnotation

      static <A extends Annotation> A findMethodLevelAnnotation(Method method, Class<A> annotationType)
      Finds the method-level annotation that governs a method, or null if none does.

      Looks at the method's own declaration first, then at the declarations it overrides. Java does not inherit method annotations, and the scan keeps only the most derived declaration of each overridable method, so without the second step an override hides its superclass's annotation completely. Spring's attribute lookup falls back from the target method to the declaring method for the same reason.

      Overriding is transitive, so the walk tests each candidate against the whole chain collected so far rather than against the leaf - see the comment in the body for the case that makes the difference.

      Exposed and shared so that the match test and the interceptors that read the annotation's attributes cannot answer differently. See issue #309.

      Type Parameters:
      A - the annotation type
      Parameters:
      method - the method being considered
      annotationType - the annotation to look for
      Returns:
      the governing annotation, or null
    • findInheritedMethodAnnotation

      static <A extends Annotation> A findInheritedMethodAnnotation(Method method, Class<A> annotationType)
      Finds the annotation on a declaration this method overrides, ignoring its own declaration.

      Split out because precedence differs from presence. Deciding whether to intercept only asks whether an annotation exists anywhere; deciding which annotation supplies the attributes has an order, and Spring's is: the method itself, then the target class, then the declaration the method overrides, then that declaration's class. A subclass that writes its own class-level annotation outranks a method-level one it inherited, because the subclass author is the one closer to the bean.

      Type Parameters:
      A - the annotation type
      Parameters:
      method - the method being considered
      annotationType - the annotation to look for
      Returns:
      the annotation on an overridden declaration, or null
    • findClassLevelAnnotation

      static <A extends Annotation> A findClassLevelAnnotation(Method method, Class<A> annotationType)
      Finds the class-level annotation that governs a method, or null if none does.

      The lookup is anchored at the method's declaring class and walks up from there. A class-level annotation is a default for the class that declares it and for that class's subclasses; it does not apply to ancestors. A bean therefore does not extend its own annotation's reach over everything it inherits - notably not over the framework base classes it extends, where swallowing an exception would turn into a null that resurfaces as an unrelated failure far from its cause. This is the rule Spring documents for a class-level @Transactional, and inherited methods must be locally redeclared to pick up a subclass's annotation.

      Type Parameters:
      A - the annotation type
      Parameters:
      method - the method being considered
      annotationType - the annotation to look for
      Returns:
      the governing annotation, or null
    • findClassLevelAnnotationOwner

      static Class<?> findClassLevelAnnotationOwner(Method method, Class<? extends Annotation> annotationType)
      The class whose type-level annotation governs the method, or null if none does.

      Same walk as findClassLevelAnnotation(Method, Class), exposed separately because a caller that has to name the offending class in a message needs the class rather than the annotation. Deriving it a second time in the caller is how two copies of one rule start disagreeing.

      Parameters:
      method - the method being considered
      annotationType - the annotation to look for
      Returns:
      the class carrying it, or null
    • isExcludedFromClassLevel

      static boolean isExcludedFromClassLevel(Method method)
      Whether a class-level annotation must never cover this method.

      Intercepting one of these swaps a visible exception for a silent wrong answer. An @ExceptionCatch that swallows equals returns false, and the caller's HashMap then fails to find a key it does hold; a propagating exception at least reaches someone. canEqual is Lombok's equals collaborator - the proxy overrides it and the superclass equals reaches it through virtual dispatch, so excluding equals without excluding canEqual excludes nothing.

      toString is deliberately absent: swallowing it costs a log line rather than a wrong answer, and "the logging statement itself threw" is a case @ExceptionCatch(silent = true) exists for. A method-level annotation on any of these three is still honoured, because the author named it explicitly.

      This lives in the pointcut rather than in the code that collects methods, so that the answer given while building the proxy and the answer given on each invocation are the same answer. Enforcing it only while collecting left a second advisor able to pull hashCode into the proxy, after which this advisor's class-level branch matched it again at invocation time and swallowed the exception into a 0. Spring reaches the same end differently, by routing equals and hashCode to callbacks that structurally cannot run advice.

      Parameters:
      method - the candidate method
      Returns:
      true if class-level coverage must skip it
    • matches

      boolean matches(Method method, Class<?> targetClass)
      Determines if this advisor applies to the given method.
      Parameters:
      method - the candidate method
      targetClass - the target class - the bean class the proxy is built for, which is what Spring's MethodMatcher means by the same parameter name. It is not the method's declaring class; for an inherited method those differ. Both call sites pass the same value at collection time and at invocation time, so an advisor's two answers cannot disagree. See issue #309.
      Returns:
      true if this advisor should intercept the method
    • getInterceptor

      MethodInterceptor getInterceptor()
      Gets the interceptor to apply for matching methods.
      Returns:
      the method interceptor
    • getOrder

      default int getOrder()
      Gets the order of this advisor. Lower values have higher priority.

      Default order is 0. Transaction advisors typically use order 100, exception handling uses order 200.

      Returns:
      the order value
    • getAnnotationType

      default Class<? extends Annotation> getAnnotationType()
      The annotation this advisor serves, for annotation-coverage checks.

      AopProxyResolver.validateAnnotationCoverage() uses this to prove that every annotation AopEligibility recognises is either served by a registered advisor or declared unavailable. An advisor whose pointcut is not annotation-based (a custom matches implementation) has nothing truthful to report here and should leave the default in place.

      Returns:
      the annotation type this advisor matches, or null if its pointcut logic is not annotation-based
    • forAnnotation

      static AopAdvisor forAnnotation(Class<? extends Annotation> annotationType, MethodInterceptor interceptor, int order)
      Creates an advisor that matches methods or classes with the given annotation.

      Builds its own, unshared AnnotationLookupCache. A caller that also constructs an ExceptionInterceptor (or any other consumer) for the same annotation type should use forAnnotation(Class, MethodInterceptor, int, AnnotationLookupCache) instead and inject one shared instance into both (D-38).

      Parameters:
      annotationType - the annotation to match
      interceptor - the interceptor to apply
      order - the advisor order
      Returns:
      a new annotation-based advisor
    • forAnnotation

      static AopAdvisor forAnnotation(Class<? extends Annotation> annotationType, MethodInterceptor interceptor, int order, AnnotationLookupCache<? extends Annotation> sharedCache)
      Creates an advisor that matches methods or classes with the given annotation, using a caller-supplied AnnotationLookupCache instead of building its own.

      PluginManager.wireAop is the intended caller: it builds one cache instance per annotation type and passes the same instance here and to the matching ExceptionInterceptor, so the class-level and inherited-method lookups are memoized once for both consumers instead of twice (D-38). Sharing the cache does not change what either consumer asks it - this advisor's AopAdvisor.AnnotationAopAdvisor.matches(java.lang.reflect.Method, java.lang.Class<?>) still collapses own-and-inherited into a single presence check.

      Parameters:
      annotationType - the annotation to match
      interceptor - the interceptor to apply
      order - the advisor order
      sharedCache - the cache to use instead of constructing a new one
      Returns:
      a new annotation-based advisor