Skip to content

Commit

Permalink
Introduce getMostSpecificMethod variant on BridgeMethodResolver
Browse files Browse the repository at this point in the history
This is able to resolve the original method even if no bridge method has been generated at the same class hierarchy level (a known difference between the Eclipse compiler and regular javac).

Closes gh-21843
  • Loading branch information
jhoeller committed Jan 7, 2024
1 parent f0e16bd commit 419e34e
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,7 +32,6 @@
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;

/**
* AOP Alliance {@code MethodInterceptor} that processes method invocations
Expand Down Expand Up @@ -101,10 +100,9 @@ public AsyncExecutionInterceptor(@Nullable Executor defaultExecutor, AsyncUncaug
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
final Method userMethod = BridgeMethodResolver.getMostSpecificMethod(invocation.getMethod(), targetClass);

AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
AsyncTaskExecutor executor = determineAsyncExecutor(userMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
Expand All @@ -118,10 +116,10 @@ public Object invoke(final MethodInvocation invocation) throws Throwable {
}
}
catch (ExecutionException ex) {
handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
handleError(ex.getCause(), userMethod, invocation.getArguments());
}
catch (Throwable ex) {
handleError(ex, userDeclaredMethod, invocation.getArguments());
handleError(ex, userMethod, invocation.getArguments());
}
return null;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -199,12 +199,11 @@ public static boolean isFinalizeMethod(@Nullable Method method) {
* @return the specific target method, or the original method if the
* {@code targetClass} doesn't implement it or is {@code null}
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
* @see org.springframework.core.BridgeMethodResolver#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
Class<?> specificTargetClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null);
Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);
// If we are dealing with method with generic parameters, find the original method.
return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
return BridgeMethodResolver.getMostSpecificMethod(method, specificTargetClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -51,7 +51,6 @@
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.SingletonSupplier;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
Expand Down Expand Up @@ -264,8 +263,7 @@ public final Set<ConstraintViolation<Object>> invokeValidatorForArguments(
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
Method mostSpecificMethod = ClassUtils.getMostSpecificMethod(method, target.getClass());
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(mostSpecificMethod);
Method bridgedMethod = BridgeMethodResolver.getMostSpecificMethod(method, target.getClass());
violations = execVal.validateParameters(target, bridgedMethod, arguments, groups);
}
return violations;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -50,43 +50,70 @@
*/
public final class BridgeMethodResolver {

private static final Map<Method, Method> cache = new ConcurrentReferenceHashMap<>();
private static final Map<Object, Method> cache = new ConcurrentReferenceHashMap<>();

private BridgeMethodResolver() {
}


/**
* Find the original method for the supplied {@link Method bridge Method}.
* Find the local original method for the supplied {@link Method bridge Method}.
* <p>It is safe to call this method passing in a non-bridge {@link Method} instance.
* In such a case, the supplied {@link Method} instance is returned directly to the caller.
* Callers are <strong>not</strong> required to check for bridging before calling this method.
* @param bridgeMethod the method to introspect
* @param bridgeMethod the method to introspect against its declaring class
* @return the original method (either the bridged method or the passed-in method
* if no more specific one could be found)
* @see #getMostSpecificMethod(Method, Class)
*/
public static Method findBridgedMethod(Method bridgeMethod) {
if (!bridgeMethod.isBridge()) {
return resolveBridgeMethod(bridgeMethod, bridgeMethod.getDeclaringClass());
}

/**
* Determine the most specific method for the supplied {@link Method bridge Method}
* in the given class hierarchy, even if not available on the local declaring class.
* <p>This is effectively a combination of {@link ClassUtils#getMostSpecificMethod}
* and {@link #findBridgedMethod}, resolving the original method even if no bridge
* method has been generated at the same class hierarchy level (a known difference
* between the Eclipse compiler and regular javac).
* @param bridgeMethod the method to introspect against the given target class
* @param targetClass the target class to find methods on
* @return the original method (either the bridged method or the passed-in method
* if no more specific one could be found)
* @since 6.1.3
* @see #findBridgedMethod
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
*/
public static Method getMostSpecificMethod(Method bridgeMethod, @Nullable Class<?> targetClass) {
Method specificMethod = ClassUtils.getMostSpecificMethod(bridgeMethod, targetClass);
return resolveBridgeMethod(specificMethod,
(targetClass != null ? targetClass : specificMethod.getDeclaringClass()));
}

private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
if (!bridgeMethod.isBridge() && localBridge) {
return bridgeMethod;
}
Method bridgedMethod = cache.get(bridgeMethod);

Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass));
Method bridgedMethod = cache.get(cacheKey);
if (bridgedMethod == null) {
// Gather all methods with matching name and parameter size.
List<Method> candidateMethods = new ArrayList<>();
MethodFilter filter = candidateMethod ->
isBridgedCandidateFor(candidateMethod, bridgeMethod);
ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter);
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
ReflectionUtils.doWithMethods(targetClass, candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) {
bridgedMethod = candidateMethods.size() == 1 ?
candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod);
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
searchCandidates(candidateMethods, bridgeMethod, targetClass));
}
if (bridgedMethod == null) {
// A bridge method was passed in but we couldn't find the bridged method.
// Let's proceed with the passed-in method and hope for the best...
bridgedMethod = bridgeMethod;
}
cache.put(bridgeMethod, bridgedMethod);
cache.put(cacheKey, bridgedMethod);
}
return bridgedMethod;
}
Expand All @@ -110,19 +137,19 @@ private static boolean isBridgedCandidateFor(Method candidateMethod, Method brid
* @return the bridged method, or {@code null} if none found
*/
@Nullable
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
if (candidateMethods.isEmpty()) {
return null;
}
Method previousMethod = null;
boolean sameSig = true;
for (Method candidateMethod : candidateMethods) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
if (isBridgeMethodFor(bridgeMethod, candidateMethod, targetClass)) {
return candidateMethod;
}
else if (previousMethod != null) {
sameSig = sameSig &&
Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
sameSig = sameSig && Arrays.equals(
candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
}
previousMethod = candidateMethod;
}
Expand All @@ -133,12 +160,12 @@ else if (previousMethod != null) {
* Determines whether the bridge {@link Method} is the bridge for the
* supplied candidate {@link Method}.
*/
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> targetClass) {
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, targetClass)) {
return true;
}
Method method = findGenericDeclaration(bridgeMethod);
return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
return (method != null && isResolvedTypeMatch(method, candidateMethod, targetClass));
}

