Skip to content

Commit f6cb30b

Browse files
committed
@ExceptionHandler is able to process Error thrown from handler method
Issue: SPR-11106
1 parent 14bf650 commit f6cb30b

File tree

5 files changed

+89
-38
lines changed

5 files changed

+89
-38
lines changed

spring-web/src/main/java/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.util.ClassUtils;
3232
import org.springframework.util.ReflectionUtils.MethodFilter;
3333
import org.springframework.web.bind.annotation.ExceptionHandler;
34+
import org.springframework.web.util.NestedServletException;
3435

3536
/**
3637
* Discovers {@linkplain ExceptionHandler @ExceptionHandler} methods in a given class,
@@ -98,8 +99,8 @@ private List<Class<? extends Throwable>> detectExceptionMappings(Method method)
9899
}
99100

100101
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
101-
ExceptionHandler annot = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
102-
result.addAll(Arrays.asList(annot.value()));
102+
ExceptionHandler ann = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
103+
result.addAll(Arrays.asList(ann.value()));
103104
}
104105

105106
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
@@ -124,7 +125,11 @@ public boolean hasExceptionMappings() {
124125
* @return a Method to handle the exception, or {@code null} if none found
125126
*/
126127
public Method resolveMethod(Exception exception) {
127-
return resolveMethodByExceptionType(exception.getClass());
128+
Method method = resolveMethodByExceptionType(exception.getClass());
129+
if (method == null && exception instanceof NestedServletException && exception.getCause() != null) {
130+
method = resolveMethodByExceptionType(exception.getCause().getClass());
131+
}
132+
return method;
128133
}
129134

