Browse Source

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
pull/28783/head
Sam Brannen 2 years ago
parent
commit
5178e9c28e
  1. 76
      spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
  2. 53
      spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
  3. 7
      spring-context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationRuntimeHintsRegistrar.java
  4. 6
      spring-core/src/main/java/org/springframework/aot/hint/ProxyHints.java
  5. 12
      spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionBeanRegistrationAotProcessor.java

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

@ -21,6 +21,7 @@ import java.lang.reflect.Method; @@ -21,6 +21,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.aop.SpringProxy;
@ -28,6 +29,8 @@ import org.springframework.aop.TargetClassAware; @@ -28,6 +29,8 @@ import org.springframework.aop.TargetClassAware;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.aot.hint.ProxyHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.core.DecoratingProxy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -93,6 +96,79 @@ public abstract class AopProxyUtils { @@ -93,6 +96,79 @@ public abstract class AopProxyUtils {
return result;
}
/**
* Complete the set of interfaces that are typically required in a JDK dynamic
* proxy generated by Spring AOP.
* <p>Specifically, {@link SpringProxy}, {@link Advised}, and {@link DecoratingProxy}
* will be appended to the set of user-specified interfaces.
* <p>Any {@linkplain Class#isSealed() sealed} interface in the set of
* user-specified interfaces will be omitted from the results, since only
* non-sealed interfaces are eligible for JDK dynamic proxies.
* <p>This method can be useful when registering {@linkplain ProxyHints proxy
* hints} for Spring's AOT support, as demonstrated in the following example
* which uses this method via a {@code static} import.
* <pre class="code">
* RuntimeHints hints = ...
* hints.proxies().registerJdkProxy(completeJdkProxyInterfaces(MyInterface.class));
* </pre>
* @param userInterfaces the set of user-specified interfaces implemented by
* the component to be proxied
* @return the complete set of interfaces that the proxy should implement
* @since 6.0
* @see SpringProxy
* @see Advised
* @see DecoratingProxy
* @see RuntimeHints#proxies()
* @see ProxyHints#registerJdkProxy(Class...)
* @see #completeJdkProxyInterfaces(String...)
*/
public static Class<?>[] completeJdkProxyInterfaces(Class<?>... userInterfaces) {
List<Class<?>> completedInterfaces = new ArrayList<>(userInterfaces.length + 3);
for (Class<?> ifc : userInterfaces) {
Assert.isTrue(ifc.isInterface(), () -> ifc.getName() + " must be an interface");
if (!ifc.isSealed()) {
completedInterfaces.add(ifc);
}
}
completedInterfaces.add(SpringProxy.class);
completedInterfaces.add(Advised.class);
completedInterfaces.add(DecoratingProxy.class);
return completedInterfaces.toArray(Class<?>[]::new);
}
/**
* Complete the set of interfaces that are typically required in a JDK dynamic
* proxy generated by Spring AOP.
* <p>Specifically, {@link SpringProxy}, {@link Advised}, and {@link DecoratingProxy}
* will be appended to the set of user-specified interfaces.
* <p>This method can be useful when registering {@linkplain ProxyHints proxy
* hints} for Spring's AOT support, as demonstrated in the following example
* which uses this method via a {@code static} import.
* <pre class="code">
* RuntimeHints hints = ...
* hints.proxies().registerJdkProxy(completeJdkProxyInterfaces("com.example.MyInterface"));
* </pre>
* @param userInterfaces the set of fully qualified names of user-specified
* interfaces implemented by the component to be proxied
* @return the complete set of fully qualified names of interfaces that the
* proxy should implement
* @since 6.0
* @see SpringProxy
* @see Advised
* @see DecoratingProxy
* @see RuntimeHints#proxies()
* @see ProxyHints#registerJdkProxy(Class...)
* @see #completeJdkProxyInterfaces(Class...)
*/
public static String[] completeJdkProxyInterfaces(String... userInterfaces) {
List<String> completedInterfaces = new ArrayList<>(userInterfaces.length + 3);
Collections.addAll(completedInterfaces, userInterfaces);
completedInterfaces.add(SpringProxy.class.getName());
completedInterfaces.add(Advised.class.getName());
completedInterfaces.add(DecoratingProxy.class.getName());
return completedInterfaces.toArray(String[]::new);
}
/**
* Determine the complete set of interfaces to proxy for the given AOP configuration.
* <p>This will always add the {@link Advised} interface unless the AdvisedSupport's

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

@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.aop.SpringProxy;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.DecoratingProxy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@ -108,4 +109,56 @@ class AopProxyUtilsTests { @@ -108,4 +109,56 @@ class AopProxyUtilsTests {
assertThatIllegalArgumentException().isThrownBy(() -> AopProxyUtils.proxiedUserInterfaces(proxy));
}
@Test
void completeJdkProxyInterfacesFromClassThatIsNotAnInterface() {
assertThatIllegalArgumentException()
.isThrownBy(() -> AopProxyUtils.completeJdkProxyInterfaces(TestBean.class))
.withMessage(TestBean.class.getName() + " must be an interface");
}
@Test
void completeJdkProxyInterfacesFromSingleClass() {
Class<?>[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class);
assertThat(jdkProxyInterfaces).containsExactly(
ITestBean.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
}
@Test
void completeJdkProxyInterfacesFromMultipleClasses() {
Class<?>[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class, Comparable.class);
assertThat(jdkProxyInterfaces).containsExactly(
ITestBean.class, Comparable.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
}
@Test
void completeJdkProxyInterfacesIgnoresSealedInterfaces() {
Class<?>[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(SealedInterface.class, Comparable.class);
assertThat(jdkProxyInterfaces).containsExactly(
Comparable.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
}
@Test
void completeJdkProxyInterfacesFromSingleClassName() {
String[] jdkProxyInterfaces = AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class.getName());
assertThat(jdkProxyInterfaces).containsExactly(
ITestBean.class.getName(), SpringProxy.class.getName(), Advised.class.getName(),
DecoratingProxy.class.getName());
}
@Test
void completeJdkProxyInterfacesFromMultipleClassNames() {
String[] jdkProxyInterfaces =
AopProxyUtils.completeJdkProxyInterfaces(ITestBean.class.getName(), Comparable.class.getName());
assertThat(jdkProxyInterfaces).containsExactly(
ITestBean.class.getName(), Comparable.class.getName(), SpringProxy.class.getName(),
Advised.class.getName(), DecoratingProxy.class.getName());
}
sealed interface SealedInterface {
}
static final class SealedType implements SealedInterface {
}
}

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

@ -18,11 +18,9 @@ package org.springframework.validation.beanvalidation; @@ -18,11 +18,9 @@ package org.springframework.validation.beanvalidation;
import jakarta.validation.Validator;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.core.DecoratingProxy;
import org.springframework.lang.Nullable;
/**
@ -36,6 +34,7 @@ public class MethodValidationRuntimeHintsRegistrar implements RuntimeHintsRegist @@ -36,6 +34,7 @@ public class MethodValidationRuntimeHintsRegistrar implements RuntimeHintsRegist
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.proxies().registerJdkProxy(Validator.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
hints.proxies().registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(Validator.class));
}
}

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

@ -80,6 +80,9 @@ public class ProxyHints { @@ -80,6 +80,9 @@ public class ProxyHints {
/**
* Register that a JDK proxy implementing the specified interfaces is
* required.
* <p>When registering a JDK proxy for Spring AOP, consider using
* {@link org.springframework.aop.framework.AopProxyUtils#completeJdkProxyInterfaces(Class...)
* AopProxyUtils.completeJdkProxyInterfaces()} for convenience.
* @param proxiedInterfaces the interfaces the proxy should implement
* @return {@code this}, to facilitate method chaining
*/
@ -91,6 +94,9 @@ public class ProxyHints { @@ -91,6 +94,9 @@ public class ProxyHints {
/**
* Register that a JDK proxy implementing the specified interfaces is
* required.
* <p>When registering a JDK proxy for Spring AOP, consider using
* {@link org.springframework.aop.framework.AopProxyUtils#completeJdkProxyInterfaces(String...)
* AopProxyUtils.completeJdkProxyInterfaces()} for convenience.
* @param proxiedInterfaces the fully qualified class names of interfaces the
* proxy should implement
* @return {@code this}, to facilitate method chaining

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

@ -20,8 +20,7 @@ import java.lang.reflect.AnnotatedElement; @@ -20,8 +20,7 @@ import java.lang.reflect.AnnotatedElement;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
@ -29,7 +28,6 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; @@ -29,7 +28,6 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@ -81,19 +79,15 @@ public class TransactionBeanRegistrationAotProcessor implements BeanRegistration @@ -81,19 +79,15 @@ public class TransactionBeanRegistrationAotProcessor implements BeanRegistration
@Override
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
LinkedHashSet<Class<?>> interfaces = new LinkedHashSet<>();
Class<?>[] proxyInterfaces = ClassUtils.getAllInterfacesForClass(this.beanClass);
if (proxyInterfaces.length == 0) {
return;
}
for (Class<?> proxyInterface : proxyInterfaces) {
interfaces.add(proxyInterface);
runtimeHints.reflection().registerType(proxyInterface, builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
}
interfaces.add(SpringProxy.class);
interfaces.add(Advised.class);
interfaces.add(DecoratingProxy.class);
runtimeHints.proxies().registerJdkProxy(interfaces.toArray(Class[]::new));
runtimeHints.proxies().registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(proxyInterfaces));
}
}
}

Loading…
Cancel
Save