springboot全局异常捕获

目录

 

全局异常捕获

自定义异常

@Valid


全局异常捕获

什么是异常?程序在启动或者运行时没有按照预期的执行,在执行途中发生某种未知的错误,导致程序非正常停止或者报错。

在我们的程序中,肯定会伴随着很多的异常,启动时:空对象、找不到数据库、用户名密码不对等等异常,都会在程序启动时抛出异常信息,运行时:空引用、参数不匹配等等都会在程序运行时抛出异常,启动的时候抛出异常我们可以马上修改,但是程序正在运行的突然报了一个错,如果没有对这个错误做处理,用户可能会看到一堆的代码信息,很不友好,所以今天讲解一下springboot全局异常捕获。

我们先看一个程序没有做异常处理会发生什么事情,下面是一个小小的例子:

private static List<String> list = new ArrayList<String>();


    static {
        list.add("小明");
        list.add("小红");
        list.add(null);
    }

    /**
     * 测试
     * @return
     */
    @GetMapping(value = "/test")
    public String test(){
        for(String s: list){
            if(s.equals("小红")){
                log.info("听说点赞的都发财了!!!");
            }else{
                log.info("没点赞的好像也发财了!!!");
            }
        }

        return "success";
    }

这是一个典型的空指针异常,真正写代码的时候是不会这么干的,这里为了展示效果才这样写的,我们请求/test接口,看看会发生什么?

我靠?这是什么玩意?这要是让用户/甲方爸爸看到,那还不得被骂死?那如何解决这个问题呢?

我们将for循环这段代码加try/catch异常捕获处理。

改造后的代码:

 /**
     * 测试
     * @return
     */
    @GetMapping(value = "/test")
    public String test(){
        try{
            for(String s: list){
                if(s.equals("小红")){
                    log.info("听说点赞的都发财了!!!");
                }else{
                    log.info("没点赞的好像也发财了!!!");
                }
            }
        }catch (Exception e){
            return "网络繁忙,请稍后再试";
        }
        return "success";
    }

再次访问:

发现已经不是提示代码信息了,而是提示了比较友好的网络繁忙,那这个时候,你可能就会有问题了,那我岂不是需要在每个接口请求中添加一个try/catch异常捕获?这样不仅让代码的可读性变差,还会牵扯到代码的可维护性,以后接手代码的同事心里可能是崩溃的。

 

                                                    

java有没有一种统一的处理方式呢?让特定的错误返回特定的提示,java这么强大,怎么可能没有?下面我们来讲讲如何实现springboot的全局异常捕获。

我们先定义一个全局异常捕获类:GlobalExceptionRespone,由于Exception是大部分异常的大哥大,所以我们针对Exception做一个异常处理

package com.ymy.exceptions;

import com.ymy.utils.ConstantUtil;
import com.ymy.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@Slf4j
@RestControllerAdvice
public class GlobalExceptionRespone {


