Detect original generic method for CGLIB bridge method
Closes gh-32888
This commit is contained in:
parent
6c08d93992
commit
345daaabbc
|
@ -42,6 +42,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;
|
||||||
|
@ -322,12 +324,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");
|
||||||
|
@ -340,6 +343,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();
|
||||||
|
@ -520,6 +540,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class IntegerHolder {
|
private static class IntegerHolder {
|
||||||
|
|
||||||
|
@ -534,6 +555,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class WildcardListHolder1 {
|
private static class WildcardListHolder1 {
|
||||||
|
|
||||||
|
@ -548,6 +570,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class WildcardListHolder2 {
|
private static class WildcardListHolder2 {
|
||||||
|
|
||||||
|
@ -562,6 +585,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class NumberUpperBoundedWildcardListHolder {
|
private static class NumberUpperBoundedWildcardListHolder {
|
||||||
|
|
||||||
|
@ -576,6 +600,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class NumberListHolder {
|
private static class NumberListHolder {
|
||||||
|
|
||||||
|
@ -590,6 +615,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class IntegerListHolder1 {
|
private static class IntegerListHolder1 {
|
||||||
|
|
||||||
|
@ -604,6 +630,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class IntegerListHolder2 {
|
private static class IntegerListHolder2 {
|
||||||
|
|
||||||
|
@ -618,6 +645,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class LongListHolder {
|
private static class LongListHolder {
|
||||||
|
|
||||||
|
@ -798,6 +826,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class BeanWithNullableTypes {
|
private static class BeanWithNullableTypes {
|
||||||
|
|
||||||
private Integer counter;
|
private Integer counter;
|
||||||
|
@ -828,6 +857,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class BeanWithPrimitiveTypes {
|
private static class BeanWithPrimitiveTypes {
|
||||||
|
|
||||||
private boolean flag;
|
private boolean flag;
|
||||||
|
@ -840,7 +870,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) {
|
||||||
|
@ -891,22 +920,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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -937,6 +966,7 @@ class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private interface OrderSummary {
|
private interface OrderSummary {
|
||||||
|
|
||||||
String getId();
|
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 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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,8 +101,12 @@ public final class BridgeMethodResolver {
|
||||||
|
|
||||||
private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
|
private static Method resolveBridgeMethod(Method bridgeMethod, Class<?> targetClass) {
|
||||||
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
|
boolean localBridge = (targetClass == bridgeMethod.getDeclaringClass());
|
||||||
|
Class<?> userClass = targetClass;
|
||||||
if (!bridgeMethod.isBridge() && localBridge) {
|
if (!bridgeMethod.isBridge() && localBridge) {
|
||||||
return bridgeMethod;
|
userClass = ClassUtils.getUserClass(targetClass);
|
||||||
|
if (userClass == targetClass) {
|
||||||
|
return bridgeMethod;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object cacheKey = (localBridge ? bridgeMethod : new MethodClassKey(bridgeMethod, targetClass));
|
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.
|
// Gather all methods with matching name and parameter size.
|
||||||
List<Method> candidateMethods = new ArrayList<>();
|
List<Method> candidateMethods = new ArrayList<>();
|
||||||
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
|
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
|
||||||
ReflectionUtils.doWithMethods(targetClass, candidateMethods::add, filter);
|
ReflectionUtils.doWithMethods(userClass, candidateMethods::add, filter);
|
||||||
if (!candidateMethods.isEmpty()) {
|
if (!candidateMethods.isEmpty()) {
|
||||||
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
|
bridgedMethod = (candidateMethods.size() == 1 ? candidateMethods.get(0) :
|
||||||
searchCandidates(candidateMethods, bridgeMethod));
|
searchCandidates(candidateMethods, bridgeMethod));
|
||||||
|
|
Loading…
Reference in New Issue