分享

spring mvc 输出 json 异常处理

 时间要去哪 2014-04-01
上一篇写了JSON支持,请求成功时,AnnotationMethodHandlerAdapter使用messageConverters将方法返回值输出到客户端。
如果请求失败呢?根本就没有返回值,怎么输出?

这种情况,这需要使用spring的错误解析器(ExceptionResolver)。当Controller发生异常,ExceptionResolver将被调用,如此便可以对上面的情况进行处理。

我们本来想使用spring的org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,很遗憾,这个错误解析器有bug,而且功能有缺陷,因此需要我们自己去实现一个ExceptionResolver。
这个bug难以描述,感兴趣的可以找出bug,去它WIKI提出,本人英文太烂,根本不会写.........。

实现ExceptionResolver的思路:
目前的异常处理:大部分的做法是在Controller中进行try...catch,try中处理正常请求,catch中处理异常请求。缺点:
1、每个方法都要这么写,代码重复。
2、捕获不到Controller外部的错误,例如某个拦截器的错误,这时将无法给客户一个有好的提示。
3、需要在catch语句块中记录日志,代码重复。
这个错误解析器应该有如下功能:
1、既然使用异常解析器,那么就不必在Controller中对异常进行处理,抛出即可,简化开发,异常统一控制。
2、ajax请求(有@ResponseBody的Controller)发生错误,输出JSON。
3、页面请求(无@ResponseBody的Controller)发生错误,输出错误页面。
4、它需要与AnnotationMethodHandlerAdapter使用同一个messageConverters
5、异常处理细节可控制。

请看 AnnotationHandlerMethodExceptionResolver ,这是我写的实现类。用法:

  1. /** 
  2.  * 不必在Controller中对异常进行处理,抛出即可,由此异常解析器统一控制。<br> 
  3.  * ajax请求(有@ResponseBody的Controller)发生错误,输出JSON。<br> 
  4.  * 页面请求(无@ResponseBody的Controller)发生错误,输出错误页面。<br> 
  5.  * 需要与AnnotationMethodHandlerAdapter使用同一个messageConverters<br> 
  6.  * Controller中需要有专门处理异常的方法。 
  7.  *  
  8.  * @author dongjian 
  9.  *  
  10.  * */  
  11. public class AnnotationHandlerMethodExceptionResolver extends ExceptionHandlerExceptionResolver {  
  12.       
  13.     private String defaultErrorView;  
  14.       
  15.     public String getDefaultErrorView() {  
  16.         return defaultErrorView;  
  17.     }  
  18.   
  19.     public void setDefaultErrorView(String defaultErrorView) {  
  20.         this.defaultErrorView = defaultErrorView;  
  21.     }  
  22.   
  23.     protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {  
  24.           
  25.         if (handlerMethod == null) {  
  26.             return null;  
  27.         }  
  28.           
  29.         Method method = handlerMethod.getMethod();  
  30.   
  31.         if (method == null) {  
  32.             return null;  
  33.         }  
  34.           
  35.         ModelAndView returnValue = super.doResolveHandlerMethodException(request, response, handlerMethod, exception);  
  36.           
  37.         ResponseBody responseBodyAnn = AnnotationUtils.findAnnotation(method, ResponseBody.class);  
  38.         if (responseBodyAnn != null) {  
  39.             try {  
  40.                 ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(method, ResponseStatus.class);  
  41.                 if (responseStatusAnn != null) {  
  42.                     HttpStatus responseStatus = responseStatusAnn.value();  
  43.                     String reason = responseStatusAnn.reason();  
  44.                     if (!StringUtils.hasText(reason)) {  
  45.                         response.setStatus(responseStatus.value());  
  46.                     } else {  
  47.                         try {  
  48.                             response.sendError(responseStatus.value(), reason);  
  49.                         } catch (IOException e) { }  
  50.                     }  
  51.                 }  
  52.               
  53.                 return handleResponseBody(returnValue, request, response);  
  54.             } catch (Exception e) {  
  55.                 return null;  
  56.             }  
  57.         }  
  58.           
  59.         if(returnValue.getViewName() == null){  
  60.             returnValue.setViewName(defaultErrorView);  
  61.         }  
  62.           
  63.         return returnValue;  
  64.           
  65.     }  
  66.       
  67.       
  68.     @SuppressWarnings({ "unchecked", "rawtypes" })  
  69.     private ModelAndView handleResponseBody(ModelAndView returnValue, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
  70.         Map value = returnValue.getModelMap();  
  71.         HttpInputMessage inputMessage = new ServletServerHttpRequest(request);  
  72.         List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();  
  73.         if (acceptedMediaTypes.isEmpty()) {  
  74.             acceptedMediaTypes = Collections.singletonList(MediaType.ALL);  
  75.         }  
  76.         MediaType.sortByQualityValue(acceptedMediaTypes);  
  77.         HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);  
  78.         Class<?> returnValueType = value.getClass();  
  79.         List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();  
  80.         if (messageConverters != null) {  
  81.             for (MediaType acceptedMediaType : acceptedMediaTypes) {  
  82.                 for (HttpMessageConverter messageConverter : messageConverters) {  
  83.                     if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {  
  84.                         messageConverter.write(value, acceptedMediaType, outputMessage);  
  85.                         return new ModelAndView();  
  86.                     }  
  87.                 }  
  88.             }  
  89.         }  
  90.         if (logger.isWarnEnabled()) {  
  91.             logger.warn("Could not find HttpMessageConverter that supports return type [" + returnValueType + "] and " + acceptedMediaTypes);  
  92.         }  
  93.         return null;  
  94.     }  
  95.   
  96. }  