130135
/**
@@ -133,7 +138,7 @@ public Method resolveMethod(Exception exception) {
133138
* @param exceptionType the exception type
134139
* @return a Method to handle the exception, or {@code null} if none found
135140
*/
136-
public Method resolveMethodByExceptionType(Class<? extends Exception> exceptionType) {
141+
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
137142
Method method = this.exceptionLookupCache.get(exceptionType);
138143
if (method == null) {
139144
method = getMappedMethod(exceptionType);
@@ -145,7 +150,7 @@ public Method resolveMethodByExceptionType(Class<? extends Exception> exceptionT
145150
/**
146151
* Return the {@link Method} mapped to the given exception type, or {@code null} if none.
147152
*/
148-
private Method getMappedMethod(Class<? extends Exception> exceptionType) {
153+
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
149154
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
150155
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
151156
if (mappedException.isAssignableFrom(exceptionType)) {

spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -972,13 +972,19 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
972972
catch (Exception ex) {
973973
dispatchException = ex;
974974
}
975+
catch (Error err) {
976+
// As of 4.3, we're processing Errors thrown from handler methods as well,
977+
// making them available for @ExceptionHandler methods and other scenarios.
978+
dispatchException = new NestedServletException("Handler dispatch failed", err);
979+
}
975980
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
976981
}
977982
catch (Exception ex) {
978983
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
979984
}
980985
catch (Error err) {
981-
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
986+
triggerAfterCompletion(processedRequest, response, mappedHandler,
987+
new NestedServletException("Handler processing failed", err));
982988
}
983989
finally {
984990
if (asyncManager.isConcurrentHandlingStarted()) {
@@ -1304,16 +1310,6 @@ private void triggerAfterCompletion(HttpServletRequest request, HttpServletRespo
13041310
throw ex;
13051311
}
13061312

1307-
private void triggerAfterCompletionWithError(HttpServletRequest request, HttpServletResponse response,
1308-
HandlerExecutionChain mappedHandler, Error error) throws Exception {
1309-
1310-
ServletException ex = new NestedServletException("Handler processing failed", error);
1311-
if (mappedHandler != null) {
1312-
mappedHandler.triggerAfterCompletion(request, response, ex);
1313-
}
1314-
throw ex;
1315-
}
1316-
13171313
/**
13181314
* Restore the request attributes after an include.
13191315
* @param request current HTTP request

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
9595

9696
public ExceptionHandlerExceptionResolver() {
9797
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
98-
stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
98+
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
9999

100100
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
101101
this.messageConverters.add(new ByteArrayHttpMessageConverter());
@@ -364,7 +364,15 @@ protected ModelAndView doResolveHandlerMethodException(HttpServletRequest reques
364364
if (logger.isDebugEnabled()) {
365365
logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
366366
}
367-
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
367+
if (exception.getCause() != null) {
368+
// Expose root cause as provided argument as well
369+
exceptionHandlerMethod.invokeAndHandle(
370+
webRequest, mavContainer, exception, exception.getCause(), handlerMethod);
371+
}
372+
else {
373+
// Otherwise, just the given exception as-is
374+
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
375+
}
368376
}
369377
catch (Exception invocationEx) {
370378
if (logger.isDebugEnabled()) {

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -42,12 +42,9 @@
4242
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
4343
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
4444
import org.springframework.web.servlet.ModelAndView;
45+
import org.springframework.web.util.NestedServletException;
4546

46-
import static org.junit.Assert.assertEquals;
47-
import static org.junit.Assert.assertFalse;
48-
import static org.junit.Assert.assertNotNull;
49-
import static org.junit.Assert.assertNull;
50-
import static org.junit.Assert.assertTrue;
47+
import static org.junit.Assert.*;
5148

5249
/**
5350
* Test fixture with {@link ExceptionHandlerExceptionResolver}.
@@ -240,6 +237,22 @@ public void resolveExceptionWithHandlerMethodArg() throws Exception {
240237
assertEquals("HandlerMethod: handle", this.response.getContentAsString());
241238
}
242239

240+
@Test
241+
public void resolveExceptionWithAssertionError() throws Exception {
242+
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
243+
this.resolver.setApplicationContext(cxt);
244+
this.resolver.afterPropertiesSet();
245+
246+
AssertionError err = new AssertionError("argh");
247+
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
248+
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod,
249+
new NestedServletException("Handler dispatch failed", err));
250+
251+
assertNotNull("Exception was not handled", mav);
252+
assertTrue(mav.isEmpty());
253+
assertEquals(err.toString(), this.response.getContentAsString());
254+
}
255+
243256
@Test
244257
public void resolveExceptionControllerAdviceHandler() throws Exception {
245258
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
@@ -349,6 +362,11 @@ public String handleException(IllegalStateException ex) {
349362
public String handleWithHandlerMethod(HandlerMethod handlerMethod) {
350363
return "HandlerMethod: " + handlerMethod.getMethod().getName();
351364
}
365+
366+
@ExceptionHandler(AssertionError.class)
367+
public String handleAssertionError(Error err) {
368+
return err.toString();
369+
}
352370
}
353371

354372

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

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,30 +139,23 @@
139139
import org.springframework.web.servlet.support.RequestContextUtils;
140140
import org.springframework.web.servlet.view.InternalResourceViewResolver;
141141

142-
import static org.junit.Assert.assertArrayEquals;
143-
import static org.junit.Assert.assertEquals;
144-
import static org.junit.Assert.assertFalse;
145-
import static org.junit.Assert.assertNotNull;
146-
import static org.junit.Assert.assertNull;
147-
import static org.junit.Assert.assertSame;
148-
import static org.junit.Assert.assertTrue;
149-
import static org.junit.Assert.fail;
142+
import static org.junit.Assert.*;
150143

151144
/**
152145
* The origin of this test class is {@link ServletAnnotationControllerHandlerMethodTests}.
153146
*
154147
* Tests in this class run against the {@link HandlerMethod} infrastructure:
155148
* <ul>
156-
* <li>RequestMappingHandlerMapping
157-
* <li>RequestMappingHandlerAdapter
158-
* <li>ExceptionHandlerExceptionResolver
149+
* <li>RequestMappingHandlerMapping
150+
* <li>RequestMappingHandlerAdapter
151+
* <li>ExceptionHandlerExceptionResolver
159152
* </ul>
160153
*
161154
* <p>Rather than against the existing infrastructure:
162155
* <ul>
163-
* <li>DefaultAnnotationHandlerMapping
164-
* <li>AnnotationMethodHandlerAdapter
165-
* <li>AnnotationMethodHandlerExceptionResolver
156+
* <li>DefaultAnnotationHandlerMapping
157+
* <li>AnnotationMethodHandlerAdapter
158+
* <li>AnnotationMethodHandlerExceptionResolver
166159
* </ul>
167160
*
168161
* @author Rossen Stoyanchev
@@ -181,6 +174,18 @@ public void emptyValueMapping() throws Exception {
181174
assertEquals("test", response.getContentAsString());
182175
}
183176

177+
@Test
178+
public void errorThrownFromHandlerMethod() throws Exception {
179+
initServletWithControllers(ControllerWithErrorThrown.class);
180+
181+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
182+
request.setContextPath("/foo");
183+
request.setServletPath("");
184+
MockHttpServletResponse response = new MockHttpServletResponse();
185+
getServlet().service(request, response);
186+
assertEquals("test", response.getContentAsString());
187+
}
188+
184189
@Test
185190
public void customAnnotationController() throws Exception {
186191
initServletWithControllers(CustomAnnotationController.class);
@@ -1808,6 +1813,25 @@ public void myPath2(Exception ex, HttpServletResponse response) throws IOExcepti
18081813
}
18091814
}
18101815

1816+
@Controller
1817+
private static class ControllerWithErrorThrown {
1818+
1819+
@RequestMapping("")
1820+
public void myPath2(HttpServletResponse response) throws IOException {
1821+
throw new AssertionError("test");
1822+
}
1823+
1824+
@RequestMapping("/bar")
1825+
public void myPath3(HttpServletResponse response) throws IOException {
1826+
response.getWriter().write("testX");
1827+
}
1828+
1829+
@ExceptionHandler
1830+
public void myPath2(Error err, HttpServletResponse response) throws IOException {
1831+
response.getWriter().write(err.getMessage());
1832+
}
1833+
}
1834+
18111835
@Controller
18121836
static class MyAdaptedController {
18131837

0 commit comments

Comments
 (0)