AOP 实现全局异常处理

第一种:

1. 我们将采用Spring AOP统一处理异常,统一返回后端接口的结果。

2. 使用一个自定义异常和一个错误前端提示枚举来逐层传递消息

3. 一个错误枚举来代替新建异常信息类,减少业务异常信息文件的数量

//正常返回的枚举
    SUCCESS(true, 2000,"正常返回", "操作成功"), 
 
    // 系统错误,50开头
    SYS_ERROR(false, 5000, "系统错误", "亲,系统出错了哦~"),
    PARAM_INVILAD(false, 5001, "参数出现异常", "参数出现异常"), 
    DATA_NO_COMPLETE(false, 5002, "数据填写不完整,请检查", "数据填写不完整,请检查");
 
    private ErrorMsgEnum(boolean ok, int code, String msg ,String userMsg) {
        this.ok = ok;
        this.code = code;
        this.msg = msg;
        this.userMsg = userMsg;
    }
 
    private boolean ok;
    private int code;
    private String msg;
    private String userMsg;
}

控制层返回结果POJO类

public class JsonResponse{
 
    String msg;
    Object data;
 
    public JsonResponse() {
        msg = "";
        data = null;
    }
 
    public static JsonResponse newOk() {
        JsonResponse response = new JsonResponse();
        response.setState(State.newOk());
        return response;
    }
 
    public static JsonResponse newOk(Object data) {
        JsonResponse response = new JsonResponse();
        response.setData(data);
        response.setState(State.newOk());
        return response;
    }
 
    public static JsonResponse newError() {
        JsonResponse response = new JsonResponse();
        response.setMsg("无情的系统异常!");
        return response;
    }
 
    public static JsonResponse newError(ErrorMsgEnum errorMsgEnum) {
        JsonResponse response = new JsonResponse();
        state.setMsg(errorMsgEnum.getErrorMsg());
        return response;
    }
}

    自定义异常类

public class CustomException extends Exception {
 
    private ErrorMsgEnum errorMsgEnum;
 
    public CustomException(ErrorMsgEnum errorMsgEnum) {
        this.errorMsgEnum = errorMsgEnum;
    }
}

AOP捕获异常处理类

@Around("execution(public * com.jason.*.controller..*.*(..))")
public JsonResponse serviceAOP(ProceedingJoinPoint pjp) throws Exception {
 
    JsonResponse newResultVo = null;
 
    try {
        return (JsonResponse) pjp.proceed();
    } catch (CustomException e) {
        logger.info("自定义业务异常:" + e.getMessage());
        ErrorMsgEnum errorMsgEnum = e.getErrorMsgEnum();
        if (Objects.nonNull(errorMsgEnum)) {
            newResultVo = JsonResponse.newError(errorMsgEnum);
        } else {
            newResultVo = JsonResponse.newError(e.getMessage());    
        }
    } catch (Exception e) {
        //可以顺便处理你的日志,此处能取到方法名,参数等等
        logger.error("出现运行时异常:", e);
        newResultVo = JsonResponse.newError();
    }
 
    return newResultVo;
 
}

第二种

在springboot中使用aop 来处理异常

1. 在SpringBoot中引入AOP

<!--spring切面aop依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 创建一个返回体报文的实体类

public class Result<T> {
 
   //    error_code 状态值:0 极为成功,其他数值代表失败
   private Integer status;
 
   //    error_msg 错误信息,若status为0时,为success
   private String msg;
 
   //    content 返回体报文的出参,使用泛型兼容不同的类型
   private T data;
 
   public Integer getStatus() {
       return status;
   }
 
   public void setStatus(Integer code) {
       this.status = code;
   }
 
   public String getMsg() {
       return msg;
   }
 
   public void setMsg(String msg) {
       this.msg = msg;
   }
 
   public T getData(Object object) {
       return data;
   }
 
   public void setData(T data) {
       this.data = data;
   }
 
   public T getData() {
       return data;
   }
 
   @Override
   public String toString() {
       return "Result{" +
               "status=" + status +
               ", msg='" + msg + '\'' +
               ", data=" + data +
               '}';
   }

3. 创建一个枚举类,来记录一些我们已知的错误信息,可以在代码中直接使用。

public enum ExceptionEnum {
    UNKNOW_ERROR(-1,"未知错误"),
    USER_NOT_FIND(-101,"用户不存在"),
;
 
    private Integer code;
 
    private String msg;
 
    ExceptionEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
 
    public Integer getCode() {
        return code;
    }
 
    public String getMsg() {
        return msg;
    }
}

4. 创建一个工具类在代码中使用

public class ResultUtil {
 
