Skip to content

Commit f6089af

Browse files
committed
Use ClassLoaderAwareGeneratorStrategy with UndeclaredThrowableStrategy delegate
See gh-32469
1 parent 5615838 commit f6089af

File tree

4 files changed

+91
-72
lines changed

4 files changed

+91
-72
lines changed

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.Serializable;
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Modifier;
22+
import java.lang.reflect.UndeclaredThrowableException;
2223
import java.util.ArrayList;
2324
import java.util.Collections;
2425
import java.util.List;
@@ -36,6 +37,7 @@
3637
import org.springframework.aop.support.AopUtils;
3738
import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy;
3839
import org.springframework.cglib.core.CodeGenerationException;
40+
import org.springframework.cglib.core.GeneratorStrategy;
3941
import org.springframework.cglib.core.SpringNamingPolicy;
4042
import org.springframework.cglib.proxy.Callback;
4143
import org.springframework.cglib.proxy.CallbackFilter;
@@ -45,6 +47,7 @@
4547
import org.springframework.cglib.proxy.MethodInterceptor;
4648
import org.springframework.cglib.proxy.MethodProxy;
4749
import org.springframework.cglib.proxy.NoOp;
50+
import org.springframework.cglib.transform.impl.UndeclaredThrowableStrategy;
4851
import org.springframework.core.KotlinDetector;
4952
import org.springframework.core.MethodParameter;
5053
import org.springframework.core.SmartClassLoader;
@@ -92,17 +95,20 @@ class CglibAopProxy implements AopProxy, Serializable {
9295
private static final int INVOKE_HASHCODE = 6;
9396

9497

98+
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
99+
100+
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent(
101+
"kotlinx.coroutines.reactor.MonoKt", CglibAopProxy.class.getClassLoader());
102+
103+
private static final GeneratorStrategy undeclaredThrowableStrategy =
104+
new UndeclaredThrowableStrategy(UndeclaredThrowableException.class);
105+
95106
/** Logger available to subclasses; static to optimize serialization. */
96107
protected static final Log logger = LogFactory.getLog(CglibAopProxy.class);
97108

98109
/** Keeps track of the Classes that we have validated for final methods. */
99110
private static final Map<Class<?>, Boolean> validatedClasses = new WeakHashMap<>();
100111

101-
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
102-
103-
private static final boolean coroutinesReactorPresent = ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt",
104-
CglibAopProxy.class.getClassLoader());;
105-
106112

107113
/** The configuration used to configure this proxy. */
108114
protected final AdvisedSupport advised;
@@ -200,7 +206,7 @@ private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly)
200206
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
201207
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
202208
enhancer.setAttemptLoad(true);
203-
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
209+
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader, undeclaredThrowableStrategy));
204210

205211
Callback[] callbacks = getCallbacks(rootClass);
206212
Class<?>[] types = new Class<?>[callbacks.length];

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

+51-50
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.aop.framework;
1818

19-
import java.io.Serial;
2019
import java.lang.reflect.Proxy;
2120
import java.lang.reflect.UndeclaredThrowableException;
2221
import java.util.Objects;
@@ -36,47 +35,72 @@
3635
import static org.mockito.BDDMockito.doThrow;
3736
import static org.mockito.BDDMockito.mock;
3837