/**
Expand All @@ -147,14 +174,14 @@ static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Cl
* are equal after resolving all types against the declaringType, otherwise
* returns {@code false}.
*/
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> targetClass) {
Type[] genericParameters = genericMethod.getGenericParameterTypes();
if (genericParameters.length != candidateMethod.getParameterCount()) {
return false;
}
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
for (int i = 0; i < candidateParameters.length; i++) {
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, targetClass);
Class<?> candidateParameter = candidateParameters[i];
if (candidateParameter.isArray()) {
// An array type: compare the component type.
Expand All @@ -163,7 +190,8 @@ private static boolean isResolvedTypeMatch(Method genericMethod, Method candidat
}
}
// A non-array type: compare the type itself.
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(
ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
return false;
}
}
Expand All @@ -177,6 +205,10 @@ private static boolean isResolvedTypeMatch(Method genericMethod, Method candidat
*/
@Nullable
private static Method findGenericDeclaration(Method bridgeMethod) {
if (!bridgeMethod.isBridge()) {
return bridgeMethod;
}

// Search parent types for method that has same signature as bridge.
Class<?> superclass = bridgeMethod.getDeclaringClass().getSuperclass();
while (superclass != null && Object.class != superclass) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -86,6 +86,17 @@ void findBridgedMethodInHierarchy() throws Exception {
assertThat(bridgedMethod.getParameterTypes()[0]).isEqualTo(Date.class);
}

@Test
void findBridgedMethodFromOriginalMethodInHierarchy() throws Exception {
Method originalMethod = Adder.class.getMethod("add", Object.class);
assertThat(originalMethod.isBridge()).isFalse();
Method bridgedMethod = BridgeMethodResolver.getMostSpecificMethod(originalMethod, DateAdder.class);
assertThat(bridgedMethod.isBridge()).isFalse();
assertThat(bridgedMethod.getName()).isEqualTo("add");
assertThat(bridgedMethod.getParameterCount()).isEqualTo(1);
assertThat(bridgedMethod.getParameterTypes()[0]).isEqualTo(Date.class);
}

@Test
void isBridgeMethodFor() throws Exception {
Method bridged = MyBar.class.getDeclaredMethod("someMethod", String.class, Object.class);
Expand Down

0 comments on commit 419e34e

Please sign in to comment.