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 类。

目录
相关文章
|
10月前
|
JSON Java 数据库
第08课:Spring Boot中的全局异常处理
第08课:Spring Boot中的全局异常处理
1013 0
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
本文介绍了在Spring Boot项目中如何通过创建`GlobalExceptionHandler`类来全局处理系统异常。通过使用`@ControllerAdvice`注解,可以拦截项目中的各种异常,并结合`@ExceptionHandler`注解针对特定异常(如参数缺失、空指针等)进行定制化处理。文中详细展示了处理参数缺失异常和空指针异常的示例代码,并说明了通过拦截`Exception`父类实现统一异常处理的方法。虽然拦截`Exception`可一劳永逸,但为便于问题排查,建议优先处理常见异常,最后再兜底处理未知异常,确保返回给调用方的信息友好且明确。
1503 0
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——拦截自定义异常
本文介绍了在实际项目中如何拦截自定义异常。首先,通过定义异常信息枚举类 `BusinessMsgEnum`,统一管理业务异常的代码和消息。接着,创建自定义业务异常类 `BusinessErrorException`,并在其构造方法中传入枚举类以实现异常信息的封装。最后,利用 `GlobalExceptionHandler` 拦截并处理自定义异常,返回标准的 JSON 响应格式。文章还提供了示例代码和测试方法,展示了全局异常处理在 Spring Boot 项目中的应用价值。
610 0
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——定义返回的统一 json 结构
本课主要讲解Spring Boot中的全局异常处理方法。在项目开发中,各层操作难免会遇到各种异常,若逐一处理将导致代码耦合度高、维护困难。因此,需将异常处理从业务逻辑中分离,实现统一管理与友好反馈。本文通过定义一个简化的JsonResult类(含状态码code和消息msg),结合全局异常拦截器,展示如何封装并返回标准化的JSON响应,从而提升代码质量和用户体验。
343 0
|
开发框架 Java UED
如何使用 Spring Boot 实现异常处理
如何使用 Spring Boot 实现异常处理
635 2
|
Java Spring UED
Spring框架的异常处理秘籍:打造不败之身的应用!
【8月更文挑战第31天】在软件开发中,异常处理对应用的稳定性和健壮性至关重要。Spring框架提供了一套完善的异常处理机制,包括使用`@ExceptionHandler`注解和配置`@ControllerAdvice`。本文将详细介绍这两种方式,并通过示例代码展示其具体应用。`@ExceptionHandler`可用于控制器类中的方法,处理特定异常;而`@ControllerAdvice`则允许定义全局异常处理器,捕获多个控制器中的异常。
246 0
|
Java API 开发者
【开发者福音】Spring Boot 异常处理:优雅应对错误,提升应用健壮性,让调试不再是噩梦!
【8月更文挑战第29天】本文通过对比传统错误处理方式与Spring Boot推荐的最佳实践,展示了如何在Spring Boot应用中实现统一且优雅的异常处理。传统方法需在每个可能出错的地方显式处理异常,导致代码冗余且不一致。而Spring Boot的全局异常处理机制则能集中处理所有异常,简化代码并确保错误响应格式统一,提高应用程序的健壮性和可维护性。文中提供了具体的示例代码以帮助读者更好地理解和应用这一机制。
643 0
|
10月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1337 0
|
11月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
1159 0
|
7月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
583 4