Skip to content

Commit 475396b

Browse files
committed
Exclude sealed interfaces from auto-proxying (for JDK 17 compatibility)
Closes gh-27027
1 parent f3f0bd2 commit 475396b

File tree

2 files changed

+35
-27
lines changed

2 files changed

+35
-27
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 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.
@@ -19,7 +19,9 @@
1919
import java.lang.reflect.Array;
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Proxy;
22+
import java.util.ArrayList;
2223
import java.util.Arrays;
24+
import java.util.List;
2325

2426
import org.springframework.aop.SpringProxy;
2527
import org.springframework.aop.TargetClassAware;
@@ -29,7 +31,9 @@
2931
import org.springframework.core.DecoratingProxy;
3032
import org.springframework.lang.Nullable;
3133
import org.springframework.util.Assert;
34+
import org.springframework.util.ClassUtils;
3235
import org.springframework.util.ObjectUtils;
36+
import org.springframework.util.ReflectionUtils;
3337

3438
/**
3539
* Utility methods for AOP proxy factories.
@@ -44,6 +48,11 @@
4448
*/
4549
public abstract class AopProxyUtils {
4650

51+
// JDK 17 Class.isSealed() method available?
52+
@Nullable
53+
private static final Method isSealedMethod = ClassUtils.getMethodIfAvailable(Class.class, "isSealed");
54+
55+
4756
/**
4857
* Obtain the singleton target object behind the given proxy, if any.
4958
* @param candidate the (potential) proxy to check
@@ -130,34 +139,23 @@ else if (Proxy.isProxyClass(targetClass)) {
130139
specifiedInterfaces = advised.getProxiedInterfaces();
131140
}
132141
}
133-
boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);
134-
boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);
135-
boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class));
136-
int nonUserIfcCount = 0;
137-
if (addSpringProxy) {
138-
nonUserIfcCount++;
139-
}
140-
if (addAdvised) {
141-
nonUserIfcCount++;
142-
}
143-
if (addDecoratingProxy) {
144-
nonUserIfcCount++;
142+
List<Class<?>> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3);
143+
for (Class<?> ifc : specifiedInterfaces) {
144+
// Only non-sealed interfaces are actually eligible for JDK proxying (on JDK 17)
145+
if (isSealedMethod == null || Boolean.FALSE.equals(ReflectionUtils.invokeMethod(isSealedMethod, ifc))) {
146+
proxiedInterfaces.add(ifc);
147+
}
145148
}
146-
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];
147-
System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);
148-
int index = specifiedInterfaces.length;
149-
if (addSpringProxy) {
150-
proxiedInterfaces[index] = SpringProxy.class;
151-
index++;
149+
if (!advised.isInterfaceProxied(SpringProxy.class)) {
150+
proxiedInterfaces.add(SpringProxy.class);
152151
}
153-
if (addAdvised) {
154-
proxiedInterfaces[index] = Advised.class;
155-
index++;
152+
if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) {
153+
proxiedInterfaces.add(Advised.class);
156154
}
157-
if (addDecoratingProxy) {
158-
proxiedInterfaces[index] = DecoratingProxy.class;
155+
if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) {
156+
proxiedInterfaces.add(DecoratingProxy.class);
159157
}
160-
return proxiedInterfaces;
158+
return ClassUtils.toClassArray(proxiedInterfaces);
161159
}
162160

163161
/**

spring-aop/src/test/java/org/springframework/aop/framework/ProxyFactoryTests.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 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.
@@ -183,7 +183,7 @@ public void testAddRepeatedInterface() {
183183
}
184184

185185
@Test
186-
public void testGetsAllInterfaces() throws Exception {
186+
public void testGetsAllInterfaces() {
187187
// Extend to get new interface
188188
class TestBeanSubclass extends TestBean implements Comparable<Object> {
189189
@Override
@@ -240,6 +240,16 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
240240
assertThat(factory.countAdvicesOfType(NopInterceptor.class) == 2).isTrue();
241241
}
242242

243+
@Test
244+
public void testSealedInterfaceExclusion() {
245+
// String implements ConstantDesc on JDK 12+, sealed as of JDK 17
246+
ProxyFactory factory = new ProxyFactory(new String());
247+
NopInterceptor di = new NopInterceptor();
248+
factory.addAdvice(0, di);
249+
Object proxy = factory.getProxy();
250+
assertThat(proxy).isInstanceOf(CharSequence.class);
251+
}
252+
243253
/**
244254
* Should see effect immediately on behavior.
245255
*/

0 commit comments

Comments
 (0)