38+
/**
39+
* @author Mikaël Francoeur
40+
* @since 6.2
41+
*/
3942
abstract class ProxyExceptionHandlingTests implements WithAssertions {
4043

4144
private static final RuntimeException uncheckedException = new RuntimeException();
45+
4246
private static final DeclaredCheckedException declaredCheckedException = new DeclaredCheckedException();
47+
4348
private static final UndeclaredCheckedException undeclaredCheckedException = new UndeclaredCheckedException();
4449

4550
protected final MyClass target = mock(MyClass.class);
51+
4652
protected final ProxyFactory proxyFactory = new ProxyFactory(target);
4753

4854
@Nullable
4955
protected MyInterface proxy;
56+
5057
@Nullable
5158
private Throwable throwableSeenByCaller;
5259

53-
static class ObjenesisCglibAopProxyTests extends ProxyExceptionHandlingTests {
54-
@BeforeEach
55-
void beforeEach() {
56-
proxyFactory.setProxyTargetClass(true);
57-
}
5860

59-
@Override
60-
protected void assertProxyType(Object proxy) {
61-
assertThat(Enhancer.isEnhanced(proxy.getClass())).isTrue();
62-
}
61+
@BeforeEach
62+
void clear() {
63+
Mockito.clearInvocations(target);
6364
}
6465

66+
protected void assertProxyType(Object proxy) {
67+
}
68+
69+
private void invokeProxy() {
70+
throwableSeenByCaller = catchThrowable(() -> Objects.requireNonNull(proxy).doSomething());
71+
}
72+
73+
@SuppressWarnings("SameParameterValue")
74+
private Answer<?> sneakyThrow(Throwable throwable) {
75+
return invocation -> {
76+
throw throwable;
77+
};
78+
}
79+
80+
6581
static class JdkAopProxyTests extends ProxyExceptionHandlingTests {
82+
6683
@Override
6784
protected void assertProxyType(Object proxy) {
6885
assertThat(Proxy.isProxyClass(proxy.getClass())).isTrue();
6986
}
7087
}
7188

72-
protected void assertProxyType(Object proxy) {
73-
}
7489

75-
@BeforeEach
76-
void beforeEach() {
77-
Mockito.clearInvocations(target);
90+
static class CglibAopProxyTests extends ProxyExceptionHandlingTests {
91+
92+
@BeforeEach
93+
void setup() {
94+
proxyFactory.setProxyTargetClass(true);
95+
}
96+
97+
@Override
98+
protected void assertProxyType(Object proxy) {
99+
assertThat(Enhancer.isEnhanced(proxy.getClass())).isTrue();
100+
}
78101
}
79102

103+
80104
@Nested
81105
class WhenThereIsOneInterceptor {
82106

@@ -86,18 +110,14 @@ class WhenThereIsOneInterceptor {
86110
@BeforeEach
87111
void beforeEach() {
88112
proxyFactory.addAdvice(captureThrowable());
89-
90-
proxy = (MyInterface) proxyFactory.getProxy();
91-
113+
proxy = (MyInterface) proxyFactory.getProxy(ProxyExceptionHandlingTests.class.getClassLoader());
92114
assertProxyType(proxy);
93115
}
94116

95117
@Test
96118
void targetThrowsUndeclaredCheckedException() throws DeclaredCheckedException {
97119
doAnswer(sneakyThrow(undeclaredCheckedException)).when(target).doSomething();
98-
99120
invokeProxy();
100-
101121
assertThat(throwableSeenByInterceptor).isSameAs(undeclaredCheckedException);
102122
assertThat(throwableSeenByCaller)
103123
.isInstanceOf(UndeclaredThrowableException.class)
@@ -107,19 +127,15 @@ void targetThrowsUndeclaredCheckedException() throws DeclaredCheckedException {
107127
@Test
108128
void targetThrowsDeclaredCheckedException() throws DeclaredCheckedException {
109129
doThrow(declaredCheckedException).when(target).doSomething();
110-
111130
invokeProxy();
112-
113131
assertThat(throwableSeenByInterceptor).isSameAs(declaredCheckedException);
114132
assertThat(throwableSeenByCaller).isSameAs(declaredCheckedException);
115133
}
116134

117135
@Test
118136
void targetThrowsUncheckedException() throws DeclaredCheckedException {
119137
doThrow(uncheckedException).when(target).doSomething();
120-
121138
invokeProxy();
122-
123139
assertThat(throwableSeenByInterceptor).isSameAs(uncheckedException);
124140
assertThat(throwableSeenByCaller).isSameAs(uncheckedException);
125141
}
@@ -129,30 +145,28 @@ private MethodInterceptor captureThrowable() {
129145
try {
130146
return invocation.proceed();
131147
}
132-
catch (Exception e) {
133-
throwableSeenByInterceptor = e;
134-
throw e;
148+
catch (Exception ex) {
149+
throwableSeenByInterceptor = ex;
150+
throw ex;
135151
}
136152
};
137153
}
138154
}
139155

156+
140157
@Nested
141158
class WhenThereAreNoInterceptors {
142159

143160
@BeforeEach
144161
void beforeEach() {
145-
proxy = (MyInterface) proxyFactory.getProxy();
146-
162+
proxy = (MyInterface) proxyFactory.getProxy(ProxyExceptionHandlingTests.class.getClassLoader());
147163
assertProxyType(proxy);
148164
}
149165

150166
@Test
151167
void targetThrowsUndeclaredCheckedException() throws DeclaredCheckedException {
152168
doAnswer(sneakyThrow(undeclaredCheckedException)).when(target).doSomething();
153-
154169
invokeProxy();
155-
156170
assertThat(throwableSeenByCaller)
157171
.isInstanceOf(UndeclaredThrowableException.class)
158172
.hasCauseReference(undeclaredCheckedException);
@@ -161,51 +175,38 @@ void targetThrowsUndeclaredCheckedException() throws DeclaredCheckedException {
161175
@Test
162176
void targetThrowsDeclaredCheckedException() throws DeclaredCheckedException {
163177
doThrow(declaredCheckedException).when(target).doSomething();
164-
165178
invokeProxy();
166-
167179
assertThat(throwableSeenByCaller).isSameAs(declaredCheckedException);
168180
}
169181

170182
@Test
171183
void targetThrowsUncheckedException() throws DeclaredCheckedException {
172184
doThrow(uncheckedException).when(target).doSomething();
173-
174185
invokeProxy();
175-
176186
assertThat(throwableSeenByCaller).isSameAs(uncheckedException);
177187
}
178188
}
179189

180-
private void invokeProxy() {
181-
throwableSeenByCaller = catchThrowable(() -> Objects.requireNonNull(proxy).doSomething());
182-
}
183190

184-
private Answer<?> sneakyThrow(@SuppressWarnings("SameParameterValue") Throwable throwable) {
185-
return invocation -> {
186-
throw throwable;
187-
};
191+
protected interface MyInterface {
192+
193+
void doSomething() throws DeclaredCheckedException;
188194
}
189195

190196
static class MyClass implements MyInterface {
197+
191198
@Override
192199
public void doSomething() throws DeclaredCheckedException {
193200
throw declaredCheckedException;
194201
}
195202
}
196203

197-
protected interface MyInterface {
198-
void doSomething() throws DeclaredCheckedException;
199-
}
200-
204+
@SuppressWarnings("serial")
201205
protected static class UndeclaredCheckedException extends Exception {
202-
@Serial
203-
private static final long serialVersionUID = -4199611059122356651L;
204206
}
205207

208+
@SuppressWarnings("serial")
206209
protected static class DeclaredCheckedException extends Exception {
207-
@Serial
208-
private static final long serialVersionUID = 8851375818059230518L;
209210
}
210211

211212
}

spring-core/src/main/java/org/springframework/cglib/core/ClassLoaderAwareGeneratorStrategy.java

+24-10
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-2024 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.
@@ -16,10 +16,6 @@
1616

1717
package org.springframework.cglib.core;
1818

19-
import java.lang.reflect.UndeclaredThrowableException;
20-
21-
import org.springframework.cglib.transform.impl.UndeclaredThrowableStrategy;
22-
2319
/**
2420
* CGLIB GeneratorStrategy variant which exposes the application ClassLoader
2521
* as current thread context ClassLoader for the time of class generation.
@@ -29,19 +25,37 @@
2925
* @author Juergen Hoeller
3026
* @since 5.2
3127
*/
32-
public class ClassLoaderAwareGeneratorStrategy extends UndeclaredThrowableStrategy {
28+
public class ClassLoaderAwareGeneratorStrategy extends DefaultGeneratorStrategy {
3329

3430
private final ClassLoader classLoader;
3531

32+
private final GeneratorStrategy delegate;
33+
34+
35+
/**
36+
* Create a default GeneratorStrategy, exposing the given ClassLoader.
37+
* @param classLoader the ClassLoader to expose as current thread context ClassLoader
38+
*/
3639
public ClassLoaderAwareGeneratorStrategy(ClassLoader classLoader) {
37-
super(UndeclaredThrowableException.class);
3840
this.classLoader = classLoader;
41+
this.delegate = super::generate;
3942
}
4043

44+
/**
45+
* Create a decorator for the given GeneratorStrategy delegate, exposing the given ClassLoader.
46+
* @param classLoader the ClassLoader to expose as current thread context ClassLoader
47+
* @since 6.2
48+
*/
49+
public ClassLoaderAwareGeneratorStrategy(ClassLoader classLoader, GeneratorStrategy delegate) {
50+
this.classLoader = classLoader;
51+
this.delegate = delegate;
52+
}
53+
54+
4155
@Override
4256
public byte[] generate(ClassGenerator cg) throws Exception {
4357
if (this.classLoader == null) {
44-
return super.generate(cg);
58+
return this.delegate.generate(cg);
4559
}
4660

4761
Thread currentThread = Thread.currentThread();
@@ -51,15 +65,15 @@ public byte[] generate(ClassGenerator cg) throws Exception {
5165
}
5266
catch (Throwable ex) {
5367
// Cannot access thread context ClassLoader - falling back...
54-
return super.generate(cg);
68+
return this.delegate.generate(cg);
5569
}
5670

5771
boolean overrideClassLoader = !this.classLoader.equals(threadContextClassLoader);
5872
if (overrideClassLoader) {
5973
currentThread.setContextClassLoader(this.classLoader);
6074
}
6175
try {
62-
return super.generate(cg);
76+
return this.delegate.generate(cg);
6377
}
6478
finally {
6579
if (overrideClassLoader) {

spring-core/src/main/java/org/springframework/cglib/transform/impl/UndeclaredThrowableTransformer.java

+4-6
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import org.springframework.cglib.core.TypeUtils;
2828
import org.springframework.cglib.transform.ClassEmitterTransformer;
2929

30-
@SuppressWarnings({"rawtypes" })
30+
@SuppressWarnings("rawtypes")
3131
public class UndeclaredThrowableTransformer extends ClassEmitterTransformer {
3232

3333
private final Type wrapper;
@@ -57,18 +57,16 @@ public CodeEmitter begin_method(int access, final Signature sig, final Type[] ex
5757
return new CodeEmitter(e) {
5858
private final boolean isConstructor = Constants.CONSTRUCTOR_NAME.equals(sig.getName());
5959
private Block handler = begin_block();
60-
private boolean calToSuperWasSeen;
61-
60+
private boolean callToSuperSeen;
6261
@Override
6362
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
6463
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
65-
if (isConstructor && !calToSuperWasSeen && Constants.CONSTRUCTOR_NAME.equals(name)) {
64+
if (isConstructor && !callToSuperSeen && Constants.CONSTRUCTOR_NAME.equals(name)) {
6665
// we start the entry in the exception table after the call to super
6766
handler = begin_block();
68-
calToSuperWasSeen = true;
67+
callToSuperSeen = true;
6968
}
7069
}
71-
7270
@Override
7371
public void visitMaxs(int maxStack, int maxLocals) {
7472
handler.end();

0 commit comments

Comments
 (0)