Class ProxyFactory

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

public class ProxyFactory extends Object
Factory for creating inheritance-based subclass proxies.

The generated class is a subclass of the target whose intercepted methods delegate to the MethodInterceptor chain and reach the original body through super. The container instantiates the generated class directly, so the proxy is the bean: there is no second object. This is what makes self-invocation interceptable and keeps fields, the constructor count, and @Autowired injection on a single instance.

Type and method annotations are copied onto the generated class, because type annotations are not @Inherited and overriding methods do not inherit method annotations. Without the copy, every reflective consumer in the framework (command registration, EventBus handler scanning, Bukkit's own @EventHandler scan) would silently stop seeing them.

Cross-class-loader dispatch: the generated class is injected into the target's own class loader (see ProxyFactoryIsolationTest, e.g. a plugin module's URLClassLoader, which is unrelated to this framework's own loader on a live Paper server). That means the intercepted methods' bytecode can only reference types resolvable from the target's loader - so dispatch goes through InvocationHandler, part of java.base and therefore resolvable from any loader, rather than MethodDelegation's @SuperCall binding, which would bake a hard reference to this framework-defined class into the proxy and throw NoClassDefFoundError the moment the target's loader cannot see it. The super call that @SuperCall would otherwise synthesize is instead generated by hand as a same-package, same-signature "trampoline" method per intercepted method, invoked reflectively by name from the dispatcher - see trampolineName(Method).

Limitations (surfaced as a RuntimeException naming the target class rather than a bare ByteBuddy exception or a silent skip; a startup-time eligibility pre-check is planned as AopEligibility):

  • Cannot subclass final classes
  • Cannot override final, private or static methods
  • The target class must have a constructor reachable by the container
Since:
6.3.0
Author:
wisdomme
  • Field Details

    • TRAMPOLINE_PREFIX

      private static final String TRAMPOLINE_PREFIX
      Prefix for the generated super-call trampoline methods; see the class javadoc.
      See Also:
    • interceptors

      private final List<MethodInterceptor> interceptors
  • Constructor Details

    • ProxyFactory

      public ProxyFactory(List<MethodInterceptor> interceptors)
      Creates a new proxy factory with the given interceptors.
      Parameters:
      interceptors - the interceptors to apply to proxied methods
  • Method Details

    • isProxyClass

      public static boolean isProxyClass(Class<?> clazz)
      Checks whether the given class is a proxy generated by this factory.

      Identity is read from ProxyOf, the marker this factory attaches at generation time - not from a name pattern. A previous implementation matched a class-name substring and silently stopped recognising proxies the moment the proxy engine changed (CGLIB to ByteBuddy); reading a declared marker instead of a naming convention is what makes this survive the next engine change too. Callers that need the original class must ask here (or unwrap(Class)) rather than deriving it themselves - see ProxyOf.

      Parameters:
      clazz - the class to test, may be null
      Returns:
      true if the class was generated by this factory
    • unwrap

      public static Class<?> unwrap(Class<?> clazz)
      Returns the class a generated proxy subclasses and delegates to.

      Reads ProxyOf directly - no hierarchy walk. A proxy of a proxy already carries a marker naming the original target (see createProxyClass(java.lang.Class<T>, java.util.Set<java.lang.reflect.Method>)), so this never needs to loop, and the divergent edge cases three previous unwrap implementations disagreed on (proxy-of-proxy, a null superclass, whether to fall back to Object.class) do not exist here to reconcile.

      Parameters:
      clazz - the class to unwrap, may be null
      Returns:
      the proxied target class if clazz is a proxy, clazz itself if it is not a proxy, or null if clazz is null
    • createProxyClass

      public <T> Class<? extends T> createProxyClass(Class<T> targetClass, Set<Method> interceptedMethods)
      Generates a proxy subclass of the given target.

      Instantiation is left to the caller so that the container's existing constructor-injection paths keep working: ByteBuddy copies every constructor of the target class onto the subclass.

      Type Parameters:
      T - the target type
      Parameters:
      targetClass - the class to subclass
      interceptedMethods - the exact methods to intercept; methods outside this set keep the original body and the original annotations. Bridge and synthetic methods are always ignored even if present in this set - they are compiler artifacts of implementing a generic supertype or interface (a raw-erasure overload calling the real method), and generating a super-call trampoline for one throws at build time
      Returns:
      the generated subclass
      Throws:
      NullPointerException - if interceptedMethods is null
      RuntimeException - if the proxy cannot be generated; see the class javadoc's Limitations section
    • trampolineName

      private static String trampolineName(Method method)
      Deterministic trampoline name for a given method, derived purely from its own name and parameter types so that createProxyClass(java.lang.Class<T>, java.util.Set<java.lang.reflect.Method>) (build time) and ProxyFactory.InterceptorDispatcher (call time, reflecting on proxy.getClass()) always agree without sharing any other state.