    /**
     * 返回成功,传入返回体具体出參
     * @param object
     * @return
     */
    public static Result success(Object object){
        Result result = new Result();
        result.setStatus(0);
        result.setMsg("success");
        result.setData(object);
        return result;
    }
 
    /**
     * 提供给部分不需要出參的接口
     * @return
     */
    public static Result success(){
        return success(null);
    }
 
    /**
     * 自定义错误信息
     * @param code
     * @param msg
     * @return
     */
    public static Result error(Integer code,String msg){
        Result result = new Result();
        result.setStatus(code);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }
 
    /**
     * 返回异常信息,在已知的范围内
     * @param exceptionEnum
     * @return
     */
    public static Result error(ExceptionEnum exceptionEnum){
        Result result = new Result();
        result.setStatus(exceptionEnum.getCode());
        result.setMsg(exceptionEnum.getMsg());
        result.setData(null);
        return result;
    }
}

5. 一般系统抛出的错误是不含错误代码的,除去部分的404,400,500错误之外,我们如果想把错误代码定义的更细致,就需要自己继承RuntimeException这个类后重新定义一个构造方法来定义我们自己的错误信息:

public class DescribeException extends RuntimeException{
 
    private Integer code;
 
    /**
     * 继承exception,加入错误状态值
     * @param exceptionEnum
     */
    public DescribeException(ExceptionEnum exceptionEnum) {
        super(exceptionEnum.getMsg());
        this.code = exceptionEnum.getCode();
    }
 
    /**
     * 自定义错误信息
     * @param message
     * @param code
     */
    public DescribeException(String message, Integer code) {
        super(message);
        this.code = code;
    }
 
    public Integer getCode() {
        return code;
    }
 
    public void setCode(Integer code) {
        this.code = code;
    }
}

6. 使用一个Handle来把Try,Catch中捕获的错误进行判定,是一个我们已知的错误信息,还是一个未知的错误信息,如果是未知的错误信息,那我们就用log记录它,便于之后的查找和解决:

    @ControllerAdvice
    public class ExceptionHandle {
 
      private final static Logger LOGGER = LoggerFactory.getLogger(ExceptionHandle.class);
 
      /**
       * 判断错误是否是已定义的已知错误,不是则由未知错误代替,同时记录在log中
       * @param e
       * @return
       */
      @ExceptionHandler(value = Exception.class)
      @ResponseBody
      public Result exceptionGet(Exception e){
          if(e instanceof DescribeException){
              DescribeException MyException = (DescribeException) e;
              return ResultUtil.error(MyException.getCode(),MyException.getMessage());
          }
 
          LOGGER.error("【系统异常】{}",e);
          return ResultUtil.error(ExceptionEnum.UNKNOW_ERROR);
      }
    }

7. 我们使用接口若出现了异常,很难知道是谁调用接口,是前端还是后端出现的问题导致异常的出现,那这时,AOP久发挥作用了,我们之前已经引入了AOP的依赖,现在我们编写一个切面类

@Aspect
@Component
public class HttpAspect {
 
    private final static Logger LOGGER = LoggerFactory.getLogger(HttpAspect.class);
 
    @Autowired
    private ExceptionHandle exceptionHandle;
 
    @Pointcut("execution(public * com.zzp.controller.*.*(..))")
    public void log(){
 
    }
 
    @Before("log()")
    public void doBefore(JoinPoint joinPoint){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
 
        //url
        LOGGER.info("url={}",request.getRequestURL());
        //method
        LOGGER.info("method={}",request.getMethod());
        //ip
        LOGGER.info("id={}",request.getRemoteAddr());
        //class_method
        LOGGER.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName() + "," + joinPoint.getSignature().getName());
        //args[]
        LOGGER.info("args={}",joinPoint.getArgs());
    }
 
    @Around("log()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Result result = null;
        try {
 
        } catch (Exception e) {
            return exceptionHandle.exceptionGet(e);
        }
        if(result == null){
            return proceedingJoinPoint.proceed();
        }else {
            return result;
        }
    }
 
    @AfterReturning(pointcut = "log()",returning = "object")//打印输出结果
    public void doAfterReturing(Object object){
        LOGGER.info("response={}",object.toString());
    }
}

8. 我们使用@Aspect来声明这是一个切面,使用@Pointcut来定义切面所需要切入的位置,这里我们是对每一个HTTP请求都需要切入,在进入方法之前我们使用@Before记录了调用的接口URL,调用的方法,调用方的IP地址以及输入的参数等。在整个接口代码运作期间,我们使用@Around来捕获异常信息,并用之前定义好的Result进行异常的返回,最后我们使用@AfterReturning来记录我们的出參。 
以上全部,我们就完成了异常的统一管理以及切面获取接口信息

