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"; + } + } + }