diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java index e89040d1f1e0..c2ea5aad5a4f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java @@ -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. @@ -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 @@ -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"); @@ -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; }; diff --git a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java index e30e4519c713..6b30d707a16d 100644 --- a/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/support/AopUtils.java @@ -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. @@ -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); } /** diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java index da156f85c731..b7d696e68b98 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationAdapter.java @@ -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. @@ -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; @@ -264,8 +263,7 @@ public final Set> 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; 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 70cddfd49348..f8bbfbf78fb9 100644 --- a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java +++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java @@ -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. @@ -50,43 +50,70 @@ */ public final class BridgeMethodResolver { - private static final Map cache = new ConcurrentReferenceHashMap<>(); + private static final Map 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}. *

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 not 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. + *

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 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; } @@ -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 candidateMethods, Method bridgeMethod) { + private static Method searchCandidates(List 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; } @@ -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)); } /** @@ -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. @@ -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; } } @@ -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) { 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 d5fb2f1f63d8..449d886d920c 100644 --- a/spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/BridgeMethodResolverTests.java @@ -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. @@ -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);