Skip to content

Commit cc90a95

Browse files
committed
Reject invalid afterThrowing signature on ThrowsAdvice
Closes gh-1896
1 parent 6dbd684 commit cc90a95

File tree

3 files changed

+41
-27
lines changed

3 files changed

+41
-27
lines changed

spring-aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java

Lines changed: 34 additions & 10 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-2023 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.
@@ -27,6 +27,7 @@
2727
import org.apache.commons.logging.LogFactory;
2828

2929
import org.springframework.aop.AfterAdvice;
30+
import org.springframework.aop.framework.AopConfigException;
3031
import org.springframework.lang.Nullable;
3132
import org.springframework.util.Assert;
3233

@@ -78,21 +79,44 @@ public ThrowsAdviceInterceptor(Object throwsAdvice) {
7879

7980
Method[] methods = throwsAdvice.getClass().getMethods();
8081
for (Method method : methods) {
81-
if (method.getName().equals(AFTER_THROWING) &&
82-
(method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
83-
Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];
84-
if (Throwable.class.isAssignableFrom(throwableParam)) {
85-
// An exception handler to register...
86-
this.exceptionHandlerMap.put(throwableParam, method);
87-
if (logger.isDebugEnabled()) {
88-
logger.debug("Found exception handler method on throws advice: " + method);
82+
if (method.getName().equals(AFTER_THROWING)) {
83+
Class<?> throwableParam = null;
84+
if (method.getParameterCount() == 1) {
85+
// just a Throwable parameter
86+
throwableParam = method.getParameterTypes()[0];
87+
if (!Throwable.class.isAssignableFrom(throwableParam)) {
88+
throw new AopConfigException("Invalid afterThrowing signature: " +
89+
"single argument must be a Throwable subclass");
8990
}
9091
}
92+
else if (method.getParameterCount() == 4) {
93+
// Method, Object[], target, throwable
94+
Class<?>[] paramTypes = method.getParameterTypes();
95+
if (!Method.class.equals(paramTypes[0]) || !Object[].class.equals(paramTypes[1]) ||
96+
Throwable.class.equals(paramTypes[2]) || !Throwable.class.isAssignableFrom(paramTypes[3])) {
97+
throw new AopConfigException("Invalid afterThrowing signature: " +
98+
"four arguments must be Method, Object[], target, throwable: " + method);
99+
}
100+
throwableParam = paramTypes[3];
101+
}
102+
if (throwableParam == null) {
103+
throw new AopConfigException("Unsupported afterThrowing signature: single throwable argument " +
104+
"or four arguments Method, Object[], target, throwable expected: " + method);
105+
}
106+
// An exception handler to register...
107+
Method existingMethod = this.exceptionHandlerMap.put(throwableParam, method);
108+
if (existingMethod != null) {
109+
throw new AopConfigException("Only one afterThrowing method per specific Throwable subclass " +
110+
"allowed: " + method + " / " + existingMethod);
111+
}
112+
if (logger.isDebugEnabled()) {
113+
logger.debug("Found exception handler method on throws advice: " + method);
114+
}
91115
}
92116
}
93117

94118
if (this.exceptionHandlerMap.isEmpty()) {
95-
throw new IllegalArgumentException(
119+
throw new AopConfigException(
96120
"At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
97121
}
98122
}

spring-aop/src/test/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptorTests.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,26 @@
2323
import org.aopalliance.intercept.MethodInvocation;
2424
import org.junit.jupiter.api.Test;
2525

26+
import org.springframework.aop.framework.AopConfigException;
2627
import org.springframework.aop.testfixture.advice.MyThrowsHandler;
2728

2829
import static org.assertj.core.api.Assertions.assertThat;
2930
import static org.assertj.core.api.Assertions.assertThatException;
3031
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
31-
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
3232
import static org.mockito.BDDMockito.given;
3333
import static org.mockito.Mockito.mock;
3434

3535
/**
3636
* @author Rod Johnson
3737
* @author Chris Beams
38+
* @author Juergen Hoeller
3839
*/
3940
public class ThrowsAdviceInterceptorTests {
4041

4142
@Test
4243
public void testNoHandlerMethods() {
4344
// should require one handler method at least
44-
assertThatIllegalArgumentException().isThrownBy(() ->
45+
assertThatExceptionOfType(AopConfigException.class).isThrownBy(() ->
4546
new ThrowsAdviceInterceptor(new Object()));
4647
}
4748

@@ -77,9 +78,7 @@ public void testCorrectHandlerUsed() throws Throwable {
7778
given(mi.getMethod()).willReturn(Object.class.getMethod("hashCode"));
7879
given(mi.getThis()).willReturn(new Object());
7980
given(mi.proceed()).willThrow(ex);
80-
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() ->
81-
ti.invoke(mi))
82-
.isSameAs(ex);
81+
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(ex);
8382
assertThat(th.getCalls()).isEqualTo(1);
8483
assertThat(th.getCalls("ioException")).isEqualTo(1);
8584
}
@@ -92,9 +91,7 @@ public void testCorrectHandlerUsedForSubclass() throws Throwable {
9291
ConnectException ex = new ConnectException("");
9392
MethodInvocation mi = mock();
9493
given(mi.proceed()).willThrow(ex);
95-
assertThatExceptionOfType(ConnectException.class).isThrownBy(() ->
96-
ti.invoke(mi))
97-
.isSameAs(ex);
94+
assertThatExceptionOfType(ConnectException.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(ex);
9895
assertThat(th.getCalls()).isEqualTo(1);
9996
assertThat(th.getCalls("remoteException")).isEqualTo(1);
10097
}
@@ -117,9 +114,7 @@ public void afterThrowing(RemoteException ex) throws Throwable {
117114
ConnectException ex = new ConnectException("");
118115
MethodInvocation mi = mock();
119116
given(mi.proceed()).willThrow(ex);
120-
assertThatExceptionOfType(Throwable.class).isThrownBy(() ->
121-
ti.invoke(mi))
122-
.isSameAs(t);
117+
assertThatExceptionOfType(Throwable.class).isThrownBy(() -> ti.invoke(mi)).isSameAs(t);
123118
assertThat(th.getCalls()).isEqualTo(1);
124119
assertThat(th.getCalls("remoteException")).isEqualTo(1);
125120
}

spring-aop/src/testFixtures/java/org/springframework/aop/testfixture/advice/MyThrowsHandler.java

Lines changed: 1 addition & 6 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-2023 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.
@@ -34,9 +34,4 @@ public void afterThrowing(RemoteException ex) throws Throwable {
3434
count("remoteException");
3535
}
3636

37-
/** Not valid, wrong number of arguments */
38-
public void afterThrowing(Method m, Exception ex) throws Throwable {
39-
throw new UnsupportedOperationException("Shouldn't be called");
40-
}
41-
4237
}

0 commit comments

Comments
 (0)