diff --git a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java index 96f556a311..518b686cc2 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java @@ -42,6 +42,8 @@ import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.beans.testfixture.beans.DerivedTestBean; import org.springframework.beans.testfixture.beans.ITestBean; import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceEditor; import org.springframework.lang.Nullable; @@ -322,12 +324,13 @@ class BeanUtilsTests { Order original = new Order("test", List.of("foo", "bar")); // Create a Proxy that loses the generic type information for the getLineItems() method. - OrderSummary proxy = proxyOrder(original); + OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] {OrderSummary.class}, new OrderInvocationHandler(original)); assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString()) - .contains("java.util.List"); + .contains("java.util.List"); assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString()) - .contains("java.util.List") - .doesNotContain(""); + .contains("java.util.List") + .doesNotContain(""); // Ensure that our custom Proxy works as expected. assertThat(proxy.getId()).isEqualTo("test"); @@ -340,6 +343,23 @@ class BeanUtilsTests { assertThat(target.getLineItems()).containsExactly("foo", "bar"); } + @Test // gh-32888 + public void copyPropertiesWithGenericCglibCLass() { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(User.class); + enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args)); + User user = (User) enhancer.create(); + user.setId(1); + user.setName("proxy"); + user.setAddress("addr"); + + User target = new User(); + BeanUtils.copyProperties(user, target); + assertThat(target.getId()).isEqualTo(user.getId()); + assertThat(target.getName()).isEqualTo(user.getName()); + assertThat(target.getAddress()).isEqualTo(user.getAddress()); + } + @Test void copyPropertiesWithEditable() throws Exception { TestBean tb = new TestBean(); @@ -520,6 +540,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class IntegerHolder { @@ -534,6 +555,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class WildcardListHolder1 { @@ -548,6 +570,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class WildcardListHolder2 { @@ -562,6 +585,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class NumberUpperBoundedWildcardListHolder { @@ -576,6 +600,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class NumberListHolder { @@ -590,6 +615,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class IntegerListHolder1 { @@ -604,6 +630,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class IntegerListHolder2 { @@ -618,6 +645,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class LongListHolder { @@ -798,6 +826,7 @@ class BeanUtilsTests { } } + private static class BeanWithNullableTypes { private Integer counter; @@ -828,6 +857,7 @@ class BeanUtilsTests { } } + private static class BeanWithPrimitiveTypes { private boolean flag; @@ -840,7 +870,6 @@ class BeanUtilsTests { private char character; private String text; - @SuppressWarnings("unused") public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount, float floatCount, double doubleCount, char character, String text) { @@ -891,22 +920,22 @@ class BeanUtilsTests { public String getText() { return text; } - } + private static class PrivateBeanWithPrivateConstructor { private PrivateBeanWithPrivateConstructor() { } } + @SuppressWarnings("unused") private static class Order { private String id; private List lineItems; - Order() { } @@ -937,6 +966,7 @@ class BeanUtilsTests { } } + private interface OrderSummary { String getId(); @@ -945,17 +975,10 @@ class BeanUtilsTests { } - private OrderSummary proxyOrder(Order order) { - return (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(), - new Class[] { OrderSummary.class }, new OrderInvocationHandler(order)); - } - - private static class OrderInvocationHandler implements InvocationHandler { private final Order order; - OrderInvocationHandler(Order order) { this.order = order; } @@ -973,4 +996,48 @@ class BeanUtilsTests { } } + + private static class GenericBaseModel { + + public GenericBaseModel() { + } + + private T id; + private String name; + + public T getId() { + return id; + } + + public void setId(T id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + + private static class User extends GenericBaseModel { + + private String address; + + public User() { + super(); + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + } 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 c2bd54988d..c7924a87eb 100644 --- a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java +++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java @@ -101,8 +101,12 @@ public final class BridgeMethodResolver { private static Method resolveBridgeMethod(Method bridgeMethod, Class targetClass) { boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass()); + Class userClass = targetClass; if (!bridgeMethod.isBridge() && localBridge) { - return bridgeMethod; + userClass = ClassUtils.getUserClass(targetClass); + if (userClass == targetClass) { + return bridgeMethod; + } } Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass)); @@ -111,7 +115,7 @@ public final class BridgeMethodResolver { // Gather all methods with matching name and parameter size. List candidateMethods = new ArrayList<>(); MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod)); - ReflectionUtils.doWithMethods(targetClass, candidateMethods::add, filter); + ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter); if (!candidateMethods.isEmpty()) { bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) : searchCandidates(candidateMethods, bridgeMethod));