Class ProxyFactory
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
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static classDispatches intercepted calls to theMethodInterceptorchain. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final List<MethodInterceptor> private static final StringPrefix for the generated super-call trampoline methods; see the class javadoc. -
Constructor Summary
ConstructorsConstructorDescriptionProxyFactory(List<MethodInterceptor> interceptors) Creates a new proxy factory with the given interceptors. -
Method Summary
Modifier and TypeMethodDescription<T> Class<? extends T> createProxyClass(Class<T> targetClass, Set<Method> interceptedMethods) Generates a proxy subclass of the given target.static booleanisProxyClass(Class<?> clazz) Checks whether the given class is a proxy generated by this factory.private static StringtrampolineName(Method method) Deterministic trampoline name for a given method, derived purely from its own name and parameter types so thatcreateProxyClass(java.lang.Class<T>, java.util.Set<java.lang.reflect.Method>)(build time) andProxyFactory.InterceptorDispatcher(call time, reflecting onproxy.getClass()) always agree without sharing any other state.static Class<?> Returns the class a generated proxy subclasses and delegates to.
-
Field Details
-
TRAMPOLINE_PREFIX
Prefix for the generated super-call trampoline methods; see the class javadoc.- See Also:
-
interceptors
-
-
Constructor Details
-
ProxyFactory
Creates a new proxy factory with the given interceptors.- Parameters:
interceptors- the interceptors to apply to proxied methods
-
-
Method Details
-
isProxyClass
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 (orunwrap(Class)) rather than deriving it themselves - seeProxyOf.- Parameters:
clazz- the class to test, may be null- Returns:
- true if the class was generated by this factory
-
unwrap
Returns the class a generated proxy subclasses and delegates to.Reads
ProxyOfdirectly - no hierarchy walk. A proxy of a proxy already carries a marker naming the original target (seecreateProxyClass(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 toObject.class) do not exist here to reconcile.- Parameters:
clazz- the class to unwrap, may be null- Returns:
- the proxied target class if
clazzis a proxy,clazzitself if it is not a proxy, ornullifclazzis 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 subclassinterceptedMethods- 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- ifinterceptedMethodsis nullRuntimeException- if the proxy cannot be generated; see the class javadoc's Limitations section
-
trampolineName
Deterministic trampoline name for a given method, derived purely from its own name and parameter types so thatcreateProxyClass(java.lang.Class<T>, java.util.Set<java.lang.reflect.Method>)(build time) andProxyFactory.InterceptorDispatcher(call time, reflecting onproxy.getClass()) always agree without sharing any other state.
-