Skip to content

Commit 5178e9c

Browse files
committed
Simplify hint registration for Spring AOP proxies
Prior to this commit, when users wished to register proxy hints for a Spring AOP JDK dynamic proxy, they were required to explicitly specify SpringProxy, Advised, and DecoratingProxy along with user interfaces. This commit simplifies hint registration for Spring AOP proxies by introducing two completeJdkProxyInterfaces() methods in AopProxyUtils, one that accepts strings and one that accepts classes that represent the user-specified interfaces implemented the user component to be proxied. The SpringProxy, Advised, and DecoratingProxy interfaces are appended to the user-specified interfaces and returned as the complete set of interfaces that the proxy will implement. Closes gh-28745
1 parent 2a0b3c1 commit 5178e9c

File tree

5 files changed

+141
-13
lines changed

5 files changed

+141
-13
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@
2121
import java.lang.reflect.Proxy;
2222
import java.util.ArrayList;
2323
import java.util.Arrays;
24+
import java.util.Collections;
2425
import java.util.List;
2526

2627
import org.springframework.aop.SpringProxy;
2728
import org.springframework.aop.TargetClassAware;
2829
import org.springframework.aop.TargetSource;
2930
import org.springframework.aop.support.AopUtils;
3031
import org.springframework.aop.target.SingletonTargetSource;
32+
import org.springframework.aot.hint.ProxyHints;
33+
import org.springframework.aot.hint.RuntimeHints;
3134
import org.springframework.core.DecoratingProxy;
3235
import org.springframework.lang.Nullable;
3336
import org.springframework.util.Assert;
@@ -93,6 +96,79 @@ public static Class<?> ultimateTargetClass(Object candidate) {
9396
return result;
9497
}
9598

