Skip to content

Commit 7f8c91a

Browse files
committed
SPR-6902 - @responsebody does not work with @ExceptionHandler
1 parent 62f9f47 commit 7f8c91a

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolver.java

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.OutputStream;
2121
import java.io.Reader;
2222
import java.io.Writer;
23+
import java.io.IOException;
2324
import java.lang.reflect.InvocationTargetException;
2425
import java.lang.reflect.Method;
2526
import java.security.Principal;
@@ -33,6 +34,7 @@
3334
import java.util.Map;
3435
import javax.servlet.ServletRequest;
3536
import javax.servlet.ServletResponse;
37+
import javax.servlet.ServletException;
3638
import javax.servlet.http.HttpServletRequest;
3739
import javax.servlet.http.HttpServletResponse;
3840
import javax.servlet.http.HttpSession;
@@ -46,6 +48,7 @@
4648
import org.springframework.util.ReflectionUtils;
4749
import org.springframework.web.bind.annotation.ExceptionHandler;
4850
import org.springframework.web.bind.annotation.ResponseStatus;
51+
import org.springframework.web.bind.annotation.ResponseBody;
4952
import org.springframework.web.bind.support.WebArgumentResolver;
5053
import org.springframework.web.context.request.NativeWebRequest;
5154
import org.springframework.web.context.request.ServletWebRequest;
@@ -54,11 +57,23 @@
5457
import org.springframework.web.servlet.View;
5558
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
5659
import org.springframework.web.servlet.support.RequestContextUtils;
60+
import org.springframework.web.HttpMediaTypeNotAcceptableException;
61+
import org.springframework.http.converter.HttpMessageConverter;
62+
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
63+
import org.springframework.http.converter.StringHttpMessageConverter;
64+
import org.springframework.http.converter.FormHttpMessageConverter;
65+
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
66+
import org.springframework.http.HttpInputMessage;
67+
import org.springframework.http.MediaType;
68+
import org.springframework.http.HttpOutputMessage;
69+
import org.springframework.http.server.ServletServerHttpRequest;
70+
import org.springframework.http.server.ServletServerHttpResponse;
5771

5872
/**
5973
* Implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver} interface that handles
60-
* exceptions through the {@link ExceptionHandler} annotation. <p>This exception resolver is enabled by default in the
61-
* {@link org.springframework.web.servlet.DispatcherServlet}.
74+
* exceptions through the {@link ExceptionHandler} annotation.
75+
*
76+
* <p>This exception resolver is enabled by default in the {@link org.springframework.web.servlet.DispatcherServlet}.
6277
*
6378
* @author Arjen Poutsma
6479
* @since 3.0
@@ -67,6 +82,11 @@ public class AnnotationMethodHandlerExceptionResolver extends AbstractHandlerExc
6782

6883
private WebArgumentResolver[] customArgumentResolvers;
6984

85+
private HttpMessageConverter<?>[] messageConverters =
86+
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
87+
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
88+
89+
7090
/**
7191
* Set a custom ArgumentResolvers to use for special method parameter types. Such a custom ArgumentResolver will kick
7292
* in first, having a chance to resolve an argument value before the standard argument handling kicks in.
@@ -83,6 +103,14 @@ public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers)
83103
this.customArgumentResolvers = argumentResolvers;
84104
}
85105

106+
/**
107+
* Set the message body converters to use.
108+
* <p>These converters are used to convert from and to HTTP requests and responses.
109+
*/
110+
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
111+
this.messageConverters = messageConverters;
112+
}
113+
86114
@Override
87115
protected ModelAndView doResolveException(HttpServletRequest request,
88116
HttpServletResponse response,
@@ -331,6 +359,11 @@ private ModelAndView getModelAndView(Method handlerMethod, Object returnValue, S
331359
HttpServletResponse response = webRequest.getResponse();
332360
response.setStatus(responseStatus.value().value());
333361
}
362+
363+
if (returnValue != null && AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {
364+
return handleResponseBody(returnValue, webRequest);
365+
}
366+
334367
if (returnValue instanceof ModelAndView) {
335368
return (ModelAndView) returnValue;
336369
}
@@ -354,6 +387,36 @@ else if (returnValue == null) {
354387
}
355388
}
356389

390+
@SuppressWarnings("unchecked")
391+
private ModelAndView handleResponseBody(Object returnValue, ServletWebRequest webRequest)
392+
throws ServletException, IOException {
393+
394+
HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());
395+
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
396+
if (acceptedMediaTypes.isEmpty()) {
397+
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
398+
}
399+
MediaType.sortBySpecificity(acceptedMediaTypes);
400+
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
401+
Class<?> returnValueType = returnValue.getClass();
402+
if (messageConverters != null) {
403+
for (MediaType acceptedMediaType : acceptedMediaTypes) {
404+
for (HttpMessageConverter messageConverter : messageConverters) {
405+
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
406+
messageConverter.write(returnValue, acceptedMediaType, outputMessage);
407+
return new ModelAndView();
408+
}
409+
}
410+
}
411+
}
412+
if (logger.isWarnEnabled()) {
413+
logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " +
414+
acceptedMediaTypes);
415+
}
416+
return null;
417+
}
418+
419+
357420
/** Comparator capable of sorting exceptions based on their depth from the thrown exception type. */
358421
private static class DepthComparator implements Comparator<Class<? extends Throwable>> {
359422

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerExceptionResolverTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.util.ClassUtils;
3535
import org.springframework.web.bind.annotation.ExceptionHandler;
3636
import org.springframework.web.bind.annotation.ResponseStatus;
37+
import org.springframework.web.bind.annotation.ResponseBody;
3738
import org.springframework.web.servlet.ModelAndView;
3839

3940
/**
@@ -91,6 +92,17 @@ public void noModelAndView() throws UnsupportedEncodingException {
9192
assertTrue("ModelAndView not empty", mav.isEmpty());
9293
assertEquals("Invalid response written", "IllegalArgumentException", response.getContentAsString());
9394
}
95+
96+
@Test
97+
public void responseBody() throws UnsupportedEncodingException {
98+
IllegalArgumentException ex = new IllegalArgumentException();
99+
ResponseBodyController controller = new ResponseBodyController();
100+
request.addHeader("Accept", "text/plain");
101+
ModelAndView mav = exceptionResolver.resolveException(request, response, controller, ex);
102+
assertNotNull("No ModelAndView returned", mav);
103+
assertTrue("ModelAndView not empty", mav.isEmpty());
104+
assertEquals("Invalid response written", "IllegalArgumentException", response.getContentAsString());
105+
}
94106

95107
@Controller
96108
private static class SimpleController {
@@ -147,4 +159,14 @@ public void handle(Exception ex, Writer writer) throws IOException {
147159
writer.write(ClassUtils.getShortName(ex.getClass()));
148160
}
149161
}
162+
163+
@Controller
164+
private static class ResponseBodyController {
165+
166+
@ExceptionHandler(Exception.class)
167+
@ResponseBody
168+
public String handle(Exception ex) {
169+
return ClassUtils.getShortName(ex.getClass());
170+
}
171+
}
150172
}

0 commit comments

Comments
 (0)