Spring - @ControllerAdvice + @ExceptionHandler 实现全局异常处理

简介: Spring - @ControllerAdvice + @ExceptionHandler 实现全局异常处理

前沿

在探寻 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 类。

目录
相关文章
|
2月前
|
Java Spring UED
Spring框架的异常处理秘籍:打造不败之身的应用!
【8月更文挑战第31天】在软件开发中,异常处理对应用的稳定性和健壮性至关重要。Spring框架提供了一套完善的异常处理机制,包括使用`@ExceptionHandler`注解和配置`@ControllerAdvice`。本文将详细介绍这两种方式,并通过示例代码展示其具体应用。`@ExceptionHandler`可用于控制器类中的方法,处理特定异常;而`@ControllerAdvice`则允许定义全局异常处理器,捕获多个控制器中的异常。
44 0
|
2月前
|
Java API 开发者
【开发者福音】Spring Boot 异常处理:优雅应对错误,提升应用健壮性,让调试不再是噩梦!
【8月更文挑战第29天】本文通过对比传统错误处理方式与Spring Boot推荐的最佳实践,展示了如何在Spring Boot应用中实现统一且优雅的异常处理。传统方法需在每个可能出错的地方显式处理异常,导致代码冗余且不一致。而Spring Boot的全局异常处理机制则能集中处理所有异常,简化代码并确保错误响应格式统一,提高应用程序的健壮性和可维护性。文中提供了具体的示例代码以帮助读者更好地理解和应用这一机制。
82 0
|
3月前
|
JSON Java 数据库
Spring Boot中的全局异常处理
主要讲解了Spring Boot 的全局异常处理,包括异常信息的封装、异常信息的捕获和处理,以及在实际项目中,我们用到的自定义异常枚举类和业务异常的捕获与处理,在项目中运用的非常广泛,基本上每个项目中都需要做全局异常处理。
|
3月前
|
JSON Java API
Spring Boot中的异常处理策略
Spring Boot中的异常处理策略
|
4月前
|
JSON Java API
Spring Boot中的异常处理策略
Spring Boot中的异常处理策略
|
5月前
|
前端开发 Java 程序员
Spring Boot统一功能处理(拦截器, 统一数据返回格式, 统一异常处理)
Spring Boot统一功能处理(拦截器, 统一数据返回格式, 统一异常处理)
77 1
|
5月前
|
Java 开发者 UED
Spring Boot异常处理:优雅处理应用程序错误
【4月更文挑战第28天】异常处理是任何应用程序开发中不可或缺的一部分。Spring Boot提供了强大的异常处理机制,能够帮助开发者优雅地处理各种错误情况,并向用户提供友好的错误信息。本篇博客将介绍Spring Boot中异常处理的基本概念,并通过实例演示如何实现异常处理。
96 0
|
23天前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
4天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
21 2
|
2月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决