    /**
     * 全局异常捕获
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public Object errorHandler( Exception e) {
        log.error("网络繁忙,请稍后再试 ->",e);
        return Result.faild(ConstantUtil.ResponeCode.SYS_ERROR_CODE,ConstantUtil.ResponeCode.SYS_ERROR);
    }


    

}

这个时候我们去掉业务代码中的try/catch:

@GetMapping(value = "/test")
    public String test() {
        for (String s : list) {
            if (s.equals("小红")) {
                log.info("听说点赞的都发财了!!!");
            } else {
                log.info("没点赞的好像也发财了!!!");
            }
        }
        return "success";
    }

重启,运行,结果如下:

发现了什么?程序发现了空指针,但是并没有像最开始那样抛出乱信息,而是经过我们处理过的信息,这感觉是不是不要太爽,就增加一个类,就解决了程序中需要假如try/catch的苦恼。

                                                 

细心的你可能发现我在全局异常捕获类中添加了一个注解:@RestControllerAdvice,这个注解有着怎样的魅力呢?

@RestControllerAdvice是一个组合注解:@ControllerAdvice+@ResponseBody

spring4.3之后被引入的一个注解,它可以被使用到HandlerMapping中,通过@ExceptionHandler来处理HandlerMapping中的异常信息。

@ExceptionHandler通过指定特定的异常类对象来做出对应的处理,如果没有指定到特定的异常类那么他将找到与这个异常类最接近的类,比如之前的空指针异常,我们并没有针对空指针做一个全局异常处理,所以@ExceptionHandler找到了他的大哥:@Exception,如果我们配置一下空指针的异常捕获,会出现什么样的结果呢?

在全局异常捕获类中加入:

/**
     * 全局异常捕获
     * @param e
     * @return
     */
    @ExceptionHandler(value = NullPointerException.class)
    public Object nullPointerException( Exception e) {
        log.error("程序中出现空引用,请检查! ->",e);
        return Result.faild(ConstantUtil.ResponeCode.NULL_POINTER_EXCEPTION_CODE,ConstantUtil.ResponeCode.NULL_POINTER_EXCEPTION);
    }

启动程序:

是不是像我刚刚说所的那样,@ExceptionHandler会优先寻找报错的异常对象,找到了直接返回,没有找到,继续往后找。

在这里我展示几种常见的异常类:

1.MissingServletRequestParameterException:缺少参数子类。

2.HttpMessageNotReadableException:参数解析失败。

3.MethodArgumentNotValidException:当对用@Valid注释的参数进行验证失败时,将引发异常。

4.BindException:参数绑定错误并且是致命错误的时候。

5.ConstraintViolationException:违反约束条件。

6.ValidationException:基础异常中所有bean验证异常的问题。

7.NoHandlerFoundException:默认情况下,当DispatcherServlet找不到请求的处理程序时,它将发送404响应。但是,如果将其属性“ throwExceptionIfNoHandlerFound” *设置为 true,则会引发此异常,并且可以使用配置的HandlerExceptionResolver进行处理。

8.HttpRequestMethodNotSupportedException:不支持的请求方式

9.HttpMediaTypeNotSupportedException:当客户端发布、放置或请求处理程序不支持的类型。

10.DuplicateKeyException:当试图插入或更新数据导致违反主键或惟一约束时引发异常。

自定义异常

需求:删除用户信息,包含三张表:用户基础信息、用户详细信息、用户图像表。

假设删除用户基础信息返回1(成功)、删除详细信息返回1(成功)、删除图像信息返回0(失败)。

如果我们将这三个放到一个事物里面,事务肯定是会提交的,因为程序没有发生异常,只是再删除的时候没有返回1而已,所以这时候我们就需要自己创建一个异常,让程序抛出。

现在我们就用一个自定义的异常来解决这个问题。

第一步:创建一个自定义的异常类继承RuntimeException(运行时异常)。

package com.ymy.exceptions;

import lombok.Getter;

@Getter
public class MyException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    private String code;

    /**
     * 提示消息
     */
    private String msg;

    public MyException(String code,String msg) {
        this.code = code;
        this.msg = msg;
    }


}

第二步:在全局异常类(GlobalExceptionRespone.java)中加入自定义的异常捕获:

 /**
     * 业务层需要自己声明异常的情况
     */
    @ExceptionHandler(MyException.class)
    public Object handleMyTokenExcption(MyException e) {
        log.error("->",e);
        return Result.faild(e.getCode(),e.getMsg());
    }

 

第三步,再返回0的时候调用自定义的异常:

/**
     * 修啊给i用户信息
     *
     * @return
     */
    @RequestMapping(value = "/updateUser", method = RequestMethod.POST)
    public Result addUser() {
        int count = 1;
        log.info("第一步:删除基础信息。。。。。。。。。。。");


        log.info("第二步:删除详细信息。。。。。。。。。。。");

        log.info("第三步:删除图像信息。。。。。。。。。。。,结果返回了0,表示删除失败");
        count = 0;
        if(count == 0){
            throw  new MyException("delete_excption","删除图像信息失败,请过一会在尝试");
        }

        return Result.OK();

    }

这里就不具体去实现了,主要是看如何调用自定义的异常使用,调用updateUser接口:

程序抛出了异常,这就会导致事务不会提交,是我们的预期结果,我们再来看看返回给用户的信息:

提示消息正是我在删除失败之后调用自定义异常。

@Valid

相信很多人都是用过:@Valid,他是用来校验参数的一个重要工具,他能简化我们很多的if代码语句,下面我们来介绍一下他的使用方法。

需求,添加一个用户,需要添加:用户名、手机、邮箱、年龄,并且字段都不可为空,邮箱格式需要正确,年龄必须在0-120岁之间。

