Browse Source

Test detection of original generic method for CGLIB bridge method

See gh-32888
pull/33048/head
Juergen Hoeller 2 years ago
parent
commit
206a89017c
  1. 98
      spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java
  2. 20
      spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java

98
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.DerivedTestBean;
import org.springframework.beans.testfixture.beans.ITestBean; import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.TestBean; 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.Resource;
import org.springframework.core.io.ResourceEditor; import org.springframework.core.io.ResourceEditor;
import org.springframework.lang.Nullable; 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; import static org.assertj.core.api.SoftAssertions.assertSoftly;
/** /**
* Unit tests for {@link BeanUtils}. * Tests for {@link BeanUtils}.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Rob Harrop * @author Rob Harrop
@ -321,12 +323,13 @@ class BeanUtilsTests {
Order original = new Order("test", List.of("foo", "bar")); Order original = new Order("test", List.of("foo", "bar"));
// Create a Proxy that loses the generic type information for the getLineItems() method. // 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()) assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List<java.lang.String>"); .contains("java.util.List<java.lang.String>");
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString()) assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List") .contains("java.util.List")
.doesNotContain("<java.lang.String>"); .doesNotContain("<java.lang.String>");
// Ensure that our custom Proxy works as expected. // Ensure that our custom Proxy works as expected.
assertThat(proxy.getId()).isEqualTo("test"); assertThat(proxy.getId()).isEqualTo("test");
@ -339,6 +342,23 @@ class BeanUtilsTests {
assertThat(target.getLineItems()).containsExactly("foo", "bar"); 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 @Test
void copyPropertiesWithEditable() throws Exception { void copyPropertiesWithEditable() throws Exception {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
@ -518,6 +538,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class IntegerHolder { private static class IntegerHolder {
@ -532,6 +553,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class WildcardListHolder1 { private static class WildcardListHolder1 {
@ -546,6 +568,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class WildcardListHolder2 { private static class WildcardListHolder2 {
@ -560,6 +583,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class NumberUpperBoundedWildcardListHolder { private static class NumberUpperBoundedWildcardListHolder {
@ -574,6 +598,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class NumberListHolder { private static class NumberListHolder {
@ -588,6 +613,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class IntegerListHolder1 { private static class IntegerListHolder1 {
@ -602,6 +628,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class IntegerListHolder2 { private static class IntegerListHolder2 {
@ -616,6 +643,7 @@ class BeanUtilsTests {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class LongListHolder { private static class LongListHolder {
@ -796,6 +824,7 @@ class BeanUtilsTests {
} }
} }
private static class BeanWithNullableTypes { private static class BeanWithNullableTypes {
private Integer counter; private Integer counter;
@ -826,6 +855,7 @@ class BeanUtilsTests {
} }
} }
private static class BeanWithPrimitiveTypes { private static class BeanWithPrimitiveTypes {
private boolean flag; private boolean flag;
@ -838,7 +868,6 @@ class BeanUtilsTests {
private char character; private char character;
private String text; private String text;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount, public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount,
float floatCount, double doubleCount, char character, String text) { float floatCount, double doubleCount, char character, String text) {
@ -889,21 +918,22 @@ class BeanUtilsTests {
public String getText() { public String getText() {
return text; return text;
} }
} }
private static class PrivateBeanWithPrivateConstructor { private static class PrivateBeanWithPrivateConstructor {
private PrivateBeanWithPrivateConstructor() { private PrivateBeanWithPrivateConstructor() {
} }
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static class Order { private static class Order {
private String id; private String id;
private List<String> lineItems;
private List<String> lineItems;
Order() { Order() {
} }
@ -935,6 +965,7 @@ class BeanUtilsTests {
} }
} }
private interface OrderSummary { private interface OrderSummary {
String getId(); 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 static class OrderInvocationHandler implements InvocationHandler {
private final Order order; private final Order order;
OrderInvocationHandler(Order order) { OrderInvocationHandler(Order order) {
this.order = order; this.order = order;
} }
@ -971,4 +995,46 @@ class BeanUtilsTests {
} }
} }
private static class GenericBaseModel<T> {
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<Integer> {
private String address;
public User() {
super();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
} }

20
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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}.
* <p>It is safe to call this method passing in a non-bridge {@link Method} instance. * <p>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. * In such a case, the supplied {@link Method} instance is returned directly to the caller.
* Callers are <strong>not</strong> required to check for bridging before calling this method. * Callers are <strong>not</strong> 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 * @return the original method (either the bridged method or the passed-in method
* if no more specific one could be found) * if no more specific one could be found)
*/ */
@ -73,8 +73,7 @@ public final class BridgeMethodResolver {
if (bridgedMethod == null) { if (bridgedMethod == null) {
// Gather all methods with matching name and parameter size. // Gather all methods with matching name and parameter size.
List<Method> candidateMethods = new ArrayList<>(); List<Method> candidateMethods = new ArrayList<>();
MethodFilter filter = candidateMethod -> MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
isBridgedCandidateFor(candidateMethod, bridgeMethod);
ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter); ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) { if (!candidateMethods.isEmpty()) {
bridgedMethod = candidateMethods.size() == 1 ? bridgedMethod = candidateMethods.size() == 1 ?
@ -121,8 +120,8 @@ public final class BridgeMethodResolver {
return candidateMethod; return candidateMethod;
} }
else if (previousMethod != null) { else if (previousMethod != null) {
sameSig = sameSig && sameSig = sameSig && Arrays.equals(
Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes()); candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
} }
previousMethod = candidateMethod; previousMethod = candidateMethod;
} }
@ -163,7 +162,8 @@ public final class BridgeMethodResolver {
} }
} }
// A non-array type: compare the type itself. // 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; return false;
} }
} }
@ -226,8 +226,8 @@ public final class BridgeMethodResolver {
/** /**
* Compare the signatures of the bridge method and the method which it bridges. If * 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 * 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. * introduced in Java 6 to fix <a href="https://bugs.openjdk.org/browse/JDK-6342411">
* See also https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html * JDK-6342411</a>.
* @return whether signatures match as described * @return whether signatures match as described
*/ */
public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) { public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {

Loading…
Cancel
Save