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 0d75f7c3718..3e374cc2904 100644 --- a/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.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. @@ -41,6 +41,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; @@ -51,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException import static org.assertj.core.api.SoftAssertions.assertSoftly; /** - * Unit tests for {@link BeanUtils}. + * Tests for {@link BeanUtils}. * * @author Juergen Hoeller * @author Rob Harrop @@ -321,12 +323,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"); @@ -339,6 +342,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(); @@ -518,6 +538,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class IntegerHolder { @@ -532,6 +553,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class WildcardListHolder1 { @@ -546,6 +568,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class WildcardListHolder2 { @@ -560,6 +583,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class NumberUpperBoundedWildcardListHolder { @@ -574,6 +598,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class NumberListHolder { @@ -588,6 +613,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class IntegerListHolder1 { @@ -602,6 +628,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class IntegerListHolder2 { @@ -616,6 +643,7 @@ class BeanUtilsTests { } } + @SuppressWarnings("unused") private static class LongListHolder { @@ -796,6 +824,7 @@ class BeanUtilsTests { } } + private static class BeanWithNullableTypes { private Integer counter; @@ -826,6 +855,7 @@ class BeanUtilsTests { } } + private static class BeanWithPrimitiveTypes { private boolean flag; @@ -838,7 +868,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) { @@ -889,21 +918,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; + private List lineItems; Order() { } @@ -935,6 +965,7 @@ class BeanUtilsTests { } } + private interface OrderSummary { String getId(); @@ -943,17 +974,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; } @@ -971,4 +995,46 @@ class BeanUtilsTests { } } + + private static class 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 8aa4380f874..811816478dd 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-2022 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. @@ -57,11 +57,11 @@ public final class 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) */ @@ -73,8 +73,7 @@ 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); + MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod)); ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter); if (!candidateMethods.isEmpty()) { bridgedMethod = candidateMethods.size() == 1 ? @@ -121,8 +120,8 @@ public final class BridgeMethodResolver { return candidateMethod; } else if (previousMethod != null) { - sameSig = sameSig && - Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes()); + sameSig = sameSig && Arrays.equals( + candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes()); } previousMethod = candidateMethod; } @@ -163,7 +162,8 @@ public final class BridgeMethodResolver { } } // 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; } } @@ -226,8 +226,8 @@ public final class BridgeMethodResolver { /** * Compare the signatures of the bridge method and the method which it bridges. If * the parameter and return types are the same, it is a 'visibility' bridge method - * introduced in Java 6 to fix https://bugs.openjdk.org/browse/JDK-6342411. - * See also https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html + * introduced in Java 6 to fix + * JDK-6342411. * @return whether signatures match as described */ public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {