Skip to content

Commit 419e34e

Browse files
committed
Introduce getMostSpecificMethod variant on BridgeMethodResolver
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
1 parent f0e16bd commit 419e34e

File tree

5 files changed

+77
-39
lines changed

5 files changed

+77
-39
lines changed

spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,7 +32,6 @@
3232
import org.springframework.core.task.AsyncTaskExecutor;
3333
import org.springframework.core.task.SimpleAsyncTaskExecutor;
3434
import org.springframework.lang.Nullable;
35-
import org.springframework.util.ClassUtils;
3635

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

107-
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
105+
AsyncTaskExecutor executor = determineAsyncExecutor(userMethod);
108106
if (executor == null) {
109107
throw new IllegalStateException(
110108
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
@@ -118,10 +116,10 @@ public Object invoke(final MethodInvocation invocation) throws Throwable {
118116
}
119117
}
120118
catch (ExecutionException ex) {
121-
handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
119+
handleError(ex.getCause(), userMethod, invocation.getArguments());
122120
}
123121
catch (Throwable ex) {
124-
handleError(ex, userDeclaredMethod, invocation.getArguments());
122+
handleError(ex, userMethod, invocation.getArguments());
125123
}
126124
return null;
127125
};

spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -199,12 +199,11 @@ public static boolean isFinalizeMethod(@Nullable Method method) {
199199
* @return the specific target method, or the original method if the
200200
* {@code targetClass} doesn't implement it or is {@code null}
201201
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
202+
* @see org.springframework.core.BridgeMethodResolver#getMostSpecificMethod
202203
*/
203204
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
204205
Class<?> specificTargetClass = (targetClass != null ? ClassUtils.getUserClass(targetClass) : null);
205-
Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);
206-
// If we are dealing with method with generic parameters, find the original method.
207-
return BridgeMethodResolver.findBridgedMethod(resolvedMethod);
206+
return BridgeMethodResolver.getMostSpecificMethod(method, specificTargetClass);
208207
}
209208

