Skip to content

Commit 9ac1fec

Browse files
committed
Restore ability to return original method for proxy-derived method
Closes gh-32365
1 parent 7029042 commit 9ac1fec

File tree

3 files changed

+72
-24
lines changed

3 files changed

+72
-24
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 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.
@@ -186,10 +186,10 @@ public static boolean isFinalizeMethod(@Nullable Method method) {
186186
* this method resolves bridge methods in order to retrieve attributes from
187187
* the <i>original</i> method definition.
188188
* @param method the method to be invoked, which may come from an interface
189-
* @param targetClass the target class for the current invocation.
190-
* May be {@code null} or may not even implement the method.
189+
* @param targetClass the target class for the current invocation
190+
* (can be {@code null} or may not even implement the method)
191191
* @return the specific target method, or the original method if the
192-
* {@code targetClass} doesn't implement it or is {@code null}
192+
* {@code targetClass} does not implement it
193193
* @see org.springframework.util.ClassUtils#getMostSpecificMethod
194194
*/
195195
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {

spring-aop/src/test/java/org/springframework/aop/support/AopUtilsTests.java

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 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.
@@ -17,29 +17,35 @@
1717
package org.springframework.aop.support;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.List;
2021

2122
import org.junit.jupiter.api.Test;
2223

2324
import org.springframework.aop.ClassFilter;
2425
import org.springframework.aop.MethodMatcher;
2526
import org.springframework.aop.Pointcut;
27+
import org.springframework.aop.framework.ProxyFactory;
2628
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
2729
import org.springframework.aop.target.EmptyTargetSource;
2830
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
2931
import org.springframework.beans.testfixture.beans.TestBean;
32+
import org.springframework.core.ResolvableType;
3033
import org.springframework.core.testfixture.io.SerializationTestUtils;
3134
import org.springframework.lang.Nullable;
35+
import org.springframework.util.ReflectionUtils;
3236

3337
import static org.assertj.core.api.Assertions.assertThat;
3438

3539
/**
3640
* @author Rod Johnson
3741
* @author Chris Beams
42+
* @author Sebastien Deleuze
43+
* @author Juergen Hoeller
3844
*/
39-
public class AopUtilsTests {
45+
class AopUtilsTests {
4046

4147
@Test
42-
public void testPointcutCanNeverApply() {
48+
void testPointcutCanNeverApply() {
4349
class TestPointcut extends StaticMethodMatcherPointcut {
4450
@Override
4551
public boolean matches(Method method, @Nullable Class<?> clazzy) {
@@ -52,13 +58,13 @@ public boolean matches(Method method, @Nullable Class<?> clazzy) {
5258
}
5359

5460
@Test
55-
public void testPointcutAlwaysApplies() {
61+
void testPointcutAlwaysApplies() {
5662
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), Object.class)).isTrue();
5763
assertThat(AopUtils.canApply(new DefaultPointcutAdvisor(new NopInterceptor()), TestBean.class)).isTrue();
5864
}
5965

6066
@Test
61-
public void testPointcutAppliesToOneMethodOnObject() {
67+
void testPointcutAppliesToOneMethodOnObject() {
6268
class TestPointcut extends StaticMethodMatcherPointcut {
6369
@Override
6470
public boolean matches(Method method, @Nullable Class<?> clazz) {
@@ -78,7 +84,7 @@ public boolean matches(Method method, @Nullable Class<?> clazz) {
7884
* that's subverted the singleton construction limitation.
7985
*/
8086
@Test
81-
public void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
87+
void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throws Exception {
8288
assertThat(SerializationTestUtils.serializeAndDeserialize(MethodMatcher.TRUE)).isSameAs(MethodMatcher.TRUE);
8389
assertThat(SerializationTestUtils.serializeAndDeserialize(ClassFilter.TRUE)).isSameAs(ClassFilter.TRUE);
8490
assertThat(SerializationTestUtils.serializeAndDeserialize(Pointcut.TRUE)).isSameAs(Pointcut.TRUE);
@@ -88,4 +94,45 @@ public void testCanonicalFrameworkClassesStillCanonicalOnDeserialization() throw
8894
assertThat(SerializationTestUtils.serializeAndDeserialize(ExposeInvocationInterceptor.INSTANCE)).isSameAs(ExposeInvocationInterceptor.INSTANCE);
8995
}
9096

97+
@Test
98+
void testInvokeJoinpointUsingReflection() throws Throwable {
99+
String name = "foo";
100+
TestBean testBean = new TestBean(name);
101+
Method method = ReflectionUtils.findMethod(TestBean.class, "getName");
102+
Object result = AopUtils.invokeJoinpointUsingReflection(testBean, method, new Object[0]);
103+
assertThat(result).isEqualTo(name);
104+
}
105+
106+
@Test // gh-32365
107+
void mostSpecificMethodBetweenJdkProxyAndTarget() throws Exception {
108+
Class<?> proxyClass = new ProxyFactory(new WithInterface()).getProxyClass(getClass().getClassLoader());
109+
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithInterface.class);
110+
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
111+
}
112+
113+
@Test // gh-32365
114+
void mostSpecificMethodBetweenCglibProxyAndTarget() throws Exception {
115+
Class<?> proxyClass = new ProxyFactory(new WithoutInterface()).getProxyClass(getClass().getClassLoader());
116+
Method specificMethod = AopUtils.getMostSpecificMethod(proxyClass.getMethod("handle", List.class), WithoutInterface.class);
117+
assertThat(ResolvableType.forMethodParameter(specificMethod, 0).getGeneric().toClass()).isEqualTo(String.class);
118+
}
119+
120+
121+
interface ProxyInterface {
122+
123+
void handle(List<String> list);
124+
}
125+
126+
static class WithInterface implements ProxyInterface {
127+
128+
public void handle(List<String> list) {
129+
}
130+
}
131+
132+
static class WithoutInterface {
133+
134+
public void handle(List<String> list) {
135+
}
136+
}
137+
91138
}

spring-core/src/main/java/org/springframework/util/ClassUtils.java

Lines changed: 15 additions & 14 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.
@@ -243,7 +243,7 @@ public static ClassLoader overrideThreadContextClassLoader(@Nullable ClassLoader
243243
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
244244
* @param name the name of the Class
245245
* @param classLoader the class loader to use
246-
* (may be {@code null}, which indicates the default class loader)
246+
* (can be {@code null}, which indicates the default class loader)
247247
* @return a class instance for the supplied name
248248
* @throws ClassNotFoundException if the class was not found
249249
* @throws LinkageError if the class file could not be loaded
@@ -314,7 +314,7 @@ public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
314314
* the exceptions thrown in case of class loading failure.
315315
* @param className the name of the Class
316316
* @param classLoader the class loader to use
317-
* (may be {@code null}, which indicates the default class loader)
317+
* (can be {@code null}, which indicates the default class loader)
318318
* @return a class instance for the supplied name
319319
* @throws IllegalArgumentException if the class name was not resolvable
320320
* (that is, the class could not be found or the class file could not be loaded)
@@ -348,7 +348,7 @@ public static Class<?> resolveClassName(String className, @Nullable ClassLoader
348348
* one of its dependencies is not present or cannot be loaded.
349349
* @param className the name of the class to check
350350
* @param classLoader the class loader to use
351-
* (may be {@code null} which indicates the default class loader)
351+
* (can be {@code null} which indicates the default class loader)
352352
* @return whether the specified class is present (including all of its
353353
* superclasses and interfaces)
354354
* @throws IllegalStateException if the corresponding class is resolvable but
@@ -375,7 +375,7 @@ public static boolean isPresent(String className, @Nullable ClassLoader classLoa
375375
* Check whether the given class is visible in the given ClassLoader.
376376
* @param clazz the class to check (typically an interface)
377377
* @param classLoader the ClassLoader to check against
378-
* (may be {@code null} in which case this method will always return {@code true})
378+
* (can be {@code null} in which case this method will always return {@code true})
379379
*/
380380
public static boolean isVisible(Class<?> clazz, @Nullable ClassLoader classLoader) {
381381
if (classLoader == null) {
@@ -399,7 +399,7 @@ public static boolean isVisible(Class<?> clazz, @Nullable ClassLoader classLoade
399399
* i.e. whether it is loaded by the given ClassLoader or a parent of it.
400400
* @param clazz the class to analyze
401401
* @param classLoader the ClassLoader to potentially cache metadata in
402-
* (may be {@code null} which indicates the system class loader)
402+
* (can be {@code null} which indicates the system class loader)
403403
*/
404404
public static boolean isCacheSafe(Class<?> clazz, @Nullable ClassLoader classLoader) {
405405
Assert.notNull(clazz, "Class must not be null");
@@ -663,7 +663,7 @@ public static String classNamesToString(Class<?>... classes) {
663663
* in the given collection.
664664
* <p>Basically like {@code AbstractCollection.toString()}, but stripping
665665
* the "class "/"interface " prefix before every class name.
666-
* @param classes a Collection of Class objects (may be {@code null})
666+
* @param classes a Collection of Class objects (can be {@code null})
667667
* @return a String of form "[com.foo.Bar, com.foo.Baz]"
668668
* @see java.util.AbstractCollection#toString()
669669
*/
@@ -718,7 +718,7 @@ public static Class<?>[] getAllInterfacesForClass(Class<?> clazz) {
718718
* <p>If the class itself is an interface, it gets returned as sole interface.
719719
* @param clazz the class to analyze for interfaces
720720
* @param classLoader the ClassLoader that the interfaces need to be visible in
721-
* (may be {@code null} when accepting all declared interfaces)
721+
* (can be {@code null} when accepting all declared interfaces)
722722
* @return all interfaces that the given object implements as an array
723723
*/
724724
public static Class<?>[] getAllInterfacesForClass(Class<?> clazz, @Nullable ClassLoader classLoader) {
@@ -753,7 +753,7 @@ public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz) {
753753
* <p>If the class itself is an interface, it gets returned as sole interface.
754754
* @param clazz the class to analyze for interfaces
755755
* @param classLoader the ClassLoader that the interfaces need to be visible in
756-
* (may be {@code null} when accepting all declared interfaces)
756+
* (can be {@code null} when accepting all declared interfaces)
757757
* @return all interfaces that the given object implements as a Set
758758
*/
759759
public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz, @Nullable ClassLoader classLoader) {
@@ -1082,7 +1082,7 @@ public static String getQualifiedMethodName(Method method) {
10821082
* fully qualified interface/class name + "." + method name.
10831083
* @param method the method
10841084
* @param clazz the clazz that the method is being invoked on
1085-
* (may be {@code null} to indicate the method's declaring class)
1085+
* (can be {@code null} to indicate the method's declaring class)
10861086
* @return the qualified name of the method
10871087
* @since 4.3.4
10881088
*/
@@ -1163,7 +1163,7 @@ public static boolean hasMethod(Class<?> clazz, String methodName, Class<?>... p
11631163
* @param clazz the clazz to analyze
11641164
* @param methodName the name of the method
11651165
* @param paramTypes the parameter types of the method
1166-
* (may be {@code null} to indicate any signature)
1166+
* (can be {@code null} to indicate any signature)
11671167
* @return the method (never {@code null})
11681168
* @throws IllegalStateException if the method has not been found
11691169
* @see Class#getMethod
@@ -1202,7 +1202,7 @@ else if (candidates.isEmpty()) {
12021202
* @param clazz the clazz to analyze
12031203
* @param methodName the name of the method
12041204
* @param paramTypes the parameter types of the method
1205-
* (may be {@code null} to indicate any signature)
1205+
* (can be {@code null} to indicate any signature)
12061206
* @return the method, or {@code null} if not found
12071207
* @see Class#getMethod
12081208
*/
@@ -1291,13 +1291,14 @@ public static boolean hasAtLeastOneMethodWithName(Class<?> clazz, String methodN
12911291
* implementation will fall back to returning the originally provided method.
12921292
* @param method the method to be invoked, which may come from an interface
12931293
* @param targetClass the target class for the current invocation
1294-
* (may be {@code null} or may not even implement the method)
1294+
* (can be {@code null} or may not even implement the method)
12951295
* @return the specific target method, or the original method if the
12961296
* {@code targetClass} does not implement it
12971297
* @see #getInterfaceMethodIfPossible(Method, Class)
12981298
*/
12991299
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
1300-
if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) {
1300+
if (targetClass != null && targetClass != method.getDeclaringClass() &&
1301+
(isOverridable(method, targetClass) || !method.getDeclaringClass().isAssignableFrom(targetClass))) {
13011302
try {
13021303
if (Modifier.isPublic(method.getModifiers())) {
13031304
try {

0 commit comments

Comments
 (0)