正常的写法是不是需要在controller中添加一堆的if语句,来判断传递的参数是否符合要求,今天我们讲解一种可读性更好,代码更简单的方式:@Valid注解。

用户VO类:

package com.ymy.vo;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.*;

@Getter
@Setter
public class UserVo {


    @NotEmpty(message = "用户名不能为空")
    private String userName;

    @NotEmpty(message = "手机号不能为空")
    private String phone;

    @NotEmpty(message = "邮箱不可为空")
    @Email(message = "邮箱格式不正确")
    private String email;

    @Min(0)
    @Max(120)
    @NotNull(message = "年龄必填")
    private Integer age;
}

controller接口实现:

  * 添加用户
     *
     * @return
     */
    @RequestMapping(value = "addUser", method = RequestMethod.POST)
    public Result addUser(@RequestBody @Valid UserVo userVo, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return Result.faild(ConstantUtil.ResponeCode.INVALID_PARAMETER,bindingResult.getFieldError().getDefaultMessage());
        }
        log.info("添加成功。。。。。。{}", userVo);
        return Result.OK();
    }

运行addUser接口:

发现已经实现了参数校验的功能,但是代码看着还是有一点点的不爽,那是为什么呢?因为controller中还是有着一个if的判断条件,如果能把这个if判断也去掉,那整个世界都美好了。

刚刚在讲全局异常捕获的时候提到了一个异常类,:MethodArgumentNotValidException,不知道还记得吗,他就是配合我们的@Valid使用的,具体如何使用呢?

在全局异常捕获类(GlobalExceptionRespone.java)中添加对@Valid注解参数校验失败捕获:

/**
     *  校验错误拦截处理
     *
     * @param exception 错误信息集合
     * @return 错误信息
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object validationBodyException(MethodArgumentNotValidException exception){

        BindingResult bindingResult = exception.getBindingResult();
        if (bindingResult.hasErrors()) {

            return Result.faild(ConstantUtil.ResponeCode.INVALID_PARAMETER,bindingResult.getFieldError().getDefaultMessage());
        }
        return Result.faild(ConstantUtil.ResponeCode.SYS_ERROR_CODE);
    }

就是这么简单,我们现在去掉controller中的if判断:

/**
     * 添加用户
     *
     * @return
     */
    @RequestMapping(value = "addUser", method = RequestMethod.POST)
    public Result addUser(@RequestBody @Valid UserVo userVo) {
        log.info("添加成功。。。。。。{}", userVo);
        return Result.OK();
    }

 

运行结果如下:

 

完美,从一堆的if判断语句到现在的一个注解,代码的可读性以及可维护性都得到了很大的提升,现在的心情就是倍爽

  @Valid常用校验注解:

@Null:只能为空

@NotNull:不能为空

@Max(value):最大数字

@Min(value):最小数字

@Size(max,min):字符长度必须在min到max之间

@DecimalMax(value):不大于指定数字

@DecimalMin(value):不小于指定数字

@Pattern(value):正则表达式

@Digits(integer,fraction):小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction

@Past:日期类型:输入时间比当前时间早

@Future:将来日期

@Past:过去日期

@Email:验证邮箱格式

@AssertFalse:只能为false

@AssertTrue:只能为true

@NotEmpty:参数不能为空

@NotBlank:参数不能为空

                      

依赖类:

package com.ymy.vo;


import com.ymy.utils.ConstantUtil;
import com.ymy.utils.DateUtil;
import lombok.Getter;

@Getter
public class Result<T> {

    private  String  code;

    private String msg;

    private T data;

    private String  createTime = DateUtil.getdateNow();

    private  Result(String code){
        this.code = code;
    }

    private  Result(String code,T data){
        this.code = code;
        this.data = data;
    }

    private  Result(String code,String msg){
        this.code = code;
        this.msg = msg;
    }

    private  Result(String code,String msg,T data){
        this.code = code;
        this.msg = msg;
        this.data = data;
    }


    /**
     * 默认成功返回
     * @param <T>
     * @return
     */
    public static<T> Result<T> OK(){
        return new Result<T>(ConstantUtil.ResponeCode.SUCCESS_CODE);
    }

    /**
     * 返回带code的信息
     * @param code
     * @param <T>
     * @return
     */
    public static<T> Result<T> OK(String code){
        return new Result<T>(code);
    }