210209
/**

spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,7 +51,6 @@
5151
import org.springframework.core.annotation.AnnotationUtils;
5252
import org.springframework.lang.Nullable;
5353
import org.springframework.util.Assert;
54-
import org.springframework.util.ClassUtils;
5554
import org.springframework.util.function.SingletonSupplier;
5655
import org.springframework.validation.BeanPropertyBindingResult;
5756
import org.springframework.validation.BindingResult;
@@ -264,8 +263,7 @@ public final Set<ConstraintViolation<Object>> invokeValidatorForArguments(
264263
catch (IllegalArgumentException ex) {
265264
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
266265
// Let's try to find the bridged method on the implementation class...
267-
Method mostSpecificMethod = ClassUtils.getMostSpecificMethod(method, target.getClass());
268-
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(mostSpecificMethod);
266+
Method bridgedMethod = BridgeMethodResolver.getMostSpecificMethod(method, target.getClass());
269267
violations = execVal.validateParameters(target, bridgedMethod, arguments, groups);
270268
}
271269
return violations;

spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,43 +50,70 @@
5050
*/
5151
public final class BridgeMethodResolver {
5252

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

5555
private BridgeMethodResolver() {
5656
}
5757

5858

5959
/**
60-
* Find the original method for the supplied {@link Method bridge Method}.
60+
* Find the local original method for the supplied {@link Method bridge Method}.
6161
* <p>It is safe to call this method passing in a non-bridge {@link Method} instance.
6262
* In such a case, the supplied {@link Method} instance is returned directly to the caller.
6363
* Callers are <strong>not</strong> required to check for bridging before calling this method.
64-
* @param bridgeMethod the method to introspect
64+
* @param bridgeMethod the method to introspect against its declaring class
6565
* @return the original method (either the bridged method or the passed-in method
6666
* if no more specific one could be found)
67+
* @see #getMostSpecificMethod(Method, Class)
6768
*/
6869
public static Method findBridgedMethod(Method bridgeMethod) {
69-
if (!bridgeMethod.isBridge()) {
70+
return resolveBridgeMethod(bridgeMethod, bridgeMethod.getDeclaringClass());
71+
}
72+
73+
/**
74+
* Determine the most specific method for the supplied {@link Method bridge Method}
75+
* in the given class hierarchy, even if not available on the local declaring class.
76+
* <p>This is effectively a combination of {@link ClassUtils#getMostSpecificMethod}
77+
* and {@link #findBridgedMethod}, resolving the original method even if no bridge
78+
* method has been generated at the same class hierarchy level (a known difference
79+
* between the Eclipse compiler and regular javac).
80+
* @param bridgeMethod the method to introspect against the given target class
81+
* @param targetClass the target class to find methods on
82+
* @return the original method (either the bridged method or the passed-in method
83+
* if no more specific one could be found)
84+
* @since 6.1.3
85+
* @see #findBridgedMethod
86+
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
87+
*/
88+
public static Method getMostSpecificMethod(Method bridgeMethod, @Nullable Class<?> targetClass) {
89+
Method specificMethod = ClassUtils.getMostSpecificMethod(bridgeMethod, targetClass);
90+
return resolveBridgeMethod(specificMethod,
91+
(targetClass != null ? targetClass : specificMethod.getDeclaringClass()));
92+
}
93+
94+
private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
95+
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
96+
if (!bridgeMethod.isBridge() && localBridge) {
7097
return bridgeMethod;
7198
}
72-
Method bridgedMethod = cache.get(bridgeMethod);
99+
100+
Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass));
101+
Method bridgedMethod = cache.get(cacheKey);
73102
if (bridgedMethod == null) {
74103
// Gather all methods with matching name and parameter size.
75104
List<Method> candidateMethods = new ArrayList<>();
76-
MethodFilter filter = candidateMethod ->
77-
isBridgedCandidateFor(candidateMethod, bridgeMethod);
78-
ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter);
105+
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
106+
ReflectionUtils.doWithMethods(targetClass, candidateMethods::add, filter);
79107
if (!candidateMethods.isEmpty()) {
80-
bridgedMethod = candidateMethods.size() == 1 ?
81-
candidateMethods.get(0) :
82-
searchCandidates(candidateMethods, bridgeMethod);
108+
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
109+
searchCandidates(candidateMethods, bridgeMethod, targetClass));
83110
}
84111
if (bridgedMethod == null) {
85112
// A bridge method was passed in but we couldn't find the bridged method.
86113
// Let's proceed with the passed-in method and hope for the best...
87114
bridgedMethod = bridgeMethod;
88115
}
89-
cache.put(bridgeMethod, bridgedMethod);
116+
cache.put(cacheKey, bridgedMethod);
90117
}
91118
return bridgedMethod;
92119
}
@@ -110,19 +137,19 @@ private static boolean isBridgedCandidateFor(Method candidateMethod, Method brid
110137
* @return the bridged method, or {@code null} if none found
111138
*/
112139
@Nullable
113-
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
140+
private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod, Class<?> targetClass) {
114141
if (candidateMethods.isEmpty()) {
115142
return null;
116143
}
117144
Method previousMethod = null;
118145
boolean sameSig = true;
119146
for (Method candidateMethod : candidateMethods) {
120-
if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
147+
if (isBridgeMethodFor(bridgeMethod, candidateMethod, targetClass)) {
121148
return candidateMethod;
122149
}
123150
else if (previousMethod != null) {
124-
sameSig = sameSig &&
125-
Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
151+
sameSig = sameSig && Arrays.equals(
152+
candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
126153
}
127154
previousMethod = candidateMethod;
128155
}
@@ -133,12 +160,12 @@ else if (previousMethod != null) {
133160
* Determines whether the bridge {@link Method} is the bridge for the
134161
* supplied candidate {@link Method}.
135162
*/
136-
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
137-
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
163+
static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> targetClass) {
164+
if (isResolvedTypeMatch(candidateMethod, bridgeMethod, targetClass)) {
138165
return true;
139166
}
140167
Method method = findGenericDeclaration(bridgeMethod);
141-
return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
168+
return (method != null && isResolvedTypeMatch(method, candidateMethod, targetClass));
142169
}
143170

144171
/**
@@ -147,14 +174,14 @@ static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Cl
147174
* are equal after resolving all types against the declaringType, otherwise
148175
* returns {@code false}.
149176
*/
150-
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
177+
private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class<?> targetClass) {
151178
Type[] genericParameters = genericMethod.getGenericParameterTypes();
152179
if (genericParameters.length != candidateMethod.getParameterCount()) {
153180
return false;
154181
}
155182
Class<?>[] candidateParameters = candidateMethod.getParameterTypes();
156183
for (int i = 0; i < candidateParameters.length; i++) {
157-
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
184+
ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, targetClass);
158185
Class<?> candidateParameter = candidateParameters[i];
159186
if (candidateParameter.isArray()) {
160187
// An array type: compare the component type.
@@ -163,7 +190,8 @@ private static boolean isResolvedTypeMatch(Method genericMethod, Method candidat
163190
}
164191
}
165192
// A non-array type: compare the type itself.
166-
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
193+
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(
194+
ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
167195
return false;
168196
}
169197
}
@@ -177,6 +205,10 @@ private static boolean isResolvedTypeMatch(Method genericMethod, Method candidat
177205
*/
178206
@Nullable
179207
private static Method findGenericDeclaration(Method bridgeMethod) {
208+
if (!bridgeMethod.isBridge()) {
209+
return bridgeMethod;
210+
}
211+
180212
// Search parent types for method that has same signature as bridge.
181213
Class<?> superclass = bridgeMethod.getDeclaringClass().getSuperclass();
182214
while (superclass != null && Object.class != superclass) {

spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,6 +86,17 @@ void findBridgedMethodInHierarchy() throws Exception {
8686
assertThat(bridgedMethod.getParameterTypes()[0]).isEqualTo(Date.class);
8787
}
8888

89+
@Test
90+
void findBridgedMethodFromOriginalMethodInHierarchy() throws Exception {
91+
Method originalMethod = Adder.class.getMethod("add", Object.class);
92+
assertThat(originalMethod.isBridge()).isFalse();
93+
Method bridgedMethod = BridgeMethodResolver.getMostSpecificMethod(originalMethod, DateAdder.class);
94+
assertThat(bridgedMethod.isBridge()).isFalse();
95+
assertThat(bridgedMethod.getName()).isEqualTo("add");
96+
assertThat(bridgedMethod.getParameterCount()).isEqualTo(1);
97+
assertThat(bridgedMethod.getParameterTypes()[0]).isEqualTo(Date.class);
98+
}
99+
89100
@Test
90101
void isBridgeMethodFor() throws Exception {
91102
Method bridged = MyBar.class.getDeclaredMethod("someMethod", String.class, Object.class);

0 commit comments

Comments
 (0)