From 475396b5167789868d80f48567ff909f1e4745a6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 12 Jul 2021 17:42:15 +0200 Subject: [PATCH] Exclude sealed interfaces from auto-proxying (for JDK 17 compatibility) Closes gh-27027 --- .../aop/framework/AopProxyUtils.java | 48 +++++++++---------- .../aop/framework/ProxyFactoryTests.java | 14 +++++- 2 files changed, 35 insertions(+), 27 deletions(-) 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 5dd18747ef3a..12ce1ce86cdc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -19,7 +19,9 @@ import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.springframework.aop.SpringProxy; import org.springframework.aop.TargetClassAware; @@ -29,7 +31,9 @@ import org.springframework.core.DecoratingProxy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; /** * Utility methods for AOP proxy factories. @@ -44,6 +48,11 @@ */ public abstract class AopProxyUtils { + // JDK 17 Class.isSealed() method available? + @Nullable + private static final Method isSealedMethod = ClassUtils.getMethodIfAvailable(Class.class, "isSealed"); + + /** * Obtain the singleton target object behind the given proxy, if any. * @param candidate the (potential) proxy to check @@ -130,34 +139,23 @@ else if (Proxy.isProxyClass(targetClass)) { specifiedInterfaces = advised.getProxiedInterfaces(); } } - boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class); - boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); - boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); - int nonUserIfcCount = 0; - if (addSpringProxy) { - nonUserIfcCount++; - } - if (addAdvised) { - nonUserIfcCount++; - } - if (addDecoratingProxy) { - nonUserIfcCount++; + List> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3); + for (Class ifc : specifiedInterfaces) { + // Only non-sealed interfaces are actually eligible for JDK proxying (on JDK 17) + if (isSealedMethod == null || Boolean.FALSE.equals(ReflectionUtils.invokeMethod(isSealedMethod, ifc))) { + proxiedInterfaces.add(ifc); + } } - Class[] proxiedInterfaces = new Class[specifiedInterfaces.length + nonUserIfcCount]; - System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); - int index = specifiedInterfaces.length; - if (addSpringProxy) { - proxiedInterfaces[index] = SpringProxy.class; - index++; + if (!advised.isInterfaceProxied(SpringProxy.class)) { + proxiedInterfaces.add(SpringProxy.class); } - if (addAdvised) { - proxiedInterfaces[index] = Advised.class; - index++; + if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) { + proxiedInterfaces.add(Advised.class); } - if (addDecoratingProxy) { - proxiedInterfaces[index] = DecoratingProxy.class; + if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) { + proxiedInterfaces.add(DecoratingProxy.class); } - return proxiedInterfaces; + return ClassUtils.toClassArray(proxiedInterfaces); } /** diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java index 2ae1d635116d..417d1dbbd840 100644 --- a/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -183,7 +183,7 @@ public void testAddRepeatedInterface() { } @Test - public void testGetsAllInterfaces() throws Exception { + public void testGetsAllInterfaces() { // Extend to get new interface class TestBeanSubclass extends TestBean implements Comparable { @Override @@ -240,6 +240,16 @@ public Object invoke(MethodInvocation invocation) throws Throwable { assertThat(factory.countAdvicesOfType(NopInterceptor.class) == 2).isTrue(); } + @Test + public void testSealedInterfaceExclusion() { + // String implements ConstantDesc on JDK 12+, sealed as of JDK 17 + ProxyFactory factory = new ProxyFactory(new String()); + NopInterceptor di = new NopInterceptor(); + factory.addAdvice(0, di); + Object proxy = factory.getProxy(); + assertThat(proxy).isInstanceOf(CharSequence.class); + } + /** * Should see effect immediately on behavior. */