2020import java .io .OutputStream ;
2121import java .io .Reader ;
2222import java .io .Writer ;
23+ import java .io .IOException ;
2324import java .lang .reflect .InvocationTargetException ;
2425import java .lang .reflect .Method ;
2526import java .security .Principal ;
3334import java .util .Map ;
3435import javax .servlet .ServletRequest ;
3536import javax .servlet .ServletResponse ;
37+ import javax .servlet .ServletException ;
3638import javax .servlet .http .HttpServletRequest ;
3739import javax .servlet .http .HttpServletResponse ;
3840import javax .servlet .http .HttpSession ;
4648import org .springframework .util .ReflectionUtils ;
4749import org .springframework .web .bind .annotation .ExceptionHandler ;
4850import org .springframework .web .bind .annotation .ResponseStatus ;
51+ import org .springframework .web .bind .annotation .ResponseBody ;
4952import org .springframework .web .bind .support .WebArgumentResolver ;
5053import org .springframework .web .context .request .NativeWebRequest ;
5154import org .springframework .web .context .request .ServletWebRequest ;
5457import org .springframework .web .servlet .View ;
5558import org .springframework .web .servlet .handler .AbstractHandlerExceptionResolver ;
5659import 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
0 commit comments