前沿
在探寻 spring 的异常处理机制的时候,我分别使用了三种方式。三种方式都是使用的 @ExceptionHandler 注解。
当一个 Controller 中有方法加了 @ExceptionHandler 之后,这个 Controller 其他方法中没有捕获的异常就会以参数的形式传入加了 @ExceptionHandler 注解的那个方法中。
第一种思路,设计一个基类。
/*** Created by liuruijie.* 处理异常的类,需要处理异常的Controller直接继承这个类*/publicclassBaseController { /*** 处理Controller抛出的异常* @param e 异常实例* @return Controller层的返回值*/@ExceptionHandler@ResponseBodypublicObjectexpHandler(Exceptione){ if(einstanceofSystemException){ SystemExceptionex= (SystemException) e; returnWebResult.buildResult().status(ex.getCode()) .msg(ex.getMessage()); }else{ e.printStackTrace(); returnWebResult.buildResult().status(Config.FAIL) .msg("系统错误"); } } }
Ps:之后所有需要异常处理的Controller都继承这个类,从而获取到异常处理的方法。虽然这种方式可以解决问题,但是极其不灵活,因为动用了继承机制就只为获取一个默认的方法,这显然是不好的。
第二种思路,将这个基类变为接口,提供此方法的默认实现(也就是接口中的default方法,java8开始支持接口方法的默认实现)。
/*** Created by liuruijie.* 接口形式的异常处理*/publicinterfaceDataExceptionSolver { @ExceptionHandler@ResponseBodydefaultObjectexceptionHandler(Exceptione){ try { throwe; } catch (SystemExceptionsystemException) { systemException.printStackTrace(); returnWebResult.buildResult().status(systemException.getCode()) .msg(systemException.getMessage()); } catch (Exceptione1){ e1.printStackTrace(); returnWebResult.buildResult().status(Config.FAIL) .msg("系统错误"); } } }
Ps:这种方式虽然没有占用继承,但是也不是很优雅,因为几乎所有的Controller都需要进行异常处理,于是我每个Controller都需要去写implement DataExceptionSolver,这显然不是我真正想要的。况且这种方式依赖java8才有的语法,这是一个很大的局限。
第三种思路,使用加强Controller做全局异常处理。
所谓加强Controller就是@ControllerAdvice注解,有这个注解的类中的方法的某些注解会应用到所有的Controller里,其中就包括@ExceptionHandler注解。于是可以写一个全局的异常处理类:
/*** Created by liuruijie on 2016/12/28.* 全局异常处理,捕获所有Controller中抛出的异常。*/@ControllerAdvicepublicclassGlobalExceptionHandler { //处理自定义的异常@ExceptionHandler(SystemException.class) @ResponseBodypublicObjectcustomHandler(SystemExceptione){ e.printStackTrace(); returnWebResult.buildResult().status(e.getCode()).msg(e.getMessage()); } //其他未处理的异常@ExceptionHandler(Exception.class) @ResponseBodypublicObjectexceptionHandler(Exceptione){ e.printStackTrace(); returnWebResult.buildResult().status(Config.FAIL).msg("系统错误"); } }
Ps:这个类中只处理了两个异常,但是已经满足了大部分需要,如果还有需要特殊处理的地方,可以再加上处理的方法就行了。第三种实现方式是目前我知道的最优雅的方式了。
因此下面我们来详细谈谈第三种设计思路...
通常在 Controller 层需要去捕获 Service 层的异常,防止返回一些不友好的错误信息到客户端,但如果 Controller 层每个方法都用模块化的 try-catch 代码去捕获异常,会很难看也难维护。
异常处理最好是解耦的,并且都放在一个地方集中管理。Spring能够较好的处理这种问题,核心如下,这里主要关注前两个:
@ExceptionHandler:统一处理某一类异常,从而能够减少代码重复率和复杂度
@ControllerAdvice:异常集中处理,更好的使业务逻辑与异常处理剥离开
单使用 @ExceptionHandler,只能在当前Controller中处理异常,与 @ControllerAdvice 组合使用,则可以实现全局异常处理,不用每个 Controller 都配置。
下面通过一个实际项目代码来看一下 @ControllerAdvice + @ExceptionHandler 的使用。
@ControllerAdvice 定义全局异常处理类 GlobalExceptionHandler
@ExceptionHandler 声明异常处理方法,使用 value 指定异常类,value = Exception.class 表示处理 Controller 层抛出的 Exception 及其子类的异常,这样 Controller 层代码就不需要进行异常处理了。
GlobalExceptionHandler 类中对多个异常进行了处理,这些异常分两类,一类是自定义异常,一类是非自定义异常。
1、GlobalExHandler 类
importcom.alibaba.fastjson.JSON; importcom.alibaba.fastjson.JSONObject; importcom.wxgj.common.LocalStorageMap; importcom.wxgj.common.ServerResponse; importcom.wxgj.common.ServiceException; importcom.wxgj.service.IMonitorService; importcom.wxgj.util.IpUtil; importcom.wxgj.util.MultipleDataSource; importcom.wxgj.util.UUIDUtil; importcom.wxgj.util.WxgjUtil; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.util.StringUtils; importorg.springframework.web.bind.annotation.ControllerAdvice; importorg.springframework.web.bind.annotation.ExceptionHandler; importorg.springframework.web.bind.annotation.ResponseBody; importjavax.annotation.Resource; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpSession; importjava.lang.reflect.UndeclaredThrowableException; importjava.util.Map; @ControllerAdvicepublicclassGlobalExHandler { privatefinalLoggerlogger=LoggerFactory.getLogger(this.getClass()); @Resource(name="iMonitorService") privateIMonitorServiceiMonitorService; @ResponseBody@ExceptionHandler(value=Throwable.class) publicServerResponsedefaultErrorHandler (HttpSessionsession, HttpServletRequestreq, Exceptione) { ServerResponseresp=ServerResponse.createErrorByMsg("系统未知错误,抢修中..."); try { // 因为代理模式的原因,会引起未知异常,但就藏在其中,取出来即可if(einstanceofUndeclaredThrowableException) { e= (Exception) ((UndeclaredThrowableException) e).getUndeclaredThrowable(); } // 判断各种异常类型if(einstanceofServiceException) { // classMethodName、errMsg、args、errStackJSONObjectjsonObject= (JSONObject) ((ServiceException) e).getData(); jsonObject.put("errorId", UUIDUtil.getUUIDString()); jsonObject.put("errorProjname", "xxx"); Stringip=IpUtil.getIpAddress(req); jsonObject.put("errorIp", ip); Objectzone=LocalStorageMap.getLocalStorageMap(ip); if (StringUtils.isEmpty(zone)) { zone=IpUtil.getIpZoneByHttp(ip); LocalStorageMap.setLocalStorageMap(ip, zone); } jsonObject.put("errorAddress", zone.toString()); Map<String,Object>requestUser= (Map<String, Object>) session.getAttribute("requestUser"); if(requestUser!=null) { IntegeruserType=requestUser.get("userType") !=null? (Integer) requestUser.get("userType") : null; StringorgName=requestUser.get("userOrgName") !=null? (String) requestUser.get("userOrgName") : null; StringuserName=requestUser.get("userName") !=null? (String) requestUser.get("userName") : null; jsonObject.put("errorUsertype", userType); jsonObject.put("errorOrgname", orgName); jsonObject.put("errorUsername", userName); } JSONObjectjoArgs=WxgjUtil.getRequestParam(req); jsonObject.put("errorArgs", joArgs.toString()); jsonObject.put("errorStacktrace", JSON.toJSONString(jsonObject.get("errorStacktrace"))); // 每次请求默认数据源是配置文件的数据源,所以这里切换后,也不需要切换回来MultipleDataSource.setThreadLocalDatasource("db_xxx"); iMonitorService.addError(jsonObject); // System.out.println(e); } returnresp; } catch (Exceptionex) { ex.printStackTrace(); } finally { MultipleDataSource.setThreadLocalDatasource("db_default"); } returnresp; } }
Ps:注意 spring.xml 需要把该类也扫描进 Bean 管理容器里。
2、AspectController 类
try { pjp.proceed(...); // 执行目标方法} catch (Exceptione){ // Controller 层以外的if(einstanceofUndeclaredThrowableException) { e= (Exception) ((UndeclaredThrowableException) e).getUndeclaredThrowable(); if(einstanceofServiceException) { JSONObjectjsonObject= (JSONObject) ((ServiceException) e).getData(); thrownewServiceException(jsonObject); } } // Controller 层JSONObjectjsonObject=newJSONObject(); WxgjUtil.handleExData(classMethodName, e.getMessage(), null, e.getStackTrace(), jsonObject); thrownewServiceException(jsonObject); }
Ps:Controller 层切面,所以在 Controller 层里面的都不需要 try...catch...。
3、AspectService 类
importcom.alibaba.fastjson.JSONObject; importcom.wxgj.common.ServiceException; importcom.wxgj.util.WxgjUtil; importorg.aspectj.lang.ProceedingJoinPoint; importorg.aspectj.lang.Signature; importorg.aspectj.lang.annotation.Around; importorg.aspectj.lang.annotation.Aspect; importorg.aspectj.lang.annotation.Pointcut; importorg.aspectj.lang.reflect.MethodSignature; importorg.slf4j.Logger; importorg.slf4j.LoggerFactory; importorg.springframework.stereotype.Component; importorg.springframework.transaction.annotation.Transactional; importorg.springframework.transaction.interceptor.TransactionAspectSupport; importjava.lang.reflect.Method; @Component@AspectpublicclassAspectService { privatefinalLoggerlogger=LoggerFactory.getLogger(this.getClass()); @Pointcut(value="execution(* com.xxx.service.*.*(..))") privatevoidpointAround(){} @Around(value="pointAround()") publicObjectaroundAdvise(ProceedingJoinPointpjp) throwsThrowable { System.out.println("=======Service环绕前通知======="); // 获取全类名StringclassName=pjp.getSignature().getDeclaringTypeName(); // 获取方法名StringmethodName=getMethod(pjp).getName(); // 全类方法名StringclassMethodName=className+"."+methodName; // 获取目标方法参数// Object[] args = pjp.getArgs();try { // int a = 1/0;ObjectrsObj=pjp.proceed(); // 执行目标方法System.out.println("=======Service环绕后通知======="); returnrsObj; } catch (Exceptione) { // 封装异常信息JSONObjectjsonObject=newJSONObject(); // 参数使用Controller层还没有RequestJson解析前的参数WxgjUtil.handleExData(classMethodName, e.getMessage(), null, e.getStackTrace(), jsonObject); StringlogStr="["+classMethodName+"]: "+e.getMessage(); logger.error(logStr); Transactionaltransactional=getMethod(pjp).getAnnotation(Transactional.class); if(transactional!=null) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } ServiceExceptionserviceException=newServiceException(jsonObject); System.out.println(serviceException); throwserviceException; } finally { System.out.println("=======Service最终通知======="); } } // 获取目标方法privateMethodgetMethod(ProceedingJoinPointpjp) throwsNoSuchMethodException { Signaturesig=pjp.getSignature(); MethodSignaturemsig= (MethodSignature) sig; Objecttarget=pjp.getTarget(); returntarget.getClass().getMethod(msig.getName(), msig.getParameterTypes()); } }
Ps:Service 层切面,所以在 Service 层里面的都不需要 try...catch...。
总结
执行顺序:Service 层 => Service 切面层 => Controller 层 => Controller 切面层 => GlobalExHandler 类。
如果中途有一层断了,则直接跳到 GlobalExHandler 类。