Class AnnotationLookupCache<A extends Annotation>

java.lang.Object
com.ultikits.ultitools.aop.AnnotationLookupCache<A>
Type Parameters:
A - the annotation type this cache answers for

public final class AnnotationLookupCache<A extends Annotation> extends Object
Memoizes the two annotation lookups in AopAdvisor for one annotation type.

Both walk the superclass chain, and both run on every intercepted invocation - from the advisor's match test and again from the interceptor that reads the annotation's attributes. On a Minecraft server that is tick-rate work for answers that cannot change once the classes are loaded. This is the split Spring uses between AbstractFallbackTransactionAttributeSource's caching getTransactionAttribute and its uncached computeTransactionAttribute.

The class-level cache is keyed on the method's declaring class, the only input that lookup reads. The method-level cache has to be keyed on the method itself, because the answer depends on the whole override chain; the generated proxy hands the same Method instance to every invocation, so the key is cheap.

Instances are deliberately not static. A cache holds Class and Method references, and a static one would pin every module's classes for the lifetime of the JVM, defeating the plugin class loader's release on unload. One instance per advisor or interceptor means the cache dies with the container that owns it.

One shared instance per annotation type, not one per consumer (D-38). The annotation advisor and ExceptionInterceptor used to each construct their own cache for the same annotation type, duplicating the class-level and inherited-method maps for no reason - the answers do not depend on which consumer is asking. PluginManager.wireAop now builds one instance per annotation type and injects it into both at the seam where the advisor is constructed and registered. Public so that seam, in a different package, can construct and pass one; this type does not exist in the released 6.2.5 artifact (D-34), so widening its visibility costs nothing today.

Sharing the cache does not merge what each consumer asks it. The advisor's match collapses own-and-inherited into a single presence check; ExceptionInterceptor needs own-method, then class-level, then inherited-method, with class-level ranking between the other two. Both keep asking their own question - only the memoized answers are shared.

Since:
6.3.0
Author:
wisdomme
  • Field Details

    • NONE

      private static final Object NONE
      Stands in for "looked up, found nothing", so a miss is cached as cheaply as a hit.
    • annotationType

      private final Class<A extends Annotation> annotationType
    • methodLevel

      private final Map<Method,Object> methodLevel
    • inherited

      private final Map<Method,Object> inherited
    • classLevel

      private final Map<Class<?>,Object> classLevel
    • classLevelOwner

      private final Map<Class<?>,Object> classLevelOwner
  • Constructor Details

    • AnnotationLookupCache

      public AnnotationLookupCache(Class<A> annotationType)
      Parameters:
      annotationType - the annotation type this cache answers for
  • Method Details

    • standalone

      static <A extends Annotation> AnnotationLookupCache<A> standalone(Class<A> annotationType)
      An unshared cache for a caller that is not wired through PluginManager.wireAop's shared seam (D-38) - standalone construction, or a consumer exercised in isolation. Production wiring never calls this: it builds one instance and injects it into both the advisor and ExceptionInterceptor explicitly, so the two share it instead of each building their own.
      Type Parameters:
      A - the annotation type
      Parameters:
      annotationType - the annotation type this cache answers for
      Returns:
      a new, unshared cache instance
    • ownMethod

      A ownMethod(Method method)
      Parameters:
      method - the method being considered, may be null
      Returns:
      the annotation on this method or on a declaration it overrides, or null
    • inheritedMethod

      A inheritedMethod(Method method)
      Parameters:
      method - the method being considered, may be null
      Returns:
      the annotation on a declaration this method overrides, or null
    • methodLevel

      A methodLevel(Method method)
    • classLevel

      A classLevel(Method method)
      Parameters:
      method - the method being considered, may be null
      Returns:
      the governing class-level annotation, or null
    • classLevelOwner

      Class<?> classLevelOwner(Method method)
      The class whose type-level annotation governs the method, cached the same way classLevel(Method) is - same key, same walk, just returning the owner instead of the annotation instance. AopProxyResolver.locateAnnotation used to call AopAdvisor.findClassLevelAnnotationOwner directly here, uncached, on every method of every unavailable-annotation entry (D-38).
      Parameters:
      method - the method being considered, may be null
      Returns:
      the class carrying the governing class-level annotation, or null
    • unwrap

      private A unwrap(Object cached)