    /**
     * 返回只带code的信息
     * @param code
     * @param <T>
     * @return
     */
    public static<T> Result<T> faild(String code){
        return new Result<T>(code,ConstantUtil.ResponeCode.SYS_ERROR);
    }


    /**
     * 返回带code与提示消息的对象
     * @param code
     * @param msg
     * @param <T>
     * @return
     */
    public static<T> Result<T> faild(String code,String msg){
        return new Result<T>(code,msg);
    }



}
package com.ymy.utils;

/**
 * 常量字典
 */
public class ConstantUtil {

    /**
     * 返回状态码信息
     */
    public static  class  ResponeCode{
        /**
         * 正常
         */
       public static final String SUCCESS_CODE="suc-001";

        /**
         * 系统错误code
         */
        public static final String SYS_ERROR_CODE="sys_error";

        /**
         * 系统错误code
         */
        public static final String NULL_POINTER_EXCEPTION_CODE="Null_Pointer_Exception";

        /**
         * 系统错误code
         */
        public static final String NULL_POINTER_EXCEPTION="程序中出现空引用,请检查!";


        /**
         * 系统错误
         */
        public static final String SYS_ERROR="网络繁忙,请稍后再试";

        /**
         * 参数错误code
         */
        public static final String INVALID_PARAMETER="invalid_parameter";
    }


    /**
     * 日期格式
     */
    public static  class  Date{
        public static final String DATE_FORMATE="yyy-MM-dd HH:mm:ss";
    }
}

 

项目地址:https://gitee.com/yq0601/global-exceptions.git

 

               

 

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: MethodArgumentNotValidException是Spring框架中的一个异常类,表示方法参数无效。通常在使用Spring MVC进行表单验证时,如果表单提交的参数不符合要求,就会抛出该异常。开发者可以通过捕获异常并进行处理,来实现自定义的异常处理逻辑。 ### 回答2: MethodArgumentNotValidException是Spring框架中的一个异常类,用于表示请求参数校验不通过的情况。通常在使用Spring MVC进行接口开发时,我们可以通过添加注解(如@Valid)来对请求参数进行校验。如果请求参数不满足预设的校验规则,就会抛出MethodArgumentNotValidException异常MethodArgumentNotValidException包含了详细的错误信息,包括哪个参数校验失败以及具体的校验错误原因。这样的设计可以帮助开发者更快速地定位和修复参数校验的问题。 在解决这个异常时,我们可以通过以下几种方式来处理: 1. 在Controller层的方法参数上添加@ExceptionHandler注解,捕获MethodArgumentNotValidException异常,并返回自定义的错误信息给客户端。 2. 使用全局异常处理器,对MethodArgumentNotValidException进行统一处理。在异常处理器中,可以将详细错误信息封装成统一的返回格式,并设置合适的HTTP状态码。 3. 对于必填参数,可以在实体类的字段上添加@NotNull注解,保证参数不为空。对于其他自定义的校验规则,可以使用@Pattern、@Size等注解进行参数校验。 总而言之,MethodArgumentNotValidException是Spring框架用于表示请求参数校验不通过的异常类,通过合适的异常处理方式,我们可以友好地向用户返回错误信息,并帮助开发者快速定位和修复参数校验问题。 ### 回答3: MethodArgumentNotValidException是Spring框架中的一个异常类,用于表示方法参数不合法的异常。 当使用Spring框架进行参数校验时,如果方法的参数不符合校验规则,就会抛出MethodArgumentNotValidException异常。 这个异常一般出现在参数校验的阶段,比如在使用注解进行参数校验时,如果校验不通过,就会抛出此异常MethodArgumentNotValidException提供了一些有用的信息,比如异常的原因、具体的错误字段和错误信息等,方便开发人员快速定位并处理问题。 通常处理MethodArgumentNotValidException的方法是通过全局异常处理器进行统一处理,可以将异常信息进行封装,返回给客户端或记录日志。 为了避免MethodArgumentNotValidException的出现,我们可以在方法参数上使用相关的校验注解,如@Valid、@NotNull等,通过定义校验规则,保证参数的合法性。 通过合理地使用参数校验功能,可以有效地提高系统的健壮性和稳定性,减少潜在的问题发生。同时,对于接口的调用方来说,也可以减少因为参数错误而导致的问题,提升系统的易用性和用户体验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值