diff --git a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java index c9e2ba2059d..8c17c46f084 100644 --- a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java +++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java @@ -29,7 +29,6 @@ import org.jspecify.annotations.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.MethodFilter; /** * Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the @@ -115,8 +114,8 @@ public final class BridgeMethodResolver { if (bridgedMethod == null) { // Gather all methods with matching name and parameter size. List candidateMethods = new ArrayList<>(); - MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod)); - ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter); + ReflectionUtils.doWithMethods(userClass, candidateMethods::add, + candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod)); if (!candidateMethods.isEmpty()) { bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) : searchCandidates(candidateMethods, bridgeMethod, targetClass)); @@ -152,9 +151,6 @@ public final class BridgeMethodResolver { private static @Nullable Method searchCandidates( List candidateMethods, Method bridgeMethod, Class targetClass) { - if (candidateMethods.isEmpty()) { - return null; - } Method previousMethod = null; boolean sameSig = true; for (Method candidateMethod : candidateMethods) { @@ -204,18 +200,16 @@ public final class BridgeMethodResolver { } private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class clazz) { + // First, compare return type. + ResolvableType genericReturnType = ResolvableType.forMethodReturnType(genericMethod, clazz); + if (!ClassUtils.resolvePrimitiveIfNecessary(candidateMethod.getReturnType()).equals( + ClassUtils.resolvePrimitiveIfNecessary(genericReturnType.toClass()))) { + return false; + } Class[] candidateParameters = candidateMethod.getParameterTypes(); for (int i = 0; i < candidateParameters.length; i++) { ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, clazz); - Class candidateParameter = candidateParameters[i]; - if (candidateParameter.isArray()) { - // An array type: compare the component type. - if (!candidateParameter.componentType().equals(genericParameter.getComponentType().toClass())) { - return false; - } - } - // A non-array type: compare the type itself. - if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals( + if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameters[i]).equals( ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) { return false; } diff --git a/spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java b/spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java index 3f9d55e416b..85ea80b7ef3 100644 --- a/spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java @@ -44,17 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat; @SuppressWarnings("rawtypes") class BridgeMethodResolverTests { - private static Method findMethodWithReturnType(String name, Class returnType, Class targetType) { - Method[] methods = targetType.getMethods(); - for (Method m : methods) { - if (m.getName().equals(name) && m.getReturnType().equals(returnType)) { - return m; - } - } - return null; - } - - @Test void findBridgedMethod() throws Exception { Method unbridged = MyFoo.class.getDeclaredMethod("someMethod", String.class, Object.class); @@ -106,6 +95,24 @@ class BridgeMethodResolverTests { assertThat(mostSpecificMethod).isSameAs(originalMethod); } + @Test + void findBridgedMethodWithDefaultMethodInInterfaceHierarchy() throws Exception { + Method getValueDefault = DefaultMethods.class.getMethod("getValue"); + Method getValuesDefault = DefaultMethods.class.getMethod("getValues"); + Method getValueArgDefault = DefaultMethods.class.getMethod("getValue", Integer.class); + Method getValuesArgDefault = DefaultMethods.class.getMethod("getValues", Integer[].class); + for (Method method : ConcreteMethods.class.getMethods()) { + if (method.getName().equals("getValue")) { + assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo( + method.getParameterCount() > 0 ? getValueArgDefault : getValueDefault); + } + else if (method.getName().equals("getValues")) { + assertThat(BridgeMethodResolver.findBridgedMethod(method)).isEqualTo( + method.getParameterCount() > 0 ? getValuesArgDefault : getValuesDefault); + } + } + } + @Test void findBridgedMethodInHierarchyWithBoundedGenerics() throws Exception { Method originalMethod = Bar.class.getDeclaredMethod("someMethod", Object.class, Object.class); @@ -161,6 +168,16 @@ class BridgeMethodResolverTests { assertThat(BridgeMethodResolver.findBridgedMethod(loadWithSettingsReturn)).isEqualTo(method); } + private static Method findMethodWithReturnType(String name, Class returnType, Class targetType) { + Method[] methods = targetType.getMethods(); + for (Method m : methods) { + if (m.getName().equals(name) && m.getReturnType().equals(returnType)) { + return m; + } + } + return null; + } + @Test void findBridgedMethodFromParent() throws Exception { Method loadFromParentBridge = SettingsDaoImpl.class.getMethod("loadFromParent"); @@ -451,6 +468,44 @@ class BridgeMethodResolverTests { } + interface InterfaceMethods { + + T getValue(); + + T[] getValues(); + + T getValue(T arg); + + T[] getValues(T[] args); + } + + + interface DefaultMethods extends InterfaceMethods { + + default Integer getValue() { + return 0; + } + + default Integer[] getValues() { + return new Integer[0]; + } + + @Override + default Integer getValue(Integer arg) { + return 0; + } + + @Override + default Integer[] getValues(Integer[] args) { + return new Integer[0]; + } + } + + + static class ConcreteMethods implements DefaultMethods { + } + + public static class Enclosing { public class Enclosed {