Skip to content

Commit b587a16

Browse files
committed
Expose all exception causes as provided handler method arguments
Closes gh-26317
1 parent dd8ea89 commit b587a16

File tree

2 files changed

+62
-15
lines changed

2 files changed

+62
-15
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java

Lines changed: 14 additions & 11 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.
@@ -410,24 +410,27 @@ protected ModelAndView doResolveHandlerMethodException(HttpServletRequest reques
410410
ServletWebRequest webRequest = new ServletWebRequest(request, response);
411411
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
412412

413+
ArrayList<Throwable> exceptions = new ArrayList<>();
413414
try {
414415
if (logger.isDebugEnabled()) {
415416
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
416417
}
417-
Throwable cause = exception.getCause();
418-
if (cause != null) {
419-
// Expose cause as provided argument as well
420-
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
421-
}
422-
else {
423-
// Otherwise, just the given exception as-is
424-
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
418+
// Expose causes as provided arguments as well
419+
Throwable exToExpose = exception;
420+
while (exToExpose != null) {
421+
exceptions.add(exToExpose);
422+
Throwable cause = exToExpose.getCause();
423+
exToExpose = (cause != exToExpose ? cause : null);
425424
}
425+
Object[] arguments = new Object[exceptions.size() + 1];
426+
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
427+
arguments[arguments.length - 1] = handlerMethod;
428+
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
426429
}
427430
catch (Throwable invocationEx) {
428-
// Any other than the original exception (or its cause) is unintended here,
431+
// Any other than the original exception (or a cause) is unintended here,
429432
// probably an accident (e.g. failed assertion or the like).
430-
if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
433+
if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
431434
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
432435
}
433436
// Continue with default processing of the original exception...

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java

Lines changed: 48 additions & 4 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.
@@ -66,6 +66,7 @@
6666
* @author Arjen Poutsma
6767
* @author Kazuki Shimizu
6868
* @author Brian Clozel
69+
* @author Rodolphe Lecocq
6970
* @since 3.1
7071
*/
7172
@SuppressWarnings("unused")
@@ -189,6 +190,18 @@ void resolveExceptionResponseBody() throws UnsupportedEncodingException, NoSuchM
189190
assertThat(this.response.getContentAsString()).isEqualTo("IllegalArgumentException");
190191
}
191192

193+
@Test // gh-26317
194+
void resolveExceptionResponseBodyMatchingCauseLevel2() throws UnsupportedEncodingException, NoSuchMethodException {
195+
Exception ex = new Exception(new Exception(new IllegalArgumentException()));
196+
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
197+
this.resolver.afterPropertiesSet();
198+
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
199+
200+
assertThat(mav).isNotNull();
201+
assertThat(mav.isEmpty()).isTrue();
202+
assertThat(this.response.getContentAsString()).isEqualTo("IllegalArgumentException");
203+
}
204+
192205
@Test
193206
void resolveExceptionResponseWriter() throws Exception {
194207
IllegalArgumentException ex = new IllegalArgumentException();
@@ -257,6 +270,21 @@ void resolveExceptionGlobalHandlerOrdered() throws Exception {
257270
assertThat(this.response.getContentAsString()).isEqualTo("TestExceptionResolver: IllegalStateException");
258271
}
259272

273+
@Test // gh-26317
274+
void resolveExceptionGlobalHandlerOrderedMatchingCauseLevel2() throws Exception {
275+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
276+
this.resolver.setApplicationContext(ctx);
277+
this.resolver.afterPropertiesSet();
278+
279+
Exception ex = new Exception(new Exception(new IllegalStateException()));
280+
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
281+
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
282+
283+
assertThat(mav).as("Exception was not handled").isNotNull();
284+
assertThat(mav.isEmpty()).isTrue();
285+
assertThat(this.response.getContentAsString()).isEqualTo("TestExceptionResolver: IllegalStateException");
286+
}
287+
260288
@Test // SPR-12605
261289
void resolveExceptionWithHandlerMethodArg() throws Exception {
262290
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
@@ -294,14 +322,15 @@ void resolveExceptionWithAssertionErrorAsRootCause() throws Exception {
294322
this.resolver.setApplicationContext(ctx);
295323
this.resolver.afterPropertiesSet();
296324

297-
AssertionError err = new AssertionError("argh");
298-
FatalBeanException ex = new FatalBeanException("wrapped", err);
325+
AssertionError rootCause = new AssertionError("argh");
326+
FatalBeanException cause = new FatalBeanException("wrapped", rootCause);
327+
Exception ex = new Exception(cause); // gh-26317
299328
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
300329
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
301330

302331
assertThat(mav).as("Exception was not handled").isNotNull();
303332
assertThat(mav.isEmpty()).isTrue();
304-
assertThat(this.response.getContentAsString()).isEqualTo(err.toString());
333+
assertThat(this.response.getContentAsString()).isEqualTo(rootCause.toString());
305334
}
306335

307336
@Test
@@ -319,6 +348,21 @@ void resolveExceptionControllerAdviceHandler() throws Exception {
319348
assertThat(this.response.getContentAsString()).isEqualTo("BasePackageTestExceptionResolver: IllegalStateException");
320349
}
321350

351+
@Test // gh-26317
352+
void resolveExceptionControllerAdviceHandlerMatchingCauseLevel2() throws Exception {
353+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
354+
this.resolver.setApplicationContext(ctx);
355+
this.resolver.afterPropertiesSet();
356+
357+
Exception ex = new Exception(new IllegalStateException());
358+
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
359+
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
360+
361+
assertThat(mav).as("Exception was not handled").isNotNull();
362+
assertThat(mav.isEmpty()).isTrue();
363+
assertThat(this.response.getContentAsString()).isEqualTo("BasePackageTestExceptionResolver: IllegalStateException");
364+
}
365+
322366
@Test
323367
void resolveExceptionControllerAdviceNoHandler() throws Exception {
324368
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);

0 commit comments

Comments
 (0)