JAVA EE项目中,不论是对底层的数据操做,仍是业务层的处理过程,仍是控制层的处理,都不可避免的会遇到各类可预知的(业务异常主动抛出)、不可预知的异常须要处理。通常dao层、service层的异常都会直接抛出,最后由controller统一进行处理,每一个过程都单独处理异常,且要考虑到异常信息和前端的反馈,代码的耦合度高,不统一,后期维护的工做也多。前端
同时还必须考虑异常模块和日志模块、国际化的支持。java
所以须要一种异常处理机制将异常处理解耦出来,这样保证相关处理过程的功能单一,和系统其它模块解耦,也实现了异常信息的统一处理和维护。程序员
接下来以实际工做中SpringMVC实现异常的统一处理为例。web
首先看看SpringMVC处理异常的3中方式,进行比较,最终选用一个比较合适的方式。ajax
对于第一种方式来讲,使用SimpleMappingExceptionResolver可以准确显示定义的异常处理页面,进行异常处理,具备集成简单、有良好的扩展性,由于是基于配置的对已有的代码没有侵入性等优势。可是该方法仅仅可以获取到异常信息,对于其余数据的状况不适用。配置方法以下:spring
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 定义默认的异常处理页面,当该异常类型的注册时使用 --> <property name="defaultErrorView" value="error"></property> <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception --> <property name="exceptionAttribute" value="ex"></property> <!-- 定义须要特殊处理的异常,用类名或彻底路径名做为key,异常也页名做为值 --> <property name="exceptionMappings"> <props> <prop key="cn.basttg.core.exception.BusinessException">error-business</prop> <prop key="cn.basttg.core.exception.ParameterException">error-parameter</prop> <!-- 这里还能够继续扩展对不一样异常类型的处理 --> </props> </property> </bean>
对于第二种方式,使用实现HandlerExceptionResolver接口的异常处理进行异常处理,具备集成简单、良好的扩展性、对已有代码没有侵入性等优势。同时因为自定义实现,咱们能够在处理异常时进行额外的处理(日志的记录、异常信息的国际化等)。项目实际的开发中也是使用的这种集成方案,配置以下:json
<bean id="exceptionResolver" class="com.***.**.common.exception.PlatformMappingExceptionResolver"> <!--配合自定义的异常解析器--> <property name="exceptionMappings"> <props> <prop key="com.***.**.common.exception.BusinessException">error/error</prop> <prop key="java.lang.Exception">error/error</prop> </props> </property> </bean>
对于第三种方式,经过@ExceptionHandler注解实现异常处理,一样十分灵活,不过这种方式须要在每一个controller上都需注解,解决方案是增长一个BaseController类,使用@ExceptionHandler注解声明异常处理,其余controller都继承他。实现方式以下:后端
public class BaseController { /** 基于@ExceptionHandler异常处理 */ @ExceptionHandler public String exp(HttpServletRequest request, Exception ex) { request.setAttribute("ex", ex); // 根据不一样错误转向不一样页面 if(ex instanceof BusinessException) { return "error-business"; }else if(ex instanceof ParameterException) { return "error-parameter"; } else { return "error"; } } }
使用这种方法存在侵入性,并且在异常处理时也不能获取异常之外的数据,且Ajax请求产生的异常信息没法反馈给前端。app
综合考虑,使用第二种方式进行异常统一处理方案的设计。异步
首先分析下方案应该实现的需求。
一、 首先自定义异常解析器,代码清单以下:
package com.cisdi.ecis.common.exception; import java.io.PrintWriter; import java.io.StringWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import com.cisdi.ecis.common.utils.ExceptionI18Message; /** * 平台异常信息跳转、解析 */ public class PlatformMappingExceptionResolver extends SimpleMappingExceptionResolver { static Logger logger = LoggerFactory.getLogger(PlatformMappingExceptionResolver.class); @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { String viewName = determineViewName(ex, request); // vm方式返回 if (viewName != null) { if (!( request.getHeader("accept").indexOf("application/json") > -1 || ( request .getHeader("X-Requested-With") != null && request .getHeader("X-Requested-With").indexOf("XMLHttpRequest") > -1 ) )) { // 非异步方式返回 Integer statusCode = determineStatusCode(request, viewName); if (statusCode != null) { applyStatusCodeIfPossible(request, response, statusCode); } // 跳转到提示页面 return getModelAndView(viewName, ex, request); } else { // 异步方式返回 try { PrintWriter writer = response.getWriter(); writer.write(ExceptionI18Message.getLocaleMessage(ex.getMessage())); response.setStatus(404, ExceptionI18Message.getLocaleMessage(ex.getMessage())); //将异常栈信息记录到日志中 logger.error(getTrace(ex)); writer.flush(); } catch ( Exception e ) { e.printStackTrace(); } // 不进行页面跳转 return null; } } else { return null; } } public static String getTrace(Throwable t) { StringWriter stringWriter= new StringWriter(); PrintWriter writer= new PrintWriter(stringWriter); t.printStackTrace(writer); StringBuffer buffer= stringWriter.getBuffer(); return buffer.toString(); } }
二、以后在SpringMVC配置文件中配置异常解析器映射路径。
<!--配置异常映射路径,ajax提示 --> <bean id="exceptionResolver" class="com.cisdi.ecis.common.exception.PlatformMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="com.cisdi.ecis.common.exception.BusinessException">error/error</prop> <prop key="java.lang.Exception">error/error</prop> </props> </property> </bean>
三、 异常信息的国际化
经过上述配置其实就已经知足了方案需求中的大部分需求,还仅剩一个需求:异常信息的国际化。上述代码中有一段代码:
ExceptionI18Message.getLocaleMessage(ex.getMessage()
ExceptionI18Message就是根据当前的语言环境获得异常信息,实现细则以下:
package com.cisdi.ecis.common.utils; import javax.servlet.http.HttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.support.RequestContext; public class ExceptionI18Message{ public static String getLocaleMessage(String key){ HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); RequestContext requestContext = new RequestContext(request); return requestContext.getMessage(key); } }
那么,全局异常处理器写好后,若是使用呢?后端程序员在编码时,能够直接抛出业务异常,可是压入的message应该是国际化文件中的"key",本身在去国际化文件中编写多套语言的key的value。例如:
##Exception pbs.exception.copyNode=The Exceptioninfo I18n
以后咱们压入的异常信息为pbs.exception.copyNode:
throw new Exception("pbs.exception.copyNode");
到此为止,方案已经设计完毕,简单的测试下是否知足咱们的需求吧,对于页面跳转的异常这里就不在测试了,主要在于前端Ajax请求controller抛出业务异常的时候前端是否可以收到反馈。
前端代码:
$.ajax({ url: "${basePath}/doc/addDocMaterials", type: "post", dataType: "json", data: obj, complete: function(xhr) { console.log(xhr); if (xhr.status == 200 && xhr.responseText != null) {} else { $.messager.alert('#springMessage("message.tip")', xhr.responseText); displayLoad(); } } });
以后后端主动抛出业务异常的时候,前端获取到的反馈结果以下:(这里咱们就以上面的抛出异常的代码为例)。
![]() |
到此为止,关于SpringMVC异常的统一处理方案(国际化、Ajax反馈)结束。