-
Notifications
You must be signed in to change notification settings - Fork 0
Enhance constructor invocation handling and matching logic #261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -115,10 +115,7 @@ private Invokable createInvokable(final Method method) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (constructorAnnotation != null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final var handle = sneaky( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| () -> privateLookup.unreflectConstructor(findConstructor(proxiedClass, method))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final boolean hasParams = handle.type().parameterCount() > 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new HandleInvokable(normalizeMethodHandleType(handle), hasParams); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new ConstructorInvokable(proxiedClass, method, privateLookup); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (staticAnnotation == null && method.getParameterCount() == 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -228,11 +225,132 @@ private static Field findField(final Class<?> clazz, final String name) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return field; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static Constructor<?> findConstructor(final Class<?> clazz, final Method method) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throws NoSuchMethodException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Constructor<?> constructor = clazz.getDeclaredConstructor(method.getParameterTypes()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor.setAccessible(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return constructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static Constructor<?> findConstructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Class<?> clazz, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Method method, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Object[] args | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) throws NoSuchMethodException { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Class<?>[] declaredTypes = method.getParameterTypes(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Constructor<?> exact = clazz.getDeclaredConstructor(declaredTypes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exact.trySetAccessible(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return exact; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (final NoSuchMethodException ignored) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Constructor<?>[] constructors = clazz.getDeclaredConstructors(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Constructor<?> best = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int bestScore = Integer.MIN_VALUE; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (final Constructor<?> constructor : constructors) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final Class<?>[] ctorTypes = constructor.getParameterTypes(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (ctorTypes.length != declaredTypes.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final int score = scoreExecutableMatch(ctorTypes, declaredTypes, args); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (score > bestScore) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| best = constructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bestScore = score; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (best == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new NoSuchMethodException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "No compatible constructor found for " + clazz.getName() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| + " with declared parameters " + Arrays.toString(declaredTypes) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| + " and runtime arguments " + runtimeTypesToString(args) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| best.setAccessible(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| best.setAccessible(true); | |
| best.trySetAccessible(); |
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConstructorInvokable.invoke resolves the constructor, unreflects it, and normalizes the method handle on every call. Compared to the previous approach (creating the handle once in createInvokable), this is a noticeable performance regression for hot paths. Consider caching the selected MethodHandle (at least for the exact declared signature, and optionally per runtime-arg-type signature) inside ConstructorInvokable to avoid repeated reflective scans and handle creation.
| @Override | |
| public @Nullable Object invoke(final Object[] args) throws Throwable { | |
| final Constructor<?> constructor = findConstructor(proxiedClass, method, args); | |
| final MethodHandle handle = normalizeMethodHandleType( | |
| privateLookup.unreflectConstructor(constructor) | |
| ); | |
| /** | |
| * Cached method handle for the last-resolved constructor signature. | |
| * This avoids repeated reflective scans and handle creation on hot paths. | |
| */ | |
| private volatile MethodHandle cachedHandle; | |
| /** | |
| * Runtime parameter types corresponding to {@link #cachedHandle}. | |
| */ | |
| private volatile Class<?>[] cachedParamTypes; | |
| @Override | |
| public @Nullable Object invoke(final Object[] args) throws Throwable { | |
| // Determine the current runtime argument types (nulls are represented as Object.class) | |
| final Class<?>[] currentTypes; | |
| if (args.length == 0) { | |
| currentTypes = new Class<?>[0]; | |
| } else { | |
| currentTypes = new Class<?>[args.length]; | |
| for (int i = 0; i < args.length; i++) { | |
| final Object arg = args[i]; | |
| currentTypes[i] = (arg != null) ? arg.getClass() : Object.class; | |
| } | |
| } | |
| MethodHandle handle = cachedHandle; | |
| final Class<?>[] cachedTypes = cachedParamTypes; | |
| // Recompute the handle if we have no cache yet or the runtime signature differs | |
| if (handle == null || cachedTypes == null || !Arrays.equals(cachedTypes, currentTypes)) { | |
| final Constructor<?> constructor = findConstructor(proxiedClass, method, args); | |
| handle = normalizeMethodHandleType( | |
| privateLookup.unreflectConstructor(constructor) | |
| ); | |
| // Publish the new cache (benign races between threads are acceptable) | |
| cachedHandle = handle; | |
| cachedParamTypes = currentTypes; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Constructor selection uses a score, but ties (equal score) currently fall back to the first constructor returned by reflection. Since reflection order isn’t specified, this can make constructor choice nondeterministic/unstable when multiple overloads are equally compatible (e.g., multiple interface-typed params that the runtime arg implements). Consider adding a deterministic tie-breaker (e.g., prefer the most specific parameter types) or detect ties and throw an explicit ambiguity error requiring a more specific proxy method signature.