Unwrap CGLIB proxy when invoking non-proxied methods in ReflectionTestUtils
Prior to this commit, when ReflectionTestUtils was used to invoke a method on a CGLIB proxy, the invocation was always performed directly on the proxy. Consequently, if the method was not proxied/intercepted by the CGLIB proxy -- for example, if the method was final or effectively private -- the invoked method could not operate on the state of the target object or interact with other private methods in the target object. With this commit, if the supplied target object is a CGLIB proxy which does not intercept the method, the proxy will be unwrapped allowing the method to be invoked directly on the ultimate target of the proxy. Closes gh-33429
This commit is contained in:
@@ -32,6 +32,7 @@ import org.springframework.test.util.subpackage.StaticMethods;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.SoftAssertions.assertSoftly;
|
||||
import static org.springframework.test.util.ReflectionTestUtils.getField;
|
||||
import static org.springframework.test.util.ReflectionTestUtils.invokeGetterMethod;
|
||||
import static org.springframework.test.util.ReflectionTestUtils.invokeMethod;
|
||||
@@ -48,7 +49,7 @@ class ReflectionTestUtilsTests {
|
||||
|
||||
private static final Float PI = (float) 22 / 7;
|
||||
|
||||
private final Person person = new PersonEntity();
|
||||
private final PersonEntity person = new PersonEntity();
|
||||
|
||||
private final Component component = new Component();
|
||||
|
||||
@@ -156,7 +157,7 @@ class ReflectionTestUtilsTests {
|
||||
assertThat(person.getAge()).as("age (private field)").isEqualTo(42);
|
||||
assertThat(person.getEyeColor()).as("eye color (package private field)").isEqualTo("blue");
|
||||
assertThat(person.likesPets()).as("'likes pets' flag (package private boolean field)").isTrue();
|
||||
assertThat(person.getFavoriteNumber()).as("'favorite number' (package field)").isEqualTo(PI);
|
||||
assertThat(person.getFavoriteNumber()).as("'favorite number' (private field)").isEqualTo(PI);
|
||||
}
|
||||
|
||||
private static void assertSetFieldAndGetFieldBehaviorForProxy(Person proxy, Person target) {
|
||||
@@ -168,7 +169,7 @@ class ReflectionTestUtilsTests {
|
||||
assertThat(target.getAge()).as("age (private field)").isEqualTo(42);
|
||||
assertThat(target.getEyeColor()).as("eye color (package private field)").isEqualTo("blue");
|
||||
assertThat(target.likesPets()).as("'likes pets' flag (package private boolean field)").isTrue();
|
||||
assertThat(target.getFavoriteNumber()).as("'favorite number' (package field)").isEqualTo(PI);
|
||||
assertThat(target.getFavoriteNumber()).as("'favorite number' (private field)").isEqualTo(PI);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -188,7 +189,7 @@ class ReflectionTestUtilsTests {
|
||||
|
||||
assertThat(person.getName()).as("name (protected field)").isNull();
|
||||
assertThat(person.getEyeColor()).as("eye color (package private field)").isNull();
|
||||
assertThat(person.getFavoriteNumber()).as("'favorite number' (package field)").isNull();
|
||||
assertThat(person.getFavoriteNumber()).as("'favorite number' (private field)").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -295,6 +296,22 @@ class ReflectionTestUtilsTests {
|
||||
assertThat(invokeGetterMethod(person, "favoriteNumber")).isEqualTo(PI);
|
||||
}
|
||||
|
||||
@Test // gh-33429
|
||||
void invokingPrivateGetterMethodViaCglibProxyInvokesMethodOnUltimateTarget() {
|
||||
this.person.setPrivateEye("I");
|
||||
ProxyFactory pf = new ProxyFactory(this.person);
|
||||
pf.setProxyTargetClass(true);
|
||||
PersonEntity proxy = (PersonEntity) pf.getProxy();
|
||||
assertThat(AopUtils.isCglibProxy(proxy)).as("Proxy is a CGLIB proxy").isTrue();
|
||||
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(getField(this.person, "privateEye")).as("'privateEye' (private field in target)").isEqualTo("I");
|
||||
softly.assertThat(getField(proxy, "privateEye")).as("'privateEye' (private field in proxy)").isEqualTo("I");
|
||||
softly.assertThat(invokeGetterMethod(this.person, "privateEye")).as("'privateEye' (getter on target)").isEqualTo("I");
|
||||
softly.assertThat(invokeGetterMethod(proxy, "privateEye")).as("'privateEye' (getter on proxy)").isEqualTo("I");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void invokeSetterMethodWithNullValuesForNonPrimitives() {
|
||||
invokeSetterMethod(person, "name", null, String.class);
|
||||
@@ -306,6 +323,41 @@ class ReflectionTestUtilsTests {
|
||||
assertThat(person.getFavoriteNumber()).as("'favorite number' (protected method for a Number)").isNull();
|
||||
}
|
||||
|
||||
@Test // gh-33429
|
||||
void invokingPrivateSetterMethodViaCglibProxyInvokesMethodOnUltimateTarget() {
|
||||
ProxyFactory pf = new ProxyFactory(this.person);
|
||||
pf.setProxyTargetClass(true);
|
||||
PersonEntity proxy = (PersonEntity) pf.getProxy();
|
||||
assertThat(AopUtils.isCglibProxy(proxy)).as("Proxy is a CGLIB proxy").isTrue();
|
||||
|
||||
// Set reflectively
|
||||
invokeSetterMethod(proxy, "favoriteNumber", PI, Number.class);
|
||||
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(getField(proxy, "favoriteNumber")).as("'favorite number' (private field)").isEqualTo(PI);
|
||||
softly.assertThat(proxy.getFavoriteNumber()).as("'favorite number' (getter on proxy)").isEqualTo(PI);
|
||||
softly.assertThat(this.person.getFavoriteNumber()).as("'favorite number' (getter on target)").isEqualTo(PI);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // gh-33429
|
||||
void invokingFinalSetterMethodViaCglibProxyInvokesMethodOnUltimateTarget() {
|
||||
ProxyFactory pf = new ProxyFactory(this.person);
|
||||
pf.setProxyTargetClass(true);
|
||||
PersonEntity proxy = (PersonEntity) pf.getProxy();
|
||||
assertThat(AopUtils.isCglibProxy(proxy)).as("Proxy is a CGLIB proxy").isTrue();
|
||||
assertThat(proxy.getPuzzle()).as("puzzle").isNull();
|
||||
|
||||
// Set reflectively
|
||||
invokeSetterMethod(proxy, "puzzle", "enigma", String.class);
|
||||
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(getField(proxy, "puzzle")).as("'puzzle' (private field)").isEqualTo("enigma");
|
||||
softly.assertThat(proxy.getPuzzle()).as("'puzzle' (getter on proxy)").isEqualTo("enigma");
|
||||
softly.assertThat(this.person.getPuzzle()).as("'puzzle' (getter on target)").isEqualTo("enigma");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void invokeSetterMethodWithNullValueForPrimitiveLong() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> invokeSetterMethod(person, "id", null, long.class));
|
||||
@@ -362,6 +414,23 @@ class ReflectionTestUtilsTests {
|
||||
assertThat(component.getText()).as("text").isNull();
|
||||
}
|
||||
|
||||
@Test // gh-33429
|
||||
void invokingPrivateMethodViaCglibProxyInvokesMethodOnUltimateTarget() {
|
||||
ProxyFactory pf = new ProxyFactory(this.person);
|
||||
pf.setProxyTargetClass(true);
|
||||
PersonEntity proxy = (PersonEntity) pf.getProxy();
|
||||
assertThat(AopUtils.isCglibProxy(proxy)).as("Proxy is a CGLIB proxy").isTrue();
|
||||
|
||||
// Set reflectively
|
||||
invokeMethod(proxy, "setFavoriteNumber", PI);
|
||||
|
||||
assertSoftly(softly -> {
|
||||
softly.assertThat(getField(proxy, "favoriteNumber")).as("'favorite number' (private field)").isEqualTo(PI);
|
||||
softly.assertThat(proxy.getFavoriteNumber()).as("'favorite number' (getter on proxy)").isEqualTo(PI);
|
||||
softly.assertThat(this.person.getFavoriteNumber()).as("'favorite number' (getter on target)").isEqualTo(PI);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void invokeInitMethodBeforeAutowiring() {
|
||||
assertThatIllegalStateException()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -37,6 +37,10 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
|
||||
private Number favoriteNumber;
|
||||
|
||||
private String puzzle;
|
||||
|
||||
private String privateEye;
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -44,7 +48,7 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void setName(final String name) {
|
||||
private void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -53,7 +57,7 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
protected void setAge(final int age) {
|
||||
protected void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
@@ -62,7 +66,7 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
return this.eyeColor;
|
||||
}
|
||||
|
||||
void setEyeColor(final String eyeColor) {
|
||||
void setEyeColor(String eyeColor) {
|
||||
this.eyeColor = eyeColor;
|
||||
}
|
||||
|
||||
@@ -71,7 +75,7 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
return this.likesPets;
|
||||
}
|
||||
|
||||
protected void setLikesPets(final boolean likesPets) {
|
||||
protected void setLikesPets(boolean likesPets) {
|
||||
this.likesPets = likesPets;
|
||||
}
|
||||
|
||||
@@ -80,10 +84,28 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
return this.favoriteNumber;
|
||||
}
|
||||
|
||||
protected void setFavoriteNumber(Number favoriteNumber) {
|
||||
@SuppressWarnings("unused")
|
||||
private void setFavoriteNumber(Number favoriteNumber) {
|
||||
this.favoriteNumber = favoriteNumber;
|
||||
}
|
||||
|
||||
public String getPuzzle() {
|
||||
return this.puzzle;
|
||||
}
|
||||
|
||||
public final void setPuzzle(String puzzle) {
|
||||
this.puzzle = puzzle;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private String getPrivateEye() {
|
||||
return this.privateEye;
|
||||
}
|
||||
|
||||
public void setPrivateEye(String privateEye) {
|
||||
this.privateEye = privateEye;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// @formatter:off
|
||||
@@ -94,6 +116,8 @@ public class PersonEntity extends PersistentEntity implements Person {
|
||||
.append("eyeColor", this.eyeColor)
|
||||
.append("likesPets", this.likesPets)
|
||||
.append("favoriteNumber", this.favoriteNumber)
|
||||
.append("puzzle", this.puzzle)
|
||||
.append("privateEye", this.privateEye)
|
||||
.toString();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user