Merge branch '7.0.x'

This commit is contained in:
Brian Clozel
2026-08-31 18:39:19 +02:00
7 changed files with 79 additions and 22 deletions
@@ -73,6 +73,29 @@ public abstract class AopProxyUtils {
return null;
}
/**
* Obtain the ultimate singleton target object behind the given proxy,
* even for a nested proxy scenario where the immediate singleton target
* is yet another proxy.
* @param candidate the (potential) proxy to check
* @return the singleton target object managed in a {@link SingletonTargetSource},
* or the original candidate if not a proxy or not an existing singleton target
* @since 7.0.10
* @see Advised#getTargetSource()
* @see SingletonTargetSource#getTarget()
*/
public static Object ultimateSingletonTarget(Object candidate) {
Object current = candidate;
while (current instanceof Advised advised) {
TargetSource targetSource = advised.getTargetSource();
if (!(targetSource instanceof SingletonTargetSource singleTargetSource)) {
break;
}
current = singleTargetSource.getTarget();
}
return current;
}
/**
* Determine the ultimate target class of the given bean instance, traversing
* not only a top-level proxy but any number of nested proxies as well —
@@ -21,6 +21,8 @@ import java.lang.reflect.Proxy;
import org.junit.jupiter.api.Test;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.target.PrototypeTargetSource;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.DecoratingProxy;
@@ -37,6 +39,36 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*/
class AopProxyUtilsTests {
@Test
void ultimateTarget() {
TestBean target = new TestBean();
Object proxy = ProxyFactory.getProxy(new SingletonTargetSource(target));
assertThat(AopProxyUtils.getSingletonTarget(proxy)).isSameAs(target);
assertThat(AopProxyUtils.ultimateSingletonTarget(proxy)).isSameAs(target);
assertThat(AopProxyUtils.ultimateTargetClass(proxy)).isEqualTo(TestBean.class);
}
@Test
void ultimateTargetWithNestedProxy() {
TestBean target = new TestBean();
Object innerProxy = ProxyFactory.getProxy(new SingletonTargetSource(target));
Object outerProxy = ProxyFactory.getProxy(new SingletonTargetSource(innerProxy));
assertThat(AopProxyUtils.getSingletonTarget(innerProxy)).isSameAs(target);
assertThat(AopProxyUtils.getSingletonTarget(outerProxy)).isSameAs(innerProxy);
assertThat(AopProxyUtils.ultimateSingletonTarget(outerProxy)).isSameAs(target);
assertThat(AopProxyUtils.ultimateTargetClass(outerProxy)).isEqualTo(TestBean.class);
}
@Test
void ultimateTargetWithNonSingleton() {
PrototypeTargetSource prototypeTarget = new PrototypeTargetSource();
prototypeTarget.setTargetClass(TestBean.class);
Object proxy = ProxyFactory.getProxy(prototypeTarget);
assertThat(AopProxyUtils.getSingletonTarget(proxy)).isNull();
assertThat(AopProxyUtils.ultimateSingletonTarget(proxy)).isSameAs(proxy);
assertThat(AopProxyUtils.ultimateTargetClass(proxy)).isEqualTo(TestBean.class);
}
@Test
void completeProxiedInterfacesWorksWithNull() {
AdvisedSupport as = new AdvisedSupport();
@@ -112,22 +144,22 @@ class AopProxyUtilsTests {
@Test
void completeJdkProxyInterfacesFromNullInterface() {
assertThatIllegalArgumentException()
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class, null, Comparable.class))
.withMessage("'userInterfaces' must not contain null values");
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class, null, Comparable.class))
.withMessage("'userInterfaces' must not contain null values");
}
@Test
void completeJdkProxyInterfacesFromClassThatIsNotAnInterface() {
assertThatIllegalArgumentException()
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(TestBean.class))
.withMessage(TestBean.class.getName() + " must be a non-sealed interface");
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(TestBean.class))
.withMessage(TestBean.class.getName() + " must be a non-sealed interface");
}
@Test
void completeJdkProxyInterfacesFromSealedInterface() {
assertThatIllegalArgumentException()
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(SealedInterface.class))
.withMessage(SealedInterface.class.getName() + " must be a non-sealed interface");
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(SealedInterface.class))
.withMessage(SealedInterface.class.getName() + " must be a non-sealed interface");
}
@Test