99+
/**
100+
* Complete the set of interfaces that are typically required in a JDK dynamic
101+
* proxy generated by Spring AOP.
102+
* <p>Specifically, {@link SpringProxy}, {@link Advised}, and {@link DecoratingProxy}
103+
* will be appended to the set of user-specified interfaces.
104+
* <p>Any {@linkplain Class#isSealed() sealed} interface in the set of
105+
* user-specified interfaces will be omitted from the results, since only
106+
* non-sealed interfaces are eligible for JDK dynamic proxies.
107+
* <p>This method can be useful when registering {@linkplain ProxyHints proxy
108+
* hints} for Spring's AOT support, as demonstrated in the following example
109+
* which uses this method via a {@code static} import.
110+
* <pre class="code">
111+
* RuntimeHints hints = ...
112+
* hints.proxies().registerJdkProxy(completeJdkProxyInterfaces(MyInterface.class));
113+
* </pre>
114+
* @param userInterfaces the set of user-specified interfaces implemented by
115+
* the component to be proxied
116+
* @return the complete set of interfaces that the proxy should implement
117+
* @since 6.0
118+
* @see SpringProxy
119+
* @see Advised
120+
* @see DecoratingProxy
121+
* @see RuntimeHints#proxies()
122+
* @see ProxyHints#registerJdkProxy(Class...)
123+
* @see #completeJdkProxyInterfaces(String...)
124+
*/
125+
public static Class<?>[] completeJdkProxyInterfaces(Class<?>... userInterfaces) {
126+
List<Class<?>> completedInterfaces = new ArrayList<>(userInterfaces.length + 3);
127+
for (Class<?> ifc : userInterfaces) {
128+
Assert.isTrue(ifc.isInterface(), () -> ifc.getName() + " must be an interface");
129+
if (!ifc.isSealed()) {
130+
completedInterfaces.add(ifc);
131+
}
132+
}
133+
completedInterfaces.add(SpringProxy.class);
134+
completedInterfaces.add(Advised.class);
135+
completedInterfaces.add(DecoratingProxy.class);
136+
return completedInterfaces.toArray(Class<?>[]::new);
137+
}
138+
139+
/**
140+
* Complete the set of interfaces that are typically required in a JDK dynamic
141+
* proxy generated by Spring AOP.
142+
* <p>Specifically, {@link SpringProxy}, {@link Advised}, and {@link DecoratingProxy}
143+
* will be appended to the set of user-specified interfaces.
144+
* <p>This method can be useful when registering {@linkplain ProxyHints proxy
145+
* hints} for Spring's AOT support, as demonstrated in the following example
146+
* which uses this method via a {@code static} import.
147+
* <pre class="code">
148+
* RuntimeHints hints = ...
149+
* hints.proxies().registerJdkProxy(completeJdkProxyInterfaces("com.example.MyInterface"));
150+
* </pre>
151+
* @param userInterfaces the set of fully qualified names of user-specified
152+
* interfaces implemented by the component to be proxied
153+
* @return the complete set of fully qualified names of interfaces that the
154+
* proxy should implement
155+
* @since 6.0
156+
* @see SpringProxy
157+
* @see Advised
158+
* @see DecoratingProxy
159+
* @see RuntimeHints#proxies()
160+
* @see ProxyHints#registerJdkProxy(Class...)
161+
* @see #completeJdkProxyInterfaces(Class...)
162+
*/
163+
public static String[] completeJdkProxyInterfaces(String... userInterfaces) {
164+
List<String> completedInterfaces = new ArrayList<>(userInterfaces.length + 3);
165+
Collections.addAll(completedInterfaces, userInterfaces);
166+
completedInterfaces.add(SpringProxy.class.getName());
167+
completedInterfaces.add(Advised.class.getName());
168+
completedInterfaces.add(DecoratingProxy.class.getName());
169+
return completedInterfaces.toArray(String[]::new);
170+
}
171+
96172
/**
97173
* Determine the complete set of interfaces to proxy for the given AOP configuration.
98174
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's

spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.aop.SpringProxy;
2424
import org.springframework.beans.testfixture.beans.ITestBean;
2525
import org.springframework.beans.testfixture.beans.TestBean;
26+
import org.springframework.core.DecoratingProxy;
2627

2728
import static org.assertj.core.api.Assertions.assertThat;
2829
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -108,4 +109,56 @@ void proxiedUserInterfacesWithNoInterface() {
108109
assertThatIllegalArgumentException().isThrownBy(() -> AopProxyUtils.proxiedUserInterfaces(proxy));
109110
}
110111

112+
@Test
113+
void completeJdkProxyInterfacesFromClassThatIsNotAnInterface() {
114+
assertThatIllegalArgumentException()
115+
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(TestBean.class))
116+
.withMessage(TestBean.class.getName() + " must be an interface");
117+
}
118+
119+
@Test
120+
void completeJdkProxyInterfacesFromSingleClass() {
121+
Class<?>[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class);
122+
assertThat(jdkProxyInterfaces).containsExactly(
123+
ITestBean.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
124+
}
125+
126+
@Test
127+
void completeJdkProxyInterfacesFromMultipleClasses() {
128+
Class<?>[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class, Comparable.class);
129+
assertThat(jdkProxyInterfaces).containsExactly(
130+
ITestBean.class, Comparable.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
131+
}
132+
133+
@Test
134+
void completeJdkProxyInterfacesIgnoresSealedInterfaces() {
135+
Class<?>[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(SealedInterface.class, Comparable.class);
136+
assertThat(jdkProxyInterfaces).containsExactly(
137+
Comparable.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
138+
}
139+
140+
@Test
141+
void completeJdkProxyInterfacesFromSingleClassName() {
142+
String[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class.getName());
143+
assertThat(jdkProxyInterfaces).containsExactly(
144+
ITestBean.class.getName(), SpringProxy.class.getName(), Advised.class.getName(),
145+
DecoratingProxy.class.getName());
146+
}
147+
148+
@Test
149+
void completeJdkProxyInterfacesFromMultipleClassNames() {
150+
String[] jdkProxyInterfaces =
151+
AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class.getName(), Comparable.class.getName());
152+
assertThat(jdkProxyInterfaces).containsExactly(
153+
ITestBean.class.getName(), Comparable.class.getName(), SpringProxy.class.getName(),
154+
Advised.class.getName(), DecoratingProxy.class.getName());
155+
}
156+
157+
158+
sealed interface SealedInterface {
159+
}
160+
161+
static final class SealedType implements SealedInterface {
162+
}
163+
111164
}

spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationRuntimeHintsRegistrar.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@
1818

1919
import jakarta.validation.Validator;
2020

21-
import org.springframework.aop.SpringProxy;
22-
import org.springframework.aop.framework.Advised;
21+
import org.springframework.aop.framework.AopProxyUtils;
2322
import org.springframework.aot.hint.RuntimeHints;
2423
import org.springframework.aot.hint.RuntimeHintsRegistrar;
25-
import org.springframework.core.DecoratingProxy;
2624
import org.springframework.lang.Nullable;
2725

2826
/**
@@ -36,6 +34,7 @@ public class MethodValidationRuntimeHintsRegistrar implements RuntimeHintsRegist
3634

3735
@Override
3836
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
39-
hints.proxies().registerJdkProxy(Validator.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
37+
hints.proxies().registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(Validator.class));
4038
}
39+
4140
}

spring-core/src/main/java/org/springframework/aot/hint/ProxyHints.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ public ProxyHints registerJdkProxy(TypeReference... proxiedInterfaces) {
8080
/**
8181
* Register that a JDK proxy implementing the specified interfaces is
8282
* required.
83+
* <p>When registering a JDK proxy for Spring AOP, consider using
84+
* {@link org.springframework.aop.framework.AopProxyUtils#completeJdkProxyInterfaces(Class...)
85+
* AopProxyUtils.completeJdkProxyInterfaces()} for convenience.
8386
* @param proxiedInterfaces the interfaces the proxy should implement
8487
* @return {@code this}, to facilitate method chaining
8588
*/
@@ -91,6 +94,9 @@ public ProxyHints registerJdkProxy(Class<?>... proxiedInterfaces) {
9194
/**
9295
* Register that a JDK proxy implementing the specified interfaces is
9396
* required.
97+
* <p>When registering a JDK proxy for Spring AOP, consider using
98+
* {@link org.springframework.aop.framework.AopProxyUtils#completeJdkProxyInterfaces(String...)
99+
* AopProxyUtils.completeJdkProxyInterfaces()} for convenience.
94100
* @param proxiedInterfaces the fully qualified class names of interfaces the
95101
* proxy should implement
96102
* @return {@code this}, to facilitate method chaining

spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionBeanRegistrationAotProcessor.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,14 @@
2020
import java.util.LinkedHashSet;
2121
import java.util.Set;
2222

23-
import org.springframework.aop.SpringProxy;
24-
import org.springframework.aop.framework.Advised;
23+
import org.springframework.aop.framework.AopProxyUtils;
2524
import org.springframework.aot.generate.GenerationContext;
2625
import org.springframework.aot.hint.MemberCategory;
2726
import org.springframework.aot.hint.RuntimeHints;
2827
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
2928
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
3029
import org.springframework.beans.factory.aot.BeanRegistrationCode;
3130
import org.springframework.beans.factory.support.RegisteredBean;
32-
import org.springframework.core.DecoratingProxy;
3331
import org.springframework.core.annotation.MergedAnnotations;
3432
import org.springframework.util.ClassUtils;
3533
import org.springframework.util.ReflectionUtils;
@@ -81,19 +79,15 @@ public TransactionBeanRegistrationAotContribution(Class<?> beanClass) {
8179
@Override
8280
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
8381
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
84-
LinkedHashSet<Class<?>> interfaces = new LinkedHashSet<>();
8582
Class<?>[] proxyInterfaces = ClassUtils.getAllInterfacesForClass(this.beanClass);
8683
if (proxyInterfaces.length == 0) {
8784
return;
8885
}
8986
for (Class<?> proxyInterface : proxyInterfaces) {
90-
interfaces.add(proxyInterface);
9187
runtimeHints.reflection().registerType(proxyInterface, builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
9288
}
93-
interfaces.add(SpringProxy.class);
94-
interfaces.add(Advised.class);
95-
interfaces.add(DecoratingProxy.class);
96-
runtimeHints.proxies().registerJdkProxy(interfaces.toArray(Class[]::new));
89+
runtimeHints.proxies().registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(proxyInterfaces));
9790
}
9891
}
92+
9993
}

0 commit comments

Comments
 (0)