Skip to content

Commit 206a890

Browse files
committed
Test detection of original generic method for CGLIB bridge method
See gh-32888
1 parent 8d1bf96 commit 206a890

File tree

2 files changed

+92
-26
lines changed

2 files changed

+92
-26
lines changed

Diff for: spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java

+82-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -41,6 +41,8 @@
4141
import org.springframework.beans.testfixture.beans.DerivedTestBean;
4242
import org.springframework.beans.testfixture.beans.ITestBean;
4343
import org.springframework.beans.testfixture.beans.TestBean;
44+
import org.springframework.cglib.proxy.Enhancer;
45+
import org.springframework.cglib.proxy.MethodInterceptor;
4446
import org.springframework.core.io.Resource;
4547
import org.springframework.core.io.ResourceEditor;
4648
import org.springframework.lang.Nullable;
@@ -51,7 +53,7 @@
5153
import static org.assertj.core.api.SoftAssertions.assertSoftly;
5254

5355
/**
54-
* Unit tests for {@link BeanUtils}.
56+
* Tests for {@link BeanUtils}.
5557
*
5658
* @author Juergen Hoeller
5759
* @author Rob Harrop
@@ -321,12 +323,13 @@ void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() thro
321323
Order original = new Order("test", List.of("foo", "bar"));
322324

323325
// Create a Proxy that loses the generic type information for the getLineItems() method.
324-
OrderSummary proxy = proxyOrder(original);
326+
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
327+
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original));
325328
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
326-
.contains("java.util.List<java.lang.String>");
329+
.contains("java.util.List<java.lang.String>");
327330
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
328-
.contains("java.util.List")
329-
.doesNotContain("<java.lang.String>");
331+
.contains("java.util.List")
332+
.doesNotContain("<java.lang.String>");
330333

331334
// Ensure that our custom Proxy works as expected.
332335
assertThat(proxy.getId()).isEqualTo("test");
@@ -339,6 +342,23 @@ void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() thro
339342
assertThat(target.getLineItems()).containsExactly("foo", "bar");
340343
}
341344

345+
@Test // gh-32888
346+
public void copyPropertiesWithGenericCglibClass() {
347+
Enhancer enhancer = new Enhancer();
348+
enhancer.setSuperclass(User.class);
349+
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
350+
User user = (User) enhancer.create();
351+
user.setId(1);
352+
user.setName("proxy");
353+
user.setAddress("addr");
354+
355+
User target = new User();
356+
BeanUtils.copyProperties(user, target);
357+
assertThat(target.getId()).isEqualTo(user.getId());
358+
assertThat(target.getName()).isEqualTo(user.getName());
359+
assertThat(target.getAddress()).isEqualTo(user.getAddress());
360+
}
361+
342362
@Test
343363
void copyPropertiesWithEditable() throws Exception {
344364
TestBean tb = new TestBean();
@@ -518,6 +538,7 @@ public void setNumber(Number number) {
518538
}
519539
}
520540

541+
521542
@SuppressWarnings("unused")
522543
private static class IntegerHolder {
523544

@@ -532,6 +553,7 @@ public void setNumber(Integer number) {
532553
}
533554
}
534555

556+
535557
@SuppressWarnings("unused")
536558
private static class WildcardListHolder1 {
537559

@@ -546,6 +568,7 @@ public void setList(List<?> list) {
546568
}
547569
}
548570

571+
549572
@SuppressWarnings("unused")
550573
private static class WildcardListHolder2 {
551574

@@ -560,6 +583,7 @@ public void setList(List<?> list) {
560583
}
561584
}
562585

586+
563587
@SuppressWarnings("unused")
564588
private static class NumberUpperBoundedWildcardListHolder {
565589

@@ -574,6 +598,7 @@ public void setList(List<? extends Number> list) {
574598
}
575599
}
576600

601+
577602
@SuppressWarnings("unused")
578603
private static class NumberListHolder {
579604

@@ -588,6 +613,7 @@ public void setList(List<Number> list) {
588613
}
589614
}
590615

616+
591617
@SuppressWarnings("unused")
592618
private static class IntegerListHolder1 {
593619

@@ -602,6 +628,7 @@ public void setList(List<Integer> list) {
602628
}
603629
}
604630

631+
605632
@SuppressWarnings("unused")
606633
private static class IntegerListHolder2 {
607634

@@ -616,6 +643,7 @@ public void setList(List<Integer> list) {
616643
}
617644
}
618645

646+
619647
@SuppressWarnings("unused")
620648
private static class LongListHolder {
621649

@@ -796,6 +824,7 @@ public void setValue(String aValue) {
796824
}
797825
}
798826

827+
799828
private static class BeanWithNullableTypes {
800829

801830
private Integer counter;
@@ -826,6 +855,7 @@ public String getValue() {
826855
}
827856
}
828857

858+
829859
private static class BeanWithPrimitiveTypes {
830860

831861
private boolean flag;
@@ -838,7 +868,6 @@ private static class BeanWithPrimitiveTypes {
838868
private char character;
839869
private String text;
840870

841-
842871
@SuppressWarnings("unused")
843872
public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount,
844873
float floatCount, double doubleCount, char character, String text) {
@@ -889,21 +918,22 @@ public char getCharacter() {
889918
public String getText() {
890919
return text;
891920
}
892-
893921
}
894922

923+
895924
private static class PrivateBeanWithPrivateConstructor {
896925

897926
private PrivateBeanWithPrivateConstructor() {
898927
}
899928
}
900929

930+
901931
@SuppressWarnings("unused")
902932
private static class Order {
903933

904934
private String id;
905-
private List<String> lineItems;
906935

936+
private List<String> lineItems;
907937

908938
Order() {
909939
}
@@ -935,6 +965,7 @@ public String toString() {
935965
}
936966
}
937967

968+
938969
private interface OrderSummary {
939970

940971
String getId();
@@ -943,17 +974,10 @@ private interface OrderSummary {
943974
}
944975

945976

946-
private OrderSummary proxyOrder(Order order) {
947-
return (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
948-
new Class<?>[] { OrderSummary.class }, new OrderInvocationHandler(order));
949-
}
950-
951-
952977
private static class OrderInvocationHandler implements InvocationHandler {
953978

954979
private final Order order;
955980

956-
957981
OrderInvocationHandler(Order order) {
958982
this.order = order;
959983
}
@@ -971,4 +995,46 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
971995
}
972996
}
973997

998+
999+
private static class GenericBaseModel<T> {
1000+
1001+
private T id;
1002+
1003+
private String name;
1004+
1005+
public T getId() {
1006+
return id;
1007+
}
1008+
1009+
public void setId(T id) {
1010+
this.id = id;
1011+
}
1012+
1013+
public String getName() {
1014+
return name;
1015+
}
1016+
1017+
public void setName(String name) {
1018+
this.name = name;
1019+
}
1020+
}
1021+
1022+
1023+
private static class User extends GenericBaseModel<Integer> {
1024+
1025+
private String address;
1026+
1027+
public User() {
1028+
super();
1029+
}
1030+
1031+
public String getAddress() {
1032+
return address;
1033+
}
1034+
1035+
public void setAddress(String address) {
1036+
this.address = address;
1037+
}
1038+
}
1039+
9741040
}

Diff for: spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,11 +57,11 @@ private BridgeMethodResolver() {
5757

5858

5959
/**
60-
* Find the original method for the supplied {@link Method bridge Method}.
60+
* Find the local original method for the supplied {@link Method bridge Method}.
6161
* <p>It is safe to call this method passing in a non-bridge {@link Method} instance.
6262
* In such a case, the supplied {@link Method} instance is returned directly to the caller.
6363
* Callers are <strong>not</strong> required to check for bridging before calling this method.
64-
* @param bridgeMethod the method to introspect
64+
* @param bridgeMethod the method to introspect against its declaring class
6565
* @return the original method (either the bridged method or the passed-in method
6666
* if no more specific one could be found)
6767
*/
@@ -73,8 +73,7 @@ public static Method findBridgedMethod(Method bridgeMethod) {
7373
if (bridgedMethod == null) {
7474
// Gather all methods with matching name and parameter size.
7575
List<Method> candidateMethods = new ArrayList<>();
76-
MethodFilter filter = candidateMethod ->
77-
isBridgedCandidateFor(candidateMethod, bridgeMethod);
76+
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
7877
ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter);
7978
if (!candidateMethods.isEmpty()) {
8079
bridgedMethod = candidateMethods.size() == 1 ?
@@ -121,8 +120,8 @@ private static Method searchCandidates(List<Method> candidateMethods, Method bri
121120
return candidateMethod;
122121
}
123122
else if (previousMethod != null) {
124-
sameSig = sameSig &&
125-
Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
123+
sameSig = sameSig && Arrays.equals(
124+
candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
126125
}
127126
previousMethod = candidateMethod;
128127
}
@@ -163,7 +162,8 @@ private static boolean isResolvedTypeMatch(Method genericMethod, Method candidat
163162
}
164163
}
165164
// A non-array type: compare the type itself.
166-
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
165+
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(
166+
ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
167167
return false;
168168
}
169169
}
@@ -226,8 +226,8 @@ private static Method searchForMatch(Class<?> type, Method bridgeMethod) {
226226
/**
227227
* Compare the signatures of the bridge method and the method which it bridges. If
228228
* the parameter and return types are the same, it is a 'visibility' bridge method
229-
* introduced in Java 6 to fix https://bugs.openjdk.org/browse/JDK-6342411.
230-
* See also https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html
229+
* introduced in Java 6 to fix <a href="https://bugs.openjdk.org/browse/JDK-6342411">
230+
* JDK-6342411</a>.
231231
* @return whether signatures match as described
232232
*/
233233
public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {

0 commit comments

Comments
 (0)