Skip to content

Commit 6fad00e

Browse files
committed
Ensure dynamic proxy with AOP introduction includes lambda interfaces
Closes gh-28209
1 parent 5f6d8df commit 6fad00e

File tree

2 files changed

+108
-5
lines changed

2 files changed

+108
-5
lines changed

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@
5050
import org.springframework.core.SmartClassLoader;
5151
import org.springframework.lang.Nullable;
5252
import org.springframework.util.Assert;
53+
import org.springframework.util.ClassUtils;
5354
import org.springframework.util.StringUtils;
5455

5556
/**
@@ -85,6 +86,7 @@
8586
* @author Juergen Hoeller
8687
* @author Rod Johnson
8788
* @author Rob Harrop
89+
* @author Sam Brannen
8890
* @since 13.10.2003
8991
* @see #setInterceptorNames
9092
* @see #getAdvicesAndAdvisorsForBean
@@ -442,8 +444,8 @@ protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
442444
proxyFactory.copyFrom(this);
443445

444446
if (proxyFactory.isProxyTargetClass()) {
445-
// Explicit handling of JDK proxy targets (for introduction advice scenarios)
446-
if (Proxy.isProxyClass(beanClass)) {
447+
// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
448+
if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
447449
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
448450
for (Class<?> ifc : beanClass.getInterfaces()) {
449451
proxyFactory.addInterface(ifc);

spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java

+103-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.lang.reflect.Method;
2222
import java.util.function.Supplier;
2323

24+
import org.aopalliance.aop.Advice;
25+
import org.aopalliance.intercept.MethodInvocation;
2426
import org.aspectj.lang.JoinPoint;
2527
import org.aspectj.lang.ProceedingJoinPoint;
2628
import org.aspectj.lang.annotation.Around;
@@ -31,11 +33,17 @@
3133
import org.junit.jupiter.params.ParameterizedTest;
3234
import org.junit.jupiter.params.provider.ValueSource;
3335

36+
import org.springframework.aop.ClassFilter;
37+
import org.springframework.aop.IntroductionAdvisor;
38+
import org.springframework.aop.IntroductionInterceptor;
3439
import org.springframework.aop.MethodBeforeAdvice;
40+
import org.springframework.aop.SpringProxy;
3541
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
3642
import org.springframework.aop.aspectj.annotation.AspectMetadata;
3743
import org.springframework.aop.config.AopConfigUtils;
44+
import org.springframework.aop.framework.Advised;
3845
import org.springframework.aop.framework.ProxyConfig;
46+
import org.springframework.aop.support.AbstractPointcutAdvisor;
3947
import org.springframework.aop.support.AopUtils;
4048
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
4149
import org.springframework.beans.PropertyValue;
@@ -52,6 +60,7 @@
5260
import org.springframework.context.annotation.EnableAspectJAutoProxy;
5361
import org.springframework.context.support.ClassPathXmlApplicationContext;
5462
import org.springframework.context.support.GenericApplicationContext;
63+
import org.springframework.core.DecoratingProxy;
5564
import org.springframework.core.NestedRuntimeException;
5665
import org.springframework.core.Ordered;
5766
import org.springframework.core.annotation.Order;
@@ -304,10 +313,26 @@ public void testWithBeanNameAutoProxyCreator() {
304313
@ValueSource(classes = {ProxyTargetClassFalseConfig.class, ProxyTargetClassTrueConfig.class})
305314
void lambdaIsAlwaysProxiedWithJdkProxy(Class<?> configClass) {
306315
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
307-
Supplier<?> supplier = context.getBean(Supplier.class);
316+
@SuppressWarnings("unchecked")
317+
Supplier<String> supplier = context.getBean(Supplier.class);
308318
assertThat(AopUtils.isAopProxy(supplier)).as("AOP proxy").isTrue();
309319
assertThat(AopUtils.isJdkDynamicProxy(supplier)).as("JDK Dynamic proxy").isTrue();
310-
assertThat(supplier.get()).asString().isEqualTo("advised: lambda");
320+
assertThat(supplier.getClass().getInterfaces())
321+
.containsExactlyInAnyOrder(Supplier.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
322+
assertThat(supplier.get()).isEqualTo("advised: lambda");
323+
}
324+
}
325+
326+
@ParameterizedTest(name = "[{index}] {0}")
327+
@ValueSource(classes = {MixinProxyTargetClassFalseConfig.class, MixinProxyTargetClassTrueConfig.class})
328+
void lambdaIsAlwaysProxiedWithJdkProxyWithIntroductions(Class<?> configClass) {
329+
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
330+
MessageGenerator messageGenerator = context.getBean(MessageGenerator.class);
331+
assertThat(AopUtils.isAopProxy(messageGenerator)).as("AOP proxy").isTrue();
332+
assertThat(AopUtils.isJdkDynamicProxy(messageGenerator)).as("JDK Dynamic proxy").isTrue();
333+
assertThat(messageGenerator.getClass().getInterfaces())
334+
.containsExactlyInAnyOrder(MessageGenerator.class, Mixin.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
335+
assertThat(messageGenerator.generateMessage()).isEqualTo("mixin: lambda");
311336
}
312337
}
313338

@@ -616,3 +641,79 @@ class ProxyTargetClassFalseConfig extends AbstractProxyTargetClassConfig {
616641
@EnableAspectJAutoProxy(proxyTargetClass = true)
617642
class ProxyTargetClassTrueConfig extends AbstractProxyTargetClassConfig {
618643
}
644+
645+
@FunctionalInterface
646+
interface MessageGenerator {
647+
String generateMessage();
648+
}
649+
650+
interface Mixin {
651+
}
652+
653+
class MixinIntroductionInterceptor implements IntroductionInterceptor {
654+
655+
@Override
656+
public Object invoke(MethodInvocation invocation) throws Throwable {
657+
return "mixin: " + invocation.proceed();
658+
}
659+
660+
@Override
661+
public boolean implementsInterface(Class<?> intf) {
662+
return Mixin.class.isAssignableFrom(intf);
663+
}
664+
665+
}
666+
667+
@SuppressWarnings("serial")
668+
class MixinAdvisor extends AbstractPointcutAdvisor implements IntroductionAdvisor {
669+
670+
@Override
671+
public org.springframework.aop.Pointcut getPointcut() {
672+
return org.springframework.aop.Pointcut.TRUE;
673+
}
674+
675+
@Override
676+
public Advice getAdvice() {
677+
return new MixinIntroductionInterceptor();
678+
}
679+
680+
@Override
681+
public Class<?>[] getInterfaces() {
682+
return new Class[] { Mixin.class };
683+
}
684+
685+
@Override
686+
public ClassFilter getClassFilter() {
687+
return MessageGenerator.class::isAssignableFrom;
688+
}
689+
690+
@Override
691+
public void validateInterfaces() {
692+
/* no-op */
693+
}
694+
695+
}
696+
697+
abstract class AbstractMixinConfig {
698+
699+
@Bean
700+
MessageGenerator messageGenerator() {
701+
return () -> "lambda";
702+
}
703+
704+
@Bean
705+
MixinAdvisor mixinAdvisor() {
706+
return new MixinAdvisor();
707+
}
708+
709+
}
710+
711+
@Configuration(proxyBeanMethods = false)
712+
@EnableAspectJAutoProxy(proxyTargetClass = false)
713+
class MixinProxyTargetClassFalseConfig extends AbstractMixinConfig {
714+
}
715+
716+
@Configuration(proxyBeanMethods = false)
717+
@EnableAspectJAutoProxy(proxyTargetClass = true)
718+
class MixinProxyTargetClassTrueConfig extends AbstractMixinConfig {
719+
}

0 commit comments

Comments
 (0)