Detect original generic method for CGLIB bridge method

Closes gh-32888
This commit is contained in:
Juergen Hoeller 2024-05-24 11:49:10 +02:00
parent 6c08d93992
commit 345daaabbc
2 changed files with 87 additions and 16 deletions

View File

@ -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<java.lang.String>");
.contains("java.util.List<java.lang.String>");
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
// 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<String> 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<T> {
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<Integer> {
private String address;
public User() {
super();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}

View File

@ -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<Method> 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));