全局异常拦截器

怎么个全局异常呢?

先了解其注解

@RestControllerAdvice 其作用是为了拦截带有@Controller注解控制层内的异常

比如还要service层,还要数据持久层。他们属于同一层,但是控制层是调用服务层,服务层会调用数据持久层,因此,无论是服务层,还是数据持久层发生异常,捕获到了,都会在控制层,全局异常拦截器给捕获到。

throw 捕获到异常后 会返回给调用此方法的调用者返回上一层?

基础不太好突然想到为什么返回JSON给前端,

突然想到带有***@RespondBody***


所以什么是全局异常拦截器?

根据Spring提供的注解@RestControllerAdvice

1
2
3
4
5
6
7
8
@Componet
@Slf4j
@RestControllerAdvice
public class GlobalExceptionInterceptor{
//关于@ @SneakyThrows注解 相当于 我要在方法中声明throw的异常,如果我加的话就不需要啦
@SneakyThrows
@ExceptionHandler(value = MethodArgumentNotValidException.class)
}

BindingResult

是Spring Framework 中用于处理表单验证错误的一个类,与@Valid 或@Validated注解一起使用通常用于捕获Java Bean 验证中的错误

主要方法

  • hasErrors(): 检查是否存在任何验证错误。
  • getFieldErrors(): 获取与字段相关的验证错误。
  • getGlobalErrors(): 获取与整个对象相关的验证错误(例如全局约束)。

总结

BindingResult 不是单独存在的,它是与 @Valid@Validated 注解一起使用的。它用于捕获和处理验证过程中产生的错误。在控制器中,BindingResult 允许你在处理验证失败时进行自定义的错误处理,而在全局异常处理器中,它可以帮助你将验证错误格式化为适当的响应返回给客户端。这使得处理和返回验证错误变得更加灵活和一致。


Optional 是Java8引入的一个工具,用于处理可能为null的值,可以帮助避免空指针异常,提供更清晰的代码结构,并支持函数式编程风格的操作,使代码更加健壮性和可读。

1
2
3
4
5
6
7
8
Optional<String> getName(User user) {
return Optional.ofNullable(user) //如果user不为空则存储包含User信息的Optional容器
.map(User::getProfile)//获取getprofile
.map(Profile::getName)//获取名字
.orElse("Unknown");//返回user
}

//但是如果为空 存一个空的optional 并且map不执行,存储Unknown信息。返回

案例

1
2
3
4
5
6
7
8
9
10
 String result = Optional.ofNullable(fileName())//判断是否为空,空不执行map
.map(String::trim)//将字符串的空白清空
.filter(name -> name.length()>5)//名字大于5的
.orElse("null");//空的话存null,不空的话存查到的数据
System.out.println(result);

private static String fileName() {
String name = " 1234 ";
return name;
}

image-20240820155602657

如果我改为 name.length()>3的话则显示1234


回到正题

枚举总结

枚举是 Java 提供的一种特殊数据类型,用于定义一组固定的常量。它可以包含字段、方法和构造函数,并支持多种高级用法,如实现接口和定义自定义行为。枚举使代码更加可读、可维护,并提供了编译时的类型安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public enum Day {
MONDAY("星期一"), TUESDAY("星期二"), WEDNESDAY("星期三"), THURSDAY("星期四"),
FRIDAY("星期五"), SATURDAY("星期六"), SUNDAY("星期天");


private final String Date;

Day(String date) {
this.Date = date;
}

public String getDate() {
return Date;
}
}

通过Day.MONDAY.getDate()获取 星期一的信息。


首先定义了三个自定义异常类

定义了一个抽象类

AbstractException 继承运行时异常RuntimeException

ClientException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ClientException extends AbstractException {

public ClientException(IErrorCode errorCode) {
this(null, null, errorCode);
}

public ClientException(String message) {
this(message, null, BaseErrorCode.CLIENT_ERROR);
}

public ClientException(String message, IErrorCode errorCode) {
this(message, null, errorCode);
}

public ClientException(String message, Throwable throwable, IErrorCode errorCode) {
super(message, throwable, errorCode);
}

@Override
public String toString() {
return "ClientException{" +
"code='" + errorCode + "'," +
"message='" + errorMessage + "'" +
'}';
}
}

RemoteException

ServiceException


以及枚举的错误码定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    package com.gues.shortLink.admin.common.convention.errorcode;