@RestController
@RequestMapping("/result")
public class ResultController {
 
    @Autowired
    private ExceptionHandle exceptionHandle;
 
    /**
     * 返回体测试
     * @param name
     * @param pwd
     * @return
     */
    @RequestMapping(value = "/getResult",method = RequestMethod.POST)
    public Result getResult(@RequestParam("name") String name, @RequestParam("pwd") String pwd){
        Result result = ResultUtil.success();
        try {
            if (name.equals("zzp")){
                result =  ResultUtil.success(new UserInfo());
            }else if (name.equals("pzz")){
                result =  ResultUtil.error(ExceptionEnum.USER_NOT_FIND);
            }else{
                int i = 1/0;
            }
        }catch (Exception e){
            result =  exceptionHandle.exceptionGet(e);
        }
        return result;
    }
}

结果:

 

第三种:基于xml 配置的aop

上图中的接口上没有任何方法,但是实现了这个接口的类必须至少实现以下4个方法中的一个,否则程序报:

Caused by: java.lang.IllegalArgumentException: At least one handler method must be found in class

接口中的异常类可以为自己自定义的异常类,方法是通过反射调用。

1. 自定义异常类

package com.apt.study.exception;

public class StudyException extends RuntimeException {

    /**
     * 
     */
    private static final long serialVersionUID = -6183216129830888521L;

    /**
     * 异常信息
     */
    protected String message;
    
    /**
     * 异常编码
     */
    protected int code;
    
    public static final StudyException PARAM_VALIDATE_EXCEPTION = new StudyException(1234,"参数校验出错");
    
    public StudyException(int code, String msgFormat, Object... args) {
        super(String.format(msgFormat, args));
        this.code = code;
        this.message = String.format(msgFormat, args);
    }
    
    public StudyException(String message, Throwable cause) {
        super(message, cause);
    }

    public StudyException(Throwable cause) {
        super(cause);
    }

    public StudyException(String message) {
        super(message);
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
    
    
}

2. 异常日志处理类

package com.apt.study.exception;

import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.ThrowsAdvice;

public class ExceptionLogHandler implements ThrowsAdvice{

    public Logger logger = LoggerFactory.getLogger(ExceptionLogHandler.class);
    
    public void afterThrowing(Method method, Object[] args, Object target, StudyException ex) {
        logger.error("ExceptionLogHandler--StudyExcception");
        logger.info("-------errCode:" + ex.getCode() + "; errMessage:" + ex.getMessage());
        errorClassInfo(ex);
        logger.info("-------" + ex.fillInStackTrace());
    }
    
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        
        logger.error("ExceptionLogHandler--Exception");
        logger.error("------->Erro Class: "+ target.getClass().getName());
        logger.error("------->Error method:"+ method.getName());
        
        if(args != null) {
            for(int i=0; i<args.length; i++) {
                logger.error("------->args[" + i + "]: " + args[i]);
            }
        }
        
        logger.error("------->Exception class: " + ex.getClass().getName());
        errorClassInfo(ex);
        logger.error("------->" + ex.fillInStackTrace());
    }
    
    //打印抛出异常地方的信息
    private void errorClassInfo(Exception ex) {
        /*
         * ex.getStackTrace()返回堆栈跟踪元素的数组,每个元素表示一个堆栈帧。数组的第零个元素(假定数据的长度为非零)表示堆栈顶部,
         * 它是序列中最后的方法调用。
         * 通常,这是创建和抛出该 throwable 的地方。数组的最后元素(假定数据的长度为非零)表示堆栈底部,它是序列中第一个方法调用
         */
        StackTraceElement[] stackTraceElementArr= ex.getStackTrace();
        StackTraceElement stackTraceElement = stackTraceElementArr[0];
        logger.error("------->Erro File:" + stackTraceElement.getFileName());
        logger.error("------->Erro Method:" + stackTraceElement.getMethodName());
        logger.error("------->Erro Line:" + stackTraceElement.getLineNumber());
    }
    
}

3. spring-context.xml 配置

<!-- 异常信息 拦截 -->
     <bean id="exceptionLog" class="com.apt.study.exception.ExceptionLogHandler"></bean>  
     <aop:config>
        <aop:pointcut id="exceptionTrade" expression="execution(* com.apt.study.service.*.*(..))" />
        <aop:advisor pointcut-ref="exceptionTrade" advice-ref="exceptionLog"/>
    </aop:config>

 

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值