赞
踩
笔者这边提供了两种处理全局异常的方式。这两种方式各有千秋,都很优雅。至于伙伴们想用哪种方式,那就仁者见仁,智者见智了。
在介绍异常处理方式前,先定义一些公共的类。这些类在两种处理方式中都会用到。
- /**
- * 自定义业务异常
- */
- @Data
- public class SunException extends RuntimeException {
-
- private Integer code;
- private String msg;
-
- public SunException(SystemEnum systemEnum) {
- this.code = systemEnum.getCode();
- this.msg = systemEnum.getDesc();
- }
- public SunException(BusinessEnum businessEnum) {
- this.code = businessEnum.getCode();
- this.msg = businessEnum.getDesc();
- }
-
- public SunException(Integer code, String msg) {
- this.code = code;
- this.msg = msg;
- }
- }
- /**
- * 系统枚举
- */
- public enum SystemEnum {
- SUCCESS(0, "success"),
- FAIL(-1, "fail"),
- PARAM_ILLEGAL(100, "参数非法!"),
- SERVICE_TIME_OUT(200, "服务间调用超时"),
- UNEXPECTED_EXCEPTION(500, "系统内部错误,请联系管理员!"),
- OTHER(9999, "Unknown Exception.");
-
- private Integer code;
- private String desc;
-
- SystemEnum(Integer code, String desc) {
- this.code = code;
- this.desc = desc;
- }
-
- public Integer getCode() {
- return code;
- }
-
- public String getDesc() {
- return desc;
- }
- }
- /**
- * 业务类枚举
- *
- * @author: dong
- * @date: 2023/2/12 21:50
- * @since: 1.0
- */
- public enum BusinessEnum {
- /**--------用户相关----------**/
- USER_ID_NOT_EXIST(1000, "userId not exist."),
-
- /**--------任务相关----------**/
- TASK_ID_NOT_EXIST(2000, "taskId not exist."),
- TASK_NAME_NOT_EXIST(2001, "taskName not exist."),
- TASK_TYPE_NOT_EXIST(2002, "taskType not exist."),
- DEADLINE_NOT_EXIST(2003, "deadline not exist."),
- CONTENT_NOT_EXIST(2004, "content not exist.")
- ;
-
- private Integer code;
- private String desc;
-
- BusinessEnum(Integer code, String desc) {
- this.code = code;
- this.desc = desc;
- }
-
- public Integer getCode() {
- return code;
- }
-
- public void setCode(Integer code) {
- this.code = code;
- }
-
- public String getDesc() {
- return desc;
- }
-
- public void setDesc(String desc) {
- this.desc = desc;
- }
- }
- /**
- * 统一封装响应体
- * @param <T>
- */
- public class BaseResult<T> {
- private Integer code;
- private String msg;
- private T data;
-
- public BaseResult() {
- }
-
- public BaseResult(Integer code, String msg, T data) {
- this.code = code;
- this.msg = msg;
- this.data = data;
- }
-
- public BaseResult(T data) {
- this.code = SystemEnum.SUCCESS.getCode();
- this.msg = SystemEnum.SUCCESS.getDesc();
- this.data = data;
- }
-
- public Integer getCode() {
- return code;
- }
-
- public void setCode(Integer code) {
- this.code = code;
- }
-
- public String getMsg() {
- return msg;
- }
-
- public void setMsg(String msg) {
- this.msg = msg;
- }
-
- public T getData() {
- return data;
- }
-
- public void setData(T data) {
- this.data = data;
- }
- }
- /**
- * 响应结果工具类
- */
- public class ResultUtil {
-
- public static <R> BaseResult<R> outSuccess() {
- return new BaseResult<>(SystemEnum.SUCCESS.getCode(), SystemEnum.SUCCESS.getDesc(), null);
- }
- public static <R> BaseResult<R> outSuccess(R data) {
- return new BaseResult<>(data);
- }
-
- public static <R> BaseResult<R> outFail(String errorMsg) {
- return new BaseResult<>(SystemEnum.FAIL.getCode(), errorMsg, null);
- }
- public static <R> BaseResult<R> outFail(Integer errorCode, String errorMsg) {
- return new BaseResult<>(errorCode, errorMsg, null);
- }
- }
如下所示,
a. 新建一个全局异常处理类,并在类名前加上@RestControllerAdvice注解,该注解可以拦截项目中抛出的异常;
b. 同时在新建一个处理异常的方法,并在方法上加上@ExceptionHandler注解,并在该注解的属性中指定具体的异常。如下代码中指定的具体异常即是 SunException(也就是上一小节中笔者自定义的业务异常)。
c. 在处理异常的方法中,通过ResultUtil.outFail() 方法统一封装返回给前端的响应体。
- /**
- * 全局异常处理
- */
- @RestControllerAdvice
- public class GlobalExceptionHandler {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
-
- @ExceptionHandler(SunException.class)
- @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
- public BaseResult handlerBusinessException(SunException sunException) {
- LOGGER.error("exception happened at {}", sunException.getMsg());
- return ResultUtil.outFail(sunException.getCode(), sunException.getMsg());
- }
- }
那么在具体的业务接口中如何抛出异常能被GlobalExceptionHandler所捕获呢?
其实很简单,只要用 throw new SunException(...); 就可以了。注意:这里只能抛出SunException,不能抛出RuntimeException或者任何其他异常。因为@ExceptionHandler已经指定了具体的异常类型。
- @GetMapping("/hello")
- public String sayHello(String name) {
- if (StringUtils.isEmpty(name)) {
- throw new SunException(SystemEnum.PARAM_ILLEGAL);
- }
- return "The sun is rising.";
- }
Swagger测试效果:
很明显,aop天生就是干这个的料。aop在业务解耦方面简直如鱼得水,像统一打印日志,统一捕获异常等等。talk is cheap,show me the code.
如下代码所示:
a. 新建这个aop监控类,新增切点,切所有模块的Controller层的所有方法;
b. 实现一个环绕通知接口,打印方法入参以及请求结果;
c. 注意代码40行到45行,就是捕获项目中所有的SunException,并封装成统一的响应体返回前端。
- /**
- * aop监控类
- **/
- @Slf4j
- @Aspect
- @Component
- public class AspectMonitor {
-
- /**
- * 日志切点,切所有模块controller层中的所有方法
- */
- @Pointcut("execution(* com.bxbro.*..controller..*.*(..))")
- public void logPointCut() {
- // do nothing.
- }
-
-
- /**
- * 对接口做统一的日志及异常处理
- * @param pjp
- * @return
- */
- @Around("logPointCut()")
- public Object apiMonitor(ProceedingJoinPoint pjp) {
- Object[] args = pjp.getArgs();
- Object[] arguments = new Object[args.length];
- for (int i=0;i<args.length;i++) {
- // ServletRequest不能序列化,从入参里排除,
- // 否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
- if(args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
- continue;
- }
- arguments[i] = args[i];
- }
- log.info("请求入参:{},方法名:{}.{}", JSON.toJSONString(arguments),pjp.getSignature().getDeclaringTypeName(),pjp.getSignature().getName());
- Object result;
- try {
- result = pjp.proceed();
- } catch (Throwable throwable) {
- if (throwable instanceof SunException) {
- SunException sunException = (SunException) throwable;
- return ResultUtil.outFail(sunException.getCode(), sunException.getMsg());
- }
- log.error("接口请求异常:{}", throwable);
- return ResultUtil.outFail("系统内部错误" + throwable.getMessage());
- }
- log.info("请求结果:{}", JSON.toJSONString(result));
- return result;
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。