diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
index d2e367c79a..565e2a3838 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java
@@ -128,7 +128,7 @@ public abstract class AopProxyUtils {
if (targetClass.isInterface()) {
advised.setInterfaces(targetClass);
}
- else if (Proxy.isProxyClass(targetClass) || isLambda(targetClass)) {
+ else if (Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
advised.setInterfaces(targetClass.getInterfaces());
}
specifiedInterfaces = advised.getProxiedInterfaces();
@@ -239,18 +239,4 @@ public abstract class AopProxyUtils {
return arguments;
}
- /**
- * Determine if the supplied {@link Class} is a JVM-generated implementation
- * class for a lambda expression or method reference.
- *
This method makes a best-effort attempt at determining this, based on
- * checks that work on modern, main stream JVMs.
- * @param clazz the class to check
- * @return {@code true} if the class is a lambda implementation class
- * @since 5.3.16
- */
- static boolean isLambda(Class> clazz) {
- return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
- (clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
- }
-
}
diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java
index 5f1acad9a9..e63e172123 100644
--- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java
+++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java
@@ -21,6 +21,7 @@ import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
import org.springframework.core.NativeDetector;
+import org.springframework.util.ClassUtils;
/**
* Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
@@ -60,7 +61,7 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
- if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || AopProxyUtils.isLambda(targetClass)) {
+ if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
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 3e68f820ec..c550168800 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.beans.factory.config.SmartInstantiationAwareBeanPostP
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 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Rod Johnson
* @author Rob Harrop
+ * @author Sam Brannen
* @since 13.10.2003
* @see #setInterceptorNames
* @see #getAdvicesAndAdvisorsForBean
@@ -442,8 +444,8 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
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-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
index 3dbf550a12..2704cf1c7c 100644
--- a/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
+++ b/spring-aop/src/test/java/org/springframework/aop/framework/AopProxyUtilsTests.java
@@ -19,7 +19,6 @@ package org.springframework.aop.framework;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
-import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@@ -134,61 +133,4 @@ public class AopProxyUtilsTests {
AopProxyUtils.proxiedUserInterfaces(proxy));
}
- @Test
- void isLambda() {
- assertIsLambda(AopProxyUtilsTests.staticLambdaExpression);
- assertIsLambda(AopProxyUtilsTests::staticStringFactory);
-
- assertIsLambda(this.instanceLambdaExpression);
- assertIsLambda(this::instanceStringFactory);
- }
-
- @Test
- void isNotLambda() {
- assertIsNotLambda(new EnigmaSupplier());
-
- assertIsNotLambda(new Supplier() {
- @Override
- public String get() {
- return "anonymous inner class";
- }
- });
-
- assertIsNotLambda(new Fake$$LambdaSupplier());
- }
-
- private static void assertIsLambda(Supplier supplier) {
- assertThat(AopProxyUtils.isLambda(supplier.getClass())).isTrue();
- }
-
- private static void assertIsNotLambda(Supplier supplier) {
- assertThat(AopProxyUtils.isLambda(supplier.getClass())).isFalse();
- }
-
- private static final Supplier staticLambdaExpression = () -> "static lambda expression";
-
- private final Supplier instanceLambdaExpression = () -> "instance lambda expressions";
-
- private static String staticStringFactory() {
- return "static string factory";
- }
-
- private String instanceStringFactory() {
- return "instance string factory";
- }
-
- private static class EnigmaSupplier implements Supplier {
- @Override
- public String get() {
- return "enigma";
- }
- }
-
- private static class Fake$$LambdaSupplier implements Supplier {
- @Override
- public String get() {
- return "fake lambda";
- }
- }
-
}
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 7c017cfa1a..c506e21063 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.annotation.RetentionPolicy;
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.api.Test;
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.Configuration;
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 class AspectJAutoProxyCreatorTests {
@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 {
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
index de70062fbe..dd54c03ed5 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -854,6 +854,20 @@ public abstract class ClassUtils {
return (clazz.isMemberClass() && !isStaticClass(clazz));
}
+ /**
+ * Determine if the supplied {@link Class} is a JVM-generated implementation
+ * class for a lambda expression or method reference.
+ * This method makes a best-effort attempt at determining this, based on
+ * checks that work on modern, mainstream JVMs.
+ * @param clazz the class to check
+ * @return {@code true} if the class is a lambda implementation class
+ * @since 5.3.19
+ */
+ public static boolean isLambdaClass(Class> clazz) {
+ return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
+ (clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
+ }
+
/**
* Check whether the given object is a CGLIB proxy.
* @param object the object to check
diff --git a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java
index c84957e7f2..898bde78f0 100644
--- a/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/util/ClassUtilsTests.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.
@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -408,6 +409,29 @@ class ClassUtilsTests {
assertThat(ClassUtils.isPrimitiveOrWrapper(type)).isTrue();
}
+ @Test
+ void isLambda() {
+ assertIsLambda(ClassUtilsTests.staticLambdaExpression);
+ assertIsLambda(ClassUtilsTests::staticStringFactory);
+
+ assertIsLambda(this.instanceLambdaExpression);
+ assertIsLambda(this::instanceStringFactory);
+ }
+
+ @Test
+ void isNotLambda() {
+ assertIsNotLambda(new EnigmaSupplier());
+
+ assertIsNotLambda(new Supplier() {
+ @Override
+ public String get() {
+ return "anonymous inner class";
+ }
+ });
+
+ assertIsNotLambda(new Fake$$LambdaSupplier());
+ }
+
@Nested
class GetStaticMethodTests {
@@ -500,4 +524,38 @@ class ClassUtilsTests {
}
}
+ private static void assertIsLambda(Supplier supplier) {
+ assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isTrue();
+ }
+
+ private static void assertIsNotLambda(Supplier supplier) {
+ assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isFalse();
+ }
+
+ private static final Supplier staticLambdaExpression = () -> "static lambda expression";
+
+ private final Supplier instanceLambdaExpression = () -> "instance lambda expressions";
+
+ private static String staticStringFactory() {
+ return "static string factory";
+ }
+
+ private String instanceStringFactory() {
+ return "instance string factory";
+ }
+
+ private static class EnigmaSupplier implements Supplier {
+ @Override
+ public String get() {
+ return "enigma";
+ }
+ }
+
+ private static class Fake$$LambdaSupplier implements Supplier {
+ @Override
+ public String get() {
+ return "fake lambda";
+ }
+ }
+
}