From 6fad00ed222c48f9d845bcea9d5a50dcf7c2a169 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 9 Apr 2022 09:57:34 +0200 Subject: [PATCH] Ensure dynamic proxy with AOP introduction includes lambda interfaces Closes gh-28209 --- .../autoproxy/AbstractAutoProxyCreator.java | 8 +- .../AspectJAutoProxyCreatorTests.java | 105 +++++++++++++++++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java index 3e68f820ecb9..c550168800e4 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -50,6 +50,7 @@ import org.springframework.core.SmartClassLoader; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -85,6 +86,7 @@ * @author Juergen Hoeller * @author Rod Johnson * @author Rob Harrop + * @author Sam Brannen * @since 13.10.2003 * @see #setInterceptorNames * @see #getAdvicesAndAdvisorsForBean @@ -442,8 +444,8 @@ protected Object createProxy(Class beanClass, @Nullable String beanName, proxyFactory.copyFrom(this); if (proxyFactory.isProxyTargetClass()) { - // Explicit handling of JDK proxy targets (for introduction advice scenarios) - if (Proxy.isProxyClass(beanClass)) { + // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios) + if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) { // Must allow for introductions; can't just set interfaces to the proxy's interfaces only. for (Class ifc : beanClass.getInterfaces()) { proxyFactory.addInterface(ifc); diff --git a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java index 7c017cfa1aad..c506e210636f 100644 --- a/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java +++ b/spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java @@ -21,6 +21,8 @@ import java.lang.reflect.Method; import java.util.function.Supplier; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInvocation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -31,11 +33,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.aop.ClassFilter; +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.IntroductionInterceptor; import org.springframework.aop.MethodBeforeAdvice; +import org.springframework.aop.SpringProxy; import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator; import org.springframework.aop.aspectj.annotation.AspectMetadata; import org.springframework.aop.config.AopConfigUtils; +import org.springframework.aop.framework.Advised; import org.springframework.aop.framework.ProxyConfig; +import org.springframework.aop.support.AbstractPointcutAdvisor; import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; import org.springframework.beans.PropertyValue; @@ -52,6 +60,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.DecoratingProxy; import org.springframework.core.NestedRuntimeException; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -304,10 +313,26 @@ public void testWithBeanNameAutoProxyCreator() { @ValueSource(classes = {ProxyTargetClassFalseConfig.class, ProxyTargetClassTrueConfig.class}) void lambdaIsAlwaysProxiedWithJdkProxy(Class configClass) { try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) { - Supplier supplier = context.getBean(Supplier.class); + @SuppressWarnings("unchecked") + Supplier supplier = context.getBean(Supplier.class); assertThat(AopUtils.isAopProxy(supplier)).as("AOP proxy").isTrue(); assertThat(AopUtils.isJdkDynamicProxy(supplier)).as("JDK Dynamic proxy").isTrue(); - assertThat(supplier.get()).asString().isEqualTo("advised: lambda"); + assertThat(supplier.getClass().getInterfaces()) + .containsExactlyInAnyOrder(Supplier.class, SpringProxy.class, Advised.class, DecoratingProxy.class); + assertThat(supplier.get()).isEqualTo("advised: lambda"); + } + } + + @ParameterizedTest(name = "[{index}] {0}") + @ValueSource(classes = {MixinProxyTargetClassFalseConfig.class, MixinProxyTargetClassTrueConfig.class}) + void lambdaIsAlwaysProxiedWithJdkProxyWithIntroductions(Class configClass) { + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) { + MessageGenerator messageGenerator = context.getBean(MessageGenerator.class); + assertThat(AopUtils.isAopProxy(messageGenerator)).as("AOP proxy").isTrue(); + assertThat(AopUtils.isJdkDynamicProxy(messageGenerator)).as("JDK Dynamic proxy").isTrue(); + assertThat(messageGenerator.getClass().getInterfaces()) + .containsExactlyInAnyOrder(MessageGenerator.class, Mixin.class, SpringProxy.class, Advised.class, DecoratingProxy.class); + assertThat(messageGenerator.generateMessage()).isEqualTo("mixin: lambda"); } } @@ -616,3 +641,79 @@ class ProxyTargetClassFalseConfig extends AbstractProxyTargetClassConfig { @EnableAspectJAutoProxy(proxyTargetClass = true) class ProxyTargetClassTrueConfig extends AbstractProxyTargetClassConfig { } + +@FunctionalInterface +interface MessageGenerator { + String generateMessage(); +} + +interface Mixin { +} + +class MixinIntroductionInterceptor implements IntroductionInterceptor { + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + return "mixin: " + invocation.proceed(); + } + + @Override + public boolean implementsInterface(Class intf) { + return Mixin.class.isAssignableFrom(intf); + } + +} + +@SuppressWarnings("serial") +class MixinAdvisor extends AbstractPointcutAdvisor implements IntroductionAdvisor { + + @Override + public org.springframework.aop.Pointcut getPointcut() { + return org.springframework.aop.Pointcut.TRUE; + } + + @Override + public Advice getAdvice() { + return new MixinIntroductionInterceptor(); + } + + @Override + public Class[] getInterfaces() { + return new Class[] { Mixin.class }; + } + + @Override + public ClassFilter getClassFilter() { + return MessageGenerator.class::isAssignableFrom; + } + + @Override + public void validateInterfaces() { + /* no-op */ + } + +} + +abstract class AbstractMixinConfig { + + @Bean + MessageGenerator messageGenerator() { + return () -> "lambda"; + } + + @Bean + MixinAdvisor mixinAdvisor() { + return new MixinAdvisor(); + } + +} + +@Configuration(proxyBeanMethods = false) +@EnableAspectJAutoProxy(proxyTargetClass = false) +class MixinProxyTargetClassFalseConfig extends AbstractMixinConfig { +} + +@Configuration(proxyBeanMethods = false) +@EnableAspectJAutoProxy(proxyTargetClass = true) +class MixinProxyTargetClassTrueConfig extends AbstractMixinConfig { +}