------------------------web.xml-------------------------------------
spring mvc当然会自动注册一些异常解析器,我们需要禁止自动注册,让其使用我们自定义的类。
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
<init-param>
<param-name>detectAllHandlerExceptionResolvers</param-name><!-- 取消其自动注册的异常解析器 -->
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

---------------------------spring mvc配置 -------------------------------------------
<!-- 由于取消自动注册,DispatcherServlet会在spring上下文寻找 id 为 handlerExceptionResolver作为异常解析器 -->
<bean id="handlerExceptionResolver" class="com.jd.spsales.common.web.AnnotationHandlerMethodExceptionResolver">
<property name="defaultErrorView" value="error.vm"/><!-- 错误页面 -->
<property name="messageConverters" ref="messageConverters"/> <!--见上一篇,有定义过,标有@ResponseBody被此messageConverters输出-->
</bean>

-------------------------------BaseController 代码-------------------------------------------
所有Controller继承BaseController
/**
* 异常控制,这便是异常细节可控,将来可用于支持国际化(异常信息国际化)
* */
@ExceptionHandler(Exception.class)
@ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public ModelAndView handleException(Exception ex, HttpServletRequest request) {
return new ModelAndView().addObject("error", "错误信息");
}

---------------------error.vm代码---------------------
$!{error}


OK,到现在就可以了。下面让我们测试一下预期功能。
------------------------------测试代码DemoController extends BaseController --------------------------------------------
@RequestMapping("/demoAjax")
@ResponseBody
public Map demoAjax(String name) {
if(StringUtils.isEmpty(name) ) throw new RuntimeException();
return Collections.singletonMap("name", name);
}
@RequestMapping("/demoPage")
public ModelAndView demoPage(String name) {
if(StringUtils.isEmpty(name) ) throw new RuntimeException();
ModelAndView mav = new ModelAndView();
mav.setViewName("demo.vm");
return mav;
}

---------------------------测试代码之前端------------------------------
$.ajaxSetup({ //设置ajax请求全局默认设置
async : true,
error : function(jqXHR, textStatus, errorThrown){
var msg = $.parseJSON(jqXHR.responseText).error;
alert(msg);
},
traditional : true,
dataType : "json",
type : "POST"
});
$.ajax({ //请求将成功
url: "demoAjax.action",
data: {name: "you param"},
dataType : "json",
type : "POST",
success: function(data){
alert("请求发送成功,返回数据:" + data.name);
}
});
$.ajax({ //请求将失败,弹出人性化的错误信息
url: "demoAjax.action",
dataType : "json",
type : "POST",
success: function(data){
alert("请求发送成功,返回数据:" + data.name);
}
});
<a href="demoPage.action?name=you name">将返回demo页面</a>
<a href="demoPage.action">将返回错误页面</a>

到此异常处理完成。
我们不必在程序中对异常进行处理,不管三七二十一,全部抛出即可,代码大大简化。

请求处理情况如下:
ajax请求:正常时,使用@ResponseBody输出。错误时,返回错误的JSON串。
页面请求:正常时进入该页面,当请求发生异常时,返回错误页面。
不用编码处理JSON和异常处理




    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多