Browse Source

Introduce default ProxyConfig bean and exposed interfaces attribute

Taken into account by all proxy processors, this enables consistent proxy type defaulting in Spring Boot as well as consistent opting out for specific bean definitions.

Closes gh-35286
Closes gh-35293
pull/35303/head
Juergen Hoeller 4 months ago
parent
commit
9edb96ae57
  1. 31
      spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java
  2. 5
      spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java
  3. 2
      spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java
  4. 4
      spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java
  5. 49
      spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java
  6. 52
      spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
  7. 23
      spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java
  8. 72
      spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java
  9. 95
      spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java

31
spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java

@ -23,6 +23,8 @@ import org.jspecify.annotations.Nullable; @@ -23,6 +23,8 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@ -96,17 +98,22 @@ public abstract class AopConfigUtils { @@ -96,17 +98,22 @@ public abstract class AopConfigUtils {
}
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
defaultProxyConfig(registry).getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
defaultProxyConfig(registry).getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
private static BeanDefinition defaultProxyConfig(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME)) {
return registry.getBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME);
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(ProxyConfig.class);
beanDefinition.setSource(AopConfigUtils.class);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME, beanDefinition);
return beanDefinition;
}
private static @Nullable BeanDefinition registerOrEscalateApcAsRequired(
@ -115,12 +122,12 @@ public abstract class AopConfigUtils { @@ -115,12 +122,12 @@ public abstract class AopConfigUtils {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
BeanDefinition beanDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(beanDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(beanDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
beanDefinition.setBeanClassName(cls.getName());
}
}
return null;
@ -128,8 +135,8 @@ public abstract class AopConfigUtils { @@ -128,8 +135,8 @@ public abstract class AopConfigUtils {
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}

5
spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java

@ -112,11 +112,13 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu @@ -112,11 +112,13 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
if (!proxyFactory.isProxyTargetClass() && !proxyFactory.hasUserSuppliedInterfaces()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(isFrozen());
proxyFactory.setPreFiltered(true);
// Use original ClassLoader if bean class not locally loaded in overriding class loader
ClassLoader classLoader = getProxyClassLoader();
@ -187,6 +189,7 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu @@ -187,6 +189,7 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSu
protected ProxyFactory prepareProxyFactory(Object bean, String beanName) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
proxyFactory.setFrozen(false);
proxyFactory.setTarget(bean);
return proxyFactory;
}

2
spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java

@ -694,7 +694,7 @@ class CglibAopProxy implements AopProxy, Serializable { @@ -694,7 +694,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
if (this.advised.isExposeProxy()) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;

4
spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java

@ -183,7 +183,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa @@ -183,7 +183,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
else if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
@ -191,7 +191,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa @@ -191,7 +191,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
Object retVal;
if (this.advised.exposeProxy) {
if (this.advised.isExposeProxy()) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;

49
spring-aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java

@ -18,6 +18,8 @@ package org.springframework.aop.framework; @@ -18,6 +18,8 @@ package org.springframework.aop.framework;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@ -34,15 +36,15 @@ public class ProxyConfig implements Serializable { @@ -34,15 +36,15 @@ public class ProxyConfig implements Serializable {
private static final long serialVersionUID = -8409359707199703185L;
private boolean proxyTargetClass = false;
private @Nullable Boolean proxyTargetClass;
private boolean optimize = false;
private @Nullable Boolean optimize;
boolean opaque = false;
private @Nullable Boolean opaque;
boolean exposeProxy = false;
private @Nullable Boolean exposeProxy;
private boolean frozen = false;
private @Nullable Boolean frozen;
/**
@ -65,7 +67,7 @@ public class ProxyConfig implements Serializable { @@ -65,7 +67,7 @@ public class ProxyConfig implements Serializable {
* Return whether to proxy the target class directly as well as any interfaces.
*/
public boolean isProxyTargetClass() {
return this.proxyTargetClass;
return (this.proxyTargetClass != null && this.proxyTargetClass);
}
/**
@ -85,7 +87,7 @@ public class ProxyConfig implements Serializable { @@ -85,7 +87,7 @@ public class ProxyConfig implements Serializable {
* Return whether proxies should perform aggressive optimizations.
*/
public boolean isOptimize() {
return this.optimize;
return (this.optimize != null && this.optimize);
}
/**
@ -103,7 +105,7 @@ public class ProxyConfig implements Serializable { @@ -103,7 +105,7 @@ public class ProxyConfig implements Serializable {
* prevented from being cast to {@link Advised}.
*/
public boolean isOpaque() {
return this.opaque;
return (this.opaque != null && this.opaque);
}
/**
@ -124,7 +126,7 @@ public class ProxyConfig implements Serializable { @@ -124,7 +126,7 @@ public class ProxyConfig implements Serializable {
* each invocation.
*/
public boolean isExposeProxy() {
return this.exposeProxy;
return (this.exposeProxy != null && this.exposeProxy);
}
/**
@ -141,7 +143,7 @@ public class ProxyConfig implements Serializable { @@ -141,7 +143,7 @@ public class ProxyConfig implements Serializable {
* Return whether the config is frozen, and no advice changes can be made.
*/
public boolean isFrozen() {
return this.frozen;
return (this.frozen != null && this.frozen);
}
@ -153,9 +155,34 @@ public class ProxyConfig implements Serializable { @@ -153,9 +155,34 @@ public class ProxyConfig implements Serializable {
Assert.notNull(other, "Other ProxyConfig object must not be null");
this.proxyTargetClass = other.proxyTargetClass;
this.optimize = other.optimize;
this.opaque = other.opaque;
this.exposeProxy = other.exposeProxy;
this.frozen = other.frozen;
this.opaque = other.opaque;
}
/**
* Copy default settings from the other config object,
* for settings that have not been locally set.
* @param other object to copy configuration from
* @since 7.0
*/
public void copyDefault(ProxyConfig other) {
Assert.notNull(other, "Other ProxyConfig object must not be null");
if (this.proxyTargetClass == null) {
this.proxyTargetClass = other.proxyTargetClass;
}
if (this.optimize == null) {
this.optimize = other.optimize;
}
if (this.opaque == null) {
this.opaque = other.opaque;
}
if (this.exposeProxy == null) {
this.exposeProxy = other.exposeProxy;
}
if (this.frozen == null) {
this.frozen = other.frozen;
}
}
@Override

52
spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java

@ -117,12 +117,6 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport @@ -117,12 +117,6 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
/** Default is global AdvisorAdapterRegistry. */
private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
/**
* Indicates whether the proxy should be frozen. Overridden from super
* to prevent the configuration from becoming frozen too early.
*/
private boolean freezeProxy = false;
/** Default is no common interceptors. */
private String[] interceptorNames = new String[0];
@ -141,22 +135,6 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport @@ -141,22 +135,6 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);
/**
* Set whether the proxy should be frozen, preventing advice
* from being added to it once it is created.
* <p>Overridden from the superclass to prevent the proxy configuration
* from being frozen before the proxy is created.
*/
@Override
public void setFrozen(boolean frozen) {
this.freezeProxy = frozen;
}
@Override
public boolean isFrozen() {
return this.freezeProxy;
}
/**
* Specify the {@link AdvisorAdapterRegistry} to use.
* <p>Default is the global {@link AdvisorAdapterRegistry}.
@ -206,6 +184,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport @@ -206,6 +184,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
AutoProxyUtils.applyDefaultProxyConfig(this, beanFactory);
}
/**
@ -471,6 +450,24 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport @@ -471,6 +450,24 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
proxyFactory.setFrozen(false);
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
Class<?>[] ifcs = (this.beanFactory instanceof ConfigurableListableBeanFactory clbf ?
AutoProxyUtils.determineExposedInterfaces(clbf, beanName) : null);
if (ifcs != null) {
proxyFactory.setProxyTargetClass(false);
for (Class<?> ifc : ifcs) {
proxyFactory.addInterface(ifc);
}
}
else if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
if (proxyFactory.isProxyTargetClass()) {
// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
@ -481,22 +478,13 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport @@ -481,22 +478,13 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
}
}
}
else {
// No proxyTargetClass flag enforced, let's apply our default checks...
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
proxyFactory.setFrozen(isFrozen());
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}

23
spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractBeanFactoryAwareAdvisingPostProcessor.java

@ -25,9 +25,9 @@ import org.springframework.beans.factory.BeanFactoryAware; @@ -25,9 +25,9 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Extension of {@link AbstractAutoProxyCreator} which implements {@link BeanFactoryAware},
* adds exposure of the original target class for each proxied bean
* ({@link AutoProxyUtils#ORIGINAL_TARGET_CLASS_ATTRIBUTE}),
* Extension of {@link AbstractAdvisingBeanPostProcessor} which implements
* {@link BeanFactoryAware}, adds exposure of the original target class for each
* proxied bean ({@link AutoProxyUtils#ORIGINAL_TARGET_CLASS_ATTRIBUTE}),
* and participates in an externally enforced target-class mode for any given bean
* ({@link AutoProxyUtils#PRESERVE_TARGET_CLASS_ATTRIBUTE}).
* This post-processor is therefore aligned with {@link AbstractAutoProxyCreator}.
@ -47,6 +47,7 @@ public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends Abst @@ -47,6 +47,7 @@ public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends Abst
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory clbf ? clbf : null);
AutoProxyUtils.applyDefaultProxyConfig(this, beanFactory);
}
@Override
@ -56,9 +57,19 @@ public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends Abst @@ -56,9 +57,19 @@ public abstract class AbstractBeanFactoryAwareAdvisingPostProcessor extends Abst
}
ProxyFactory proxyFactory = super.prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass() && this.beanFactory != null &&
AutoProxyUtils.shouldProxyTargetClass(this.beanFactory, beanName)) {
proxyFactory.setProxyTargetClass(true);
if (this.beanFactory != null) {
if (AutoProxyUtils.shouldProxyTargetClass(this.beanFactory, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
Class<?>[] ifcs = AutoProxyUtils.determineExposedInterfaces(this.beanFactory, beanName);
if (ifcs != null) {
proxyFactory.setProxyTargetClass(false);
for (Class<?> ifc : ifcs) {
proxyFactory.addInterface(ifc);
}
}
}
}
return proxyFactory;
}

72
spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java

@ -18,6 +18,8 @@ package org.springframework.aop.framework.autoproxy; @@ -18,6 +18,8 @@ package org.springframework.aop.framework.autoproxy;
import org.jspecify.annotations.Nullable;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@ -31,9 +33,37 @@ import org.springframework.util.StringUtils; @@ -31,9 +33,37 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @since 2.0.3
* @see AbstractAutoProxyCreator
* @see AbstractBeanFactoryAwareAdvisingPostProcessor
*/
public abstract class AutoProxyUtils {
/**
* The bean name of the internally managed auto-proxy creator.
* @since 7.0
*/
public static final String DEFAULT_PROXY_CONFIG_BEAN_NAME =
"org.springframework.aop.framework.autoproxy.defaultProxyConfig";
/**
* Bean definition attribute that may indicate the interfaces to be proxied
* (in case of it getting proxied in the first place). The value is either
* a single interface {@code Class} or an array of {@code Class}, with an
* empty array specifically signalling that all implemented interfaces need
* to be proxied.
* @since 7.0
* @see #determineExposedInterfaces
*/
public static final String EXPOSED_INTERFACES_ATTRIBUTE =
Conventions.getQualifiedAttributeName(AutoProxyUtils.class, "exposedInterfaces");
/**
* Attribute value for specifically signalling that all implemented interfaces
* need to be proxied (through an empty {@code Class} array).
* @since 7.0
* @see #EXPOSED_INTERFACES_ATTRIBUTE
*/
public static final Object ALL_INTERFACES_ATTRIBUTE_VALUE = new Class<?>[0];
/**
* Bean definition attribute that may indicate whether a given bean is supposed
* to be proxied with its target class (in case of it getting proxied in the first
@ -57,6 +87,47 @@ public abstract class AutoProxyUtils { @@ -57,6 +87,47 @@ public abstract class AutoProxyUtils {
Conventions.getQualifiedAttributeName(AutoProxyUtils.class, "originalTargetClass");
/**
* Apply default ProxyConfig settings to the given ProxyConfig instance, if necessary.
* @param proxyConfig the current ProxyConfig instance
* @param beanFactory the BeanFactory to take the default ProxyConfig from
* @since 7.0
* @see #DEFAULT_PROXY_CONFIG_BEAN_NAME
* @see ProxyConfig#copyDefault
*/
static void applyDefaultProxyConfig(ProxyConfig proxyConfig, BeanFactory beanFactory) {
if (beanFactory.containsBean(DEFAULT_PROXY_CONFIG_BEAN_NAME)) {
ProxyConfig defaultProxyConfig = beanFactory.getBean(DEFAULT_PROXY_CONFIG_BEAN_NAME, ProxyConfig.class);
proxyConfig.copyDefault(defaultProxyConfig);
}
}
/**
* Determine the specific interfaces for proxying the given bean, if any.
* Checks the {@link #EXPOSED_INTERFACES_ATTRIBUTE "exposedInterfaces" attribute}
* of the corresponding bean definition.
* @param beanFactory the containing ConfigurableListableBeanFactory
* @param beanName the name of the bean
* @return whether the given bean should be proxied with its target class
* @since 7.0
* @see #EXPOSED_INTERFACES_ATTRIBUTE
*/
static Class<?> @Nullable [] determineExposedInterfaces(
ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {
if (beanName != null && beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
Object interfaces = bd.getAttribute(EXPOSED_INTERFACES_ATTRIBUTE);
if (interfaces instanceof Class<?>[] ifcs) {
return ifcs;
}
else if (interfaces instanceof Class<?> ifc) {
return new Class<?>[] {ifc};
}
}
return null;
}
/**
* Determine whether the given bean should be proxied with its target
* class rather than its interfaces. Checks the
@ -65,6 +136,7 @@ public abstract class AutoProxyUtils { @@ -65,6 +136,7 @@ public abstract class AutoProxyUtils {
* @param beanFactory the containing ConfigurableListableBeanFactory
* @param beanName the name of the bean
* @return whether the given bean should be proxied with its target class
* @see #PRESERVE_TARGET_CLASS_ATTRIBUTE
*/
public static boolean shouldProxyTargetClass(
ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {

95
spring-context/src/test/java/org/springframework/resilience/RetryInterceptorTests.java

@ -26,7 +26,10 @@ import java.util.concurrent.atomic.AtomicInteger; @@ -26,7 +26,10 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -76,6 +79,78 @@ class RetryInterceptorTests { @@ -76,6 +79,78 @@ class RetryInterceptorTests {
assertThat(target.counter).isEqualTo(6);
}
@Test
void withPostProcessorForMethodWithInterface() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("bean", new RootBeanDefinition(AnnotatedMethodBeanWithInterface.class));
RetryAnnotationBeanPostProcessor bpp = new RetryAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
AnnotatedInterface proxy = bf.getBean(AnnotatedInterface.class);
AnnotatedMethodBeanWithInterface target = (AnnotatedMethodBeanWithInterface) AopProxyUtils.getSingletonTarget(proxy);
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThatIOException().isThrownBy(proxy::retryOperation).withMessage("6");
assertThat(target.counter).isEqualTo(6);
}
@Test
void withPostProcessorForMethodWithInterfaceAndDefaultTargetClass() {
ProxyConfig defaultProxyConfig = new ProxyConfig();
defaultProxyConfig.setProxyTargetClass(true);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerSingleton(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME, defaultProxyConfig);
bf.registerBeanDefinition("bean", new RootBeanDefinition(AnnotatedMethodBeanWithInterface.class));
RetryAnnotationBeanPostProcessor bpp = new RetryAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
AnnotatedInterface proxy = bf.getBean(AnnotatedInterface.class);
AnnotatedMethodBeanWithInterface target = (AnnotatedMethodBeanWithInterface) AopProxyUtils.getSingletonTarget(proxy);
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
assertThatIOException().isThrownBy(proxy::retryOperation).withMessage("6");
assertThat(target.counter).isEqualTo(6);
}
@Test
void withPostProcessorForMethodWithInterfaceAndPreserveTargetClass() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
RootBeanDefinition bd = new RootBeanDefinition(AnnotatedMethodBeanWithInterface.class);
bd.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
bf.registerBeanDefinition("bean", bd);
RetryAnnotationBeanPostProcessor bpp = new RetryAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
AnnotatedInterface proxy = bf.getBean(AnnotatedInterface.class);
AnnotatedMethodBeanWithInterface target = (AnnotatedMethodBeanWithInterface) AopProxyUtils.getSingletonTarget(proxy);
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
assertThatIOException().isThrownBy(proxy::retryOperation).withMessage("6");
assertThat(target.counter).isEqualTo(6);
}
@Test
void withPostProcessorForMethodWithInterfaceAndExposeInterfaces() {
ProxyConfig defaultProxyConfig = new ProxyConfig();
defaultProxyConfig.setProxyTargetClass(true);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerSingleton(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME, defaultProxyConfig);
RootBeanDefinition bd = new RootBeanDefinition(AnnotatedMethodBeanWithInterface.class);
bd.setAttribute(AutoProxyUtils.EXPOSED_INTERFACES_ATTRIBUTE, AutoProxyUtils.ALL_INTERFACES_ATTRIBUTE_VALUE);
bf.registerBeanDefinition("bean", bd);
RetryAnnotationBeanPostProcessor bpp = new RetryAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
AnnotatedInterface proxy = bf.getBean(AnnotatedInterface.class);
AnnotatedMethodBeanWithInterface target = (AnnotatedMethodBeanWithInterface) AopProxyUtils.getSingletonTarget(proxy);
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThatIOException().isThrownBy(proxy::retryOperation).withMessage("6");
assertThat(target.counter).isEqualTo(6);
}
@Test
void withPostProcessorForClass() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@ -160,6 +235,26 @@ class RetryInterceptorTests { @@ -160,6 +235,26 @@ class RetryInterceptorTests {
}
static class AnnotatedMethodBeanWithInterface implements AnnotatedInterface {
int counter = 0;
@Retryable(maxAttempts = 5, delay = 10)
@Override
public void retryOperation() throws IOException {
counter++;
throw new IOException(Integer.toString(counter));
}
}
interface AnnotatedInterface {
@Retryable(maxAttempts = 5, delay = 10)
void retryOperation() throws IOException;
}
@Retryable(delay = 10, jitter = 5, multiplier = 2.0, maxDelay = 40,
includes = IOException.class, excludes = AccessDeniedException.class,
predicate = CustomPredicate.class)

Loading…
Cancel
Save