前沿
在探寻 spring 的
异常处理机制
的时候,
我分别使用了三种方式。三种方式都是使用的 @ExceptionHandler 注解。
当一个 Controller 中有方法加了 @ExceptionHandler 之后,这个 Controller 其他方法中没有捕获的异常就会以参数的形式传入加了 @ExceptionHandler 注解的那个方法中。
第一种思路,设计一个基类。
public class BaseController {
@ExceptionHandler
@ResponseBody
public Object expHandler(Exception e){
if(e instanceof SystemException){
SystemException ex= (SystemException) e;
return WebResult.buildResult().status(ex.getCode())
.msg(ex.getMessage());
}else{
e.printStackTrace();
return WebResult.buildResult().status(Config.FAIL)
.msg("系统错误");
}
Ps:之后所有需要
异常处理
的Controller都继承这个类,从而获取到异常处理的方法。虽然这种方式可以解决问题,但是极其不灵活,因为动用了继承机制就只为获取一个默认的方法,这显然是不好的。
第二种思路,将这个基类变为接口,提供此方法的默认实现(也就是接口中的default方法,java8开始支持接口方法的默认实现)。
public interface DataExceptionSolver {
@ExceptionHandler
@ResponseBody
default Object exceptionHandler(Exception e){
try {
throw e;
} catch (SystemException systemException) {
systemException.printStackTrace();
return WebResult.buildResult().status(systemException.getCode())
.msg(systemException.getMessage());
} catch (Exception e1){
e1.printStackTrace();
return WebResult.buildResult().status(Config.FAIL)
.msg("系统错误");
}
Ps:这种方式虽然没有占用继承,但是也不是很优雅,因为几乎所有的Controller都需要进行异常处理,于是我每个Controller都需要去写implement DataExceptionSolver,这显然不是我真正想要的。况且这种方式依赖java8才有的语法,这是一个很大的局限。
第三种思路,使用加强Controller做全局异常处理。
所谓加强Controller就是@ControllerAdvice注解,有这个注解的类中的方法的某些注解会应用到所有的Controller里,其中就包括@ExceptionHandler注解。于是可以写一个全局的异常处理类:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(SystemException.class)
@ResponseBody
public Object customHandler(SystemException e){
e.printStackTrace();
return WebResult.buildResult().status(e.getCode()).msg(e.getMessage());
@ExceptionHandler(Exception.class)
@ResponseBody
public Object exceptionHandler(Exception e){
e.printStackTrace();
return WebResult.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 类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wxgj.common.LocalStorageMap;
import com.wxgj.common.ServerResponse;
import com.wxgj.common.ServiceException;
import com.wxgj.service.IMonitorService;
import com.wxgj.util.IpUtil;
import com.wxgj.util.MultipleDataSource;
import com.wxgj.util.UUIDUtil;
import com.wxgj.util.WxgjUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Map;
@ControllerAdvice
public class GlobalExHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource(name = "iMonitorService")
private IMonitorService iMonitorService;
@ResponseBody
@ExceptionHandler(value = Throwable.class)
public ServerResponse defaultErrorHandler (HttpSession session, HttpServletRequest req, Exception e) {
ServerResponse resp = ServerResponse.createErrorByMsg("系统未知错误,抢修中...");
try {
if(e instanceof UndeclaredThrowableException) {
e = (Exception) ((UndeclaredThrowableException) e).getUndeclaredThrowable();
if(e instanceof ServiceException) {
JSONObject jsonObject = (JSONObject) ((ServiceException) e).getData();
jsonObject.put("errorId", UUIDUtil.getUUIDString());
jsonObject.put("errorProjname", "xxx");
String ip = IpUtil.getIpAddress(req);
jsonObject.put("errorIp", ip);
Object zone = 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) {
Integer userType = requestUser.get("userType") != null ? (Integer) requestUser.get("userType") : null;
String orgName = requestUser.get("userOrgName") != null ? (String) requestUser.get("userOrgName") : null;
String userName = requestUser.get("userName") != null ? (String) requestUser.get("userName") : null;
jsonObject.put("errorUsertype", userType);
jsonObject.put("errorOrgname", orgName);
jsonObject.put("errorUsername", userName);
JSONObject joArgs = WxgjUtil.getRequestParam(req);
jsonObject.put("errorArgs", joArgs.toString());
jsonObject.put("errorStacktrace", JSON.toJSONString(jsonObject.get("errorStacktrace")));
MultipleDataSource.setThreadLocalDatasource("db_xxx");
iMonitorService.addError(jsonObject);
return resp;
catch (Exception ex) {
ex.printStackTrace();
finally {
MultipleDataSource.setThreadLocalDatasource("db_default");
return resp;
}
Ps:注意 spring.xml 需要把该类也扫描进 Bean 管理容器里。
2、AspectController 类
try {
pjp.proceed(...);
catch (Exception e){
if(e instanceof UndeclaredThrowableException) {
e = (Exception) ((UndeclaredThrowableException) e).getUndeclaredThrowable();
if(e instanceof ServiceException) {
JSONObject jsonObject = (JSONObject) ((ServiceException) e).getData();
throw new ServiceException(jsonObject);
JSONObject jsonObject = new JSONObject();
WxgjUtil.handleExData(classMethodName, e.getMessage(), null, e.getStackTrace(), jsonObject);
throw new ServiceException(jsonObject);
}
Ps:Controller 层切面,所以在 Controller 层里面的都不需要 try...catch...。
3、AspectService 类
import com.alibaba.fastjson.JSONObject;
import com.wxgj.common.ServiceException;
import com.wxgj.util.WxgjUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.lang.reflect.Method;
@Component
@Aspect
public class AspectService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut(value="execution(* com.xxx.service.*.*(..))")
private void pointAround(){}
@Around(value = "pointAround()")
public Object aroundAdvise(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("=======Service环绕前通知=======");
String className = pjp.getSignature().getDeclaringTypeName();
String methodName = getMethod(pjp).getName();
String classMethodName = className + "." + methodName;
try {
Object rsObj = pjp.proceed();
System.out.println("=======Service环绕后通知=======");
return rsObj;
catch (Exception e) {
JSONObject jsonObject = new JSONObject();
WxgjUtil.handleExData(classMethodName, e.getMessage(), null, e.getStackTrace(), jsonObject);
String logStr = "[" + classMethodName + "]: " + e.getMessage();
logger.error(logStr);
Transactional transactional = getMethod(pjp).getAnnotation(Transactional.class);
if(transactional != null)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
ServiceException serviceException = new ServiceException(jsonObject);
System.out.println(serviceException);
throw serviceException;
finally {
System.out.println("=======Service最终通知=======");
private Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
Signature sig = pjp.getSignature();
MethodSignature msig = (MethodSignature) sig;
Object target = pjp.getTarget();
return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
}
Ps:Service 层切面,所以在 Service 层里面的都不需要 try...catch...。
总结
执行顺序:Service 层 => Service 切面层 => Controller 层 => Controller 切面层 => GlobalExHandler 类。
如果中途有一层断了,则直接跳到 GlobalExHandler 类。