/**
* 基础错误码定义
*/
public enum BaseErrorCode implements IErrorCode {

// ========== 一级宏观错误码 客户端错误 ==========
CLIENT_ERROR("A000001", "用户端错误"),

// ========== 二级宏观错误码 用户注册错误 ==========
USER_REGISTER_ERROR("A000100", "用户注册错误"),
USER_NAME_VERIFY_ERROR("A000110", "用户名校验失败"),
USER_NAME_EXIST_ERROR("A000111", "用户名已存在"),
USER_NAME_SENSITIVE_ERROR("A000112", "用户名包含敏感词"),
USER_NAME_SPECIAL_CHARACTER_ERROR("A000113", "用户名包含特殊字符"),
USER_NOT_EXIST("A000114","用户不存在"),
PASSWORD_VERIFY_ERROR("A000120", "密码校验失败"),
PASSWORD_SHORT_ERROR("A000121", "密码长度不够"),
PHONE_VERIFY_ERROR("A000151", "手机格式校验失败"),

// ========== 二级宏观错误码 系统请求缺少幂等Token ==========
IDEMPOTENT_TOKEN_NULL_ERROR("A000200", "幂等Token为空"),
IDEMPOTENT_TOKEN_DELETE_ERROR("A000201", "幂等Token已被使用或失效"),


// ========== 二级宏观错误码 系统请求操作频繁 ==========
FLOW_LIMIT_ERROR("A000300", "当前系统繁忙,请稍后再试"),

// ========== 一级宏观错误码 系统执行出错 ==========
SERVICE_ERROR("B000001", "系统执行出错"),
// ========== 二级宏观错误码 系统执行超时 ==========
SERVICE_TIMEOUT_ERROR("B000100", "系统执行超时"),

// ========== 一级宏观错误码 调用第三方服务出错 ==========
REMOTE_ERROR("C000001", "调用第三方服务出错");

private final String code;

private final String message;

BaseErrorCode(String code, String message) {
this.code = code;
this.message = message;
}

@Override
public String code() {
return code;
}

@Override
public String message() {
return message;
}
}

实现一个接口

IEooroCode类

1
2
3
4
public interimple IErrorCode{
String code();
String message();
}

建一个全局异常拦截器的类加注解

@RestControllerAdvice

@Compoent

@Slf4j

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.gues.shortLink.admin.common.web;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.gues.shortLink.admin.common.convention.errorcode.BaseErrorCode;
import com.gues.shortLink.admin.common.convention.execption.AbstractException;
import com.gues.shortLink.admin.common.convention.execption.ClientException;
import com.gues.shortLink.admin.common.convention.result.Result;
import com.gues.shortLink.admin.common.convention.result.Results;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Optional;

/**
* 全局异常处理器
*
*/
@Component
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 拦截参数验证异常
*/
@SneakyThrows
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors());
String exceptionStr = Optional.ofNullable(firstFieldError)
.map(FieldError::getDefaultMessage)
.orElse(StrUtil.EMPTY);
log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionStr);
return Results.failure(BaseErrorCode.CLIENT_ERROR.code(), exceptionStr);
}
/**
* 拦截应用内抛出的异常
*/
@ExceptionHandler(value = {AbstractException.class})
public Result abstractException(HttpServletRequest request, AbstractException ex) {
if (ex.getCause() != null) {
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString(), ex.getCause());
return Results.failure(ex);
}
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString());
return Results.failure(ex);
}

/**
* 拦截未捕获异常
*/
@ExceptionHandler(value = Throwable.class)
public Result defaultErrorHandler(HttpServletRequest request, Throwable throwable) {
log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable);
return Results.failure();
}
private String getUrl(HttpServletRequest request) {
if (StringUtils.isEmpty(request.getQueryString())) {
return request.getRequestURL().toString();
}
return request.getRequestURL().toString() + "?" + request.getQueryString();
}
}

建议每一行都会看懂 ,以为我以及把基础在前面已经讲了,最后还要返回类型我在后面补上。


返回异常到前台。

1
2
3
4
5
6
7
8
9
public static Result<Void> failure(AbstractException abstractException) {
String errorCode = Optional.ofNullable(abstractException.getErrorCode())
.orElse(BaseErrorCode.SERVICE_ERROR.code());
String errorMessage = Optional.ofNullable(abstractException.getErrorMessage())
.orElse(BaseErrorCode.SERVICE_ERROR.message());
return new Result<Void>()
.setCode(errorCode)
.setMessage(errorMessage);
}

Result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.gues.shortLink.admin.common.convention.result;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serial;
import java.io.Serializable;

/**
* 全局返回对象
*/
@Data
@Accessors(chain = true)
public class Result<T> implements Serializable {

@Serial
private static final long serialVersionUID = 5679018624309023727L;

/**
* 正确返回码
*/
public static final String SUCCESS_CODE = "0";

/**
* 返回码
*/
private String code;

/**
* 返回消息
*/
private String message;

/**
* 响应数据
*/
private T data;

/**
* 请求ID
*/
private String requestId;

public boolean isSuccess() {
return SUCCESS_CODE.equals(code);
}
}

必须理通!


总结

@RestControllerAdvice 拦截控制层的异常

@ResponBody 返回JSON包装

@SneakyThrows注解 相当于 我要在方法中声明throw的异常,如果我加的话就不需要啦

BindResult

1
2
BindingResult bindingResult = ex.getBindingResult();
FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors());

枚举 枚举是 Java 提供的一种特殊数据类型,用于定义一组固定的常量

Optional 是一个容器 预防空指针的最佳良器 Java 8 推出的

1
2
3
4
String result = Optional.ofNullable(findName())
.map(String::trim())
.filter(name -> name.length()>3)
.orElse("null")