添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

前沿

在探寻 spring 的 异常处理机制 的时候, 我分别使用了三种方式。三种方式都是使用的 @ExceptionHandler 注解。

当一个 Controller 中有方法加了 @ExceptionHandler 之后,这个 Controller 其他方法中没有捕获的异常就会以参数的形式传入加了 @ExceptionHandler 注解的那个方法中。


第一种思路,设计一个基类。

/**
 * Created by liuruijie.
 * 处理异常的类,需要处理异常的Controller直接继承这个类
public class BaseController {
     * 处理Controller抛出的异常
     * @param e 异常实例
     * @return Controller层的返回值
    @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开始支持接口方法的默认实现)。

/**
 * Created by liuruijie.
 * 接口形式的异常处理
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注解。于是可以写一个全局的异常处理类:

/**
 * Created by liuruijie on 2016/12/28.
 * 全局异常处理,捕获所有Controller中抛出的异常。
@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) {
                // classMethodName、errMsg、args、errStack
                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);
    //            System.out.println(e);
            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){
    // Controller 层以外的
    if(e instanceof UndeclaredThrowableException) {
        e = (Exception) ((UndeclaredThrowableException) e).getUndeclaredThrowable();
        if(e instanceof ServiceException) {
            JSONObject jsonObject = (JSONObject) ((ServiceException) e).getData();
            throw new ServiceException(jsonObject);
    // Controller 层
    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;
        // 获取目标方法参数
//        Object[] args = pjp.getArgs();
        try {
//            int a = 1/0;
            Object rsObj = pjp.proceed(); // 执行目标方法
            System.out.println("=======Service环绕后通知=======");
            return rsObj;
        catch (Exception e) {
            // 封装异常信息
            JSONObject jsonObject = new JSONObject();
            // 参数使用Controller层还没有RequestJson解析前的参数
            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 类。

Spring Boot2.x-11 使用@ControllerAdvice和@ExceptionHandler实现自定义全局异常
Spring Boot2.x-11 使用@ControllerAdvice和@ExceptionHandler实现自定义全局异常
Spring Boot 系列(八)@ControllerAdvice 拦截异常并统一处理
在spring 3.2中,新增了@ControllerAdvice 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。
1.注解介绍 对于与数据库相关的 Spring MVC 项目,我们通常会把事务配置在Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事务管理器就会进行回滚。 如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。
SpringMVC——核心技术:异常处理(@ExceptionHandler、@ControllerAdvice)
SpringMVC——核心技术:异常处理(@ExceptionHandler、@ControllerAdvice)
【Spring Boot实战与进阶】全局异常处理@ExceptionHandler+@ControllerAdvice的使用
全局异常处理@ExceptionHandler+@ControllerAdvice的使用