Class AopProxyResolver

java.lang.Object
com.ultikits.ultitools.aop.AopProxyResolver

public class AopProxyResolver extends Object
Decides which class the container should instantiate for a bean.

This replaces the former AopProxyBeanPostProcessor. Proxy creation happens before the bean exists, so it cannot be a BeanPostProcessor: both of that interface's callbacks take an already-constructed instance. The container asks this resolver for a class, instantiates it through the normal constructor paths, and the resulting object is the bean. There is no second instance.

Since:
6.3.0
Author:
wisdomme
  • Field Details

    • LOGGER

      private static final Logger LOGGER
    • resolvedClasses

      private final Map<Class<?>,Class<?>> resolvedClasses
      Bean class to the class the container should instantiate for it.

      resolve is called from SimpleContainer.createBean, which runs once per instantiation - so for a prototype-scoped bean it ran on every getBean(), repeating three hierarchy scans, re-emitting the "annotation ignored" warning that the javadoc calls a startup warning, and asking ByteBuddy for a brand new proxy class each time. The answer depends only on the bean class and this resolver's advisors, so it is computed once. Spring caches generated proxy classes for the same reason.

      Only successful resolutions are stored. A refusal re-runs and re-throws, which costs nothing because it fails the module load anyway.

      Instance-scoped, like the annotation caches: it holds Class references and must die with the container that owns it rather than pin a module's classes for the life of the JVM.

    • configGeneration

      private final AtomicLong configGeneration
      Bumped by every change to the advisor set or the unavailable-annotation map.

      Clearing the memo after a mutation closes only one of the two interleavings. The other is a resolve() that read the old configuration, was overtaken by a mutation and its clear, and then stored its now-stale answer - which nothing would clear again. A resolution is kept only if the configuration it was computed against is still current.

    • advisors

      private final List<AopAdvisor> advisors
    • unavailableAnnotations

      private final Map<Class<? extends Annotation>,String> unavailableAnnotations
      Annotations the framework recognises but deliberately does not implement in this release, mapped to the reason. A bean carrying one is rejected rather than handed back unproxied, because an unproxied bean is indistinguishable from a working annotation.
    • lookupCaches

      private final Map<Class<? extends Annotation>,AnnotationLookupCache<?>> lookupCaches
      Per-annotation-type caches this resolver owns for its own class-level lookups. Instance- scoped like resolvedClasses: it holds Class references and must die with the container that owns this resolver, not pin a module's classes for the life of the JVM.
  • Constructor Details

    • AopProxyResolver

      public AopProxyResolver()
  • Method Details

    • addUnavailableAnnotation

      public void addUnavailableAnnotation(Class<? extends Annotation> type, String reason)
      Declares an annotation as recognised but unavailable.
      Parameters:
      type - the annotation type
      reason - what the module author should know, including where to follow up
    • validateAnnotationCoverage

      public void validateAnnotationCoverage()
      Verifies that every recognised AOP annotation is accounted for.

      Each annotation must either have a registered advisor that can intercept it, or be declared unavailable so that beans using it are rejected outright. An annotation that is neither is the silent-failure case this framework is removing: beans carrying it would be proxied for their other annotations while this one quietly does nothing.

      Throws:
      ContainerException - if any recognised annotation is neither served nor declared
    • addAdvisor

      public void addAdvisor(AopAdvisor advisor)
      Adds an advisor to be considered during proxy resolution.
      Parameters:
      advisor - the advisor to add
    • removeAdvisor

      public boolean removeAdvisor(AopAdvisor advisor)
      Removes an advisor.
      Parameters:
      advisor - the advisor to remove
      Returns:
      true if the advisor was removed
    • getAdvisors

      public List<AopAdvisor> getAdvisors()
      Gets all registered advisors, ordered by AopAdvisor.getOrder().
      Returns:
      a copy of the advisor list
    • resolve

      public Class<?> resolve(Class<?> beanClass)
      Resolves the class the container should instantiate for the given bean class.
      Parameters:
      beanClass - the declared bean class
      Returns:
      the same class when no interception is needed, otherwise a generated subclass
      Throws:
      ContainerException - if the class requests interception but cannot be proxied
    • invalidate

      private void invalidate()
      Marks every memoized resolution as computed against a configuration that no longer holds.
    • memoize

      private Class<?> memoize(Class<?> beanClass, Class<?> resolved, long generation)
      Stores a resolution, but only if the configuration it was computed against is still the current one. Returns the resolution either way - it is correct for this call, just not necessarily for the next one.
    • rejectUnavailableAnnotations

      private void rejectUnavailableAnnotations(MethodScan scan)
      Throws if the bean uses an annotation declared unavailable.
    • locateAnnotation

      private String locateAnnotation(MethodScan scan, Class<? extends Annotation> type)
      Finds where an annotation appears on the class, or null if it does not.

      The class-level half is routed through this resolver's own per-type AnnotationLookupCache (D-38) - it used to call AopAdvisor.findClassLevelAnnotationOwner directly on every method of every unavailable-annotation entry, uncached. Not the same instance PluginManager.wireAop shares between the advisor and ExceptionInterceptor: an unavailable annotation type is typically one neither of those ever sees, so this resolver owns its own per-type caches rather than assuming the wiring seam already built one for it.

      Returns:
      the class name for a type-level annotation, class#method for a method-level one, or null
    • lookupCacheFor

      private AnnotationLookupCache<?> lookupCacheFor(Class<? extends Annotation> type)
    • collectInterceptedMethods

      private Set<Method> collectInterceptedMethods(MethodScan scan)
      Collects methods matched by at least one advisor, across the whole inheritance hierarchy.
    • warnAboutClassLevelAnnotationsCoveringNothing

      private void warnAboutClassLevelAnnotationsCoveringNothing(MethodScan scan)
      Warns for a class-level annotation that ends up covering no method at all.

      Every other skip in this class is named; this one used to be the exception, and @ExceptionCatch class ServiceImpl extends AbstractService {} is a shape authors write. The annotation is located with the same hierarchy walk everything else uses - testing beanClass.isAnnotationPresent instead missed an annotation inherited from an ancestor, which is the silent no-op this warning exists to remove.

      The reason is derived rather than assumed. Ancestor scope is only one way to cover nothing; a class whose every method is unproxyable, or whose only methods are the excluded signatures, gets there too, and telling that author to "redeclare the method here" would send them after methods already declared there.

    • buildMessage

      private String buildMessage(Class<?> beanClass, List<AopEligibility.Problem> problems)
      Builds a message that names every blocking problem and its remedy.