Java Spring Boot 自定义异常与全局异常处理【Java进阶】

我们在对比 过滤器与拦截器 一文中,知道请求过来,各种拦截处理的顺序:

1.过滤器
2.拦截器
3.controllerAdvice
4.AOP
5.controller
6.AOP
7.controllerAdvice
8.拦截器
9.过滤器
今天我们学习的 自定义异常与异常处理 这块内容,恰好就是 ControllerAdvice/RestControllerAdvice 部分了。

在日常开发中,对于异常的处理我们要么是主要 try…catch… 或者是 throw new xxxException(msg) 几种方式,比如我们主要捕捉异常可以捕捉文件读写时可能存在的 IOException 等,或者是 throw Exception(“这是某种异常”),但在 Spring Boot web 开发中,我们还是希望能和业务尽量结合起来,一起优雅通过统一的响应结构输出。

在捕捉异常的时候,我们往往希望 最好是具体的某种异常,再是一般的某种异常,所以这里自然就会涉及到 异常处理的顺序问题,后面我们在应用中也会提到。

接下来的学习中,主要分通用异常处理、自定义异常处理,其中内容含统一响应部分,可看上篇文档,传送门:Java Spring Boot 规范统一响应体结构。
通用异常处理
package com.example.springbootexceptiondemo.params;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**

  • @ExceptionHandler(value = MyException.class) – 注解类型

  • @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) – 错误码

  • @ResponseBody – 返回json

  • @ControllerAdvice – 顾名思义,这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:

  • 1.全局异常处理

  • 2.全局数据绑定

  • 3.全局数据预处理
    */
    @Slf4j
    @ControllerAdvice // @RestControllerAdvice = @ControllerAdvice + @ResponseBody
    @ResponseBody
    public class GlobalExceptionAdvice {

    /**

    • 通用异常
      */
      @ExceptionHandler(value = Exception.class)
      public RespInfo exception(Exception ex) {
      log.error("服务器异常: ", ex);
      return RespInfo.fail(RespCodeEnums.INNER_SEVER_ERROR.getCode(), ex.getMessage());
      }
      }
      如果我们要用到异常处理拦截,在 Spring Boot 中,只需要加上 @ControllerAdvice 注解,或者 @RestControllerAdvice 注解,二者的区别就是是否通过 Json 格式返回,或者说 @RestControllerAdvice = @ControllerAdvice + @ResponseBody ,然后在我们处理的方法中,指定通过哪种 异常类 来匹配,即 @ExceptionHandler注解,通过 value 的值指定匹配的异常类,这里我们通过 Exception类,大部分的异常类通过继承自该类。

如上面代码,一个通用的异常类就实现了。

自定义异常处理
自定义异常,首先要明确,我们这个异常的功能,这里为了演示方便,只是简单继承自 Exception类,实际用的时候,请结合自己的项目。

定义自己的异常类
package com.example.springbootexceptiondemo.exception;

public class MyException extends RuntimeException{
// 使用时传入错误信息
public MyException(String msg) {
super(msg);
}
}
实际使用时,我们只需要通过传入 错误msg 来构造实例。

在异常处理中添加
package com.example.springbootexceptiondemo.params;

import com.example.springbootexceptiondemo.exception.MyException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class MyExceptionAdvice {

/**
 * 自定义异常
 * @param ex
 * @return
 */
@ExceptionHandler(value = MyException.class)
public RespInfo bizExceptionHandler(MyException ex) {
    log.error("自定义业务异常: ", ex);
    return RespInfo.fail(RespCodeEnums.BIZ_EXCEPTION.getCode(), ex.getMessage());
}

}
注意我们此处用的注解是 @RestControllerAdvice,我们在 @ExceptionHandler 指定相应的异常类是自定义的异常类,如果应用中哪里触发了该异常,应该就要匹配到这个异常处理的。

当然很多情况下,Java 自带的异常类已经可以满足一定的需求,比如我们有这样的业务场景,对于上传的请求参数,如果在校验validate参数时发生异常,在异常处理中,我们就可以捕捉到,进而返回响应,如下。

Java自带的异常类
package com.example.springbootexceptiondemo.params;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Objects;

@Slf4j
@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE - 2) // 异常处理器顺序,越小越优先,-2保证比全局更优先
public class ArgumentsValidateAdvice {

@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public RespInfo methodArgsNotValidExceptionHandler(MethodArgumentNotValidException ex) {
    log.error("参数异常,msg ->", ex);
    return RespInfo.fail(RespCodeEnums.PARAMS_NOT_VALID_ERROR.getCode(), Objects.requireNonNull(ex.getBindingResult().getFieldError().getDefaultMessage()));
}

}
这里其实就是用到参数校验不合规异常,相应的我们的 User bean 如下:
package com.example.springbootexceptiondemo.model;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@NotBlank(message = “密码不能为空”)
@Size(min = 6, max = 20, message = “密码长度在 min ~ max 之间”)
private String password;

@NotNull(message = "id不能为空")
@NotBlank(message = "id不能为空")
private int id;

@NotBlank(message = "姓名不能为空")
@Size(min = 2, max = 8, message = "username 长度在 min ~ max 之间")
private String username;

private int age;

}
测试异常处理
结合上面的异常处理,我们编写个 controller,一是测试通用异常,二是测试自定义的异常。
package com.example.springbootexceptiondemo.controller;

import com.example.springbootexceptiondemo.exception.MyException;
import com.example.springbootexceptiondemo.model.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(“/user”)
public class UserLoginController {

@GetMapping("/test")
public User testLogin() throws MyException {
    throw new MyException("测试自定义异常"); // 主动抛出异常
}

@PostMapping("/add")
public User addUser(@RequestBody @Validated User user) {
    return user;
}

@GetMapping("/haha")
public Object haha() {
    int i = 1 / 0;
    return "haha";
}

}
测试情况:

通用异常处理
在这里插入图片描述
自定义异常处理
在这里插入图片描述
Java自带异常类-参数校验不合规

在这里插入图片描述
注:在多个异常处理中,我们可以通过 @Order注解 来限定异常的处理优先级:

Ordered.HIGHEST_PRECEDENCE,级别最高,最优先处理,实际就是 int.MIN 的值
Ordered.LOWEST_PRECEDENCE,级别最低,最后处理,实际就是 int.MAX 的值
@Slf4j
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ArgumentsValidateAdvice {

@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public RespInfo methodArgsNotValidExceptionHandler(MethodArgumentNotValidException ex) {
    log.error("参数异常,msg ->", ex);
    return RespInfo.fail(RespCodeEnums.PARAMS_NOT_VALID_ERROR.getCode(), Objects.requireNonNull(ex.getBindingResult().getFieldError().getDefaultMessage()));
}

}
我们在用的时候,要比 Exception类 更优先,只需要调整这个 value 即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值