全局统一异常处理, 断言工具-JSR303

发布时间:2023年12月20日

一.异常处理

1..定义异常

在开发中,为了保证业务的正确性我们会对业务参数进行校验,不满足要求的参数采用throw new XxxException 来抛出异常,阻止程序继续执行,比如:用户名不可为空,密码错误等。这类异常是需要展示给用户看的,还有一类是程序抛出来的一些未知的异常,如:SQL异常,空指针异常等等,这类异常不能直接抛给用户,而是应该统一捕获后,封装成统一的错误提示返回给用户,如“系统内部异常啦”

为了把我们手动抛出的异常和系统内部出现的异常区分开,我们需要抛出自定义的异常。通过继承RuntimeException自定义异常。

//全局异常
@Data
public class GlobleException extends RuntimeException{

    /**--------------------------------------------------------
     传一个错误信息给异常对象
     --------------------------------------------------------**/
    public GlobleException(String message){
        super(message);
    }
}
1.2.使用异常

在业务中使用自定义的异常来throw异常

1.3.统一捕获异常(异常处理器)

统一捕获异常,使用AOP的思想,解决在controller中大量try-catch重复代码。

通过两个注解来实现异常统一处理:

  • @RestControllerAdvice : 放在类上,是@ControllerAdvice和@ResponseBody注解的组合,它的作用是将controller中抛出的异常进行处理,而处理结果都以JSON格式返回给客户端。

该注解除了搭配@ExceptionHandler注解处理全局异常,还可以搭配其他注解做其他工作!!!

  • @ExceptionHandler(异常类.class) :贴在方法上,可捕获指定类型的异常

    /**--------------------------------------------------------
    全局异常处理
     @RestControllerAdvice :贴在类上,这个类就可以在controller的方法执行前,或者执行后做一些事情
     --------------------------------------------------------**/
    //@ControllerAdvice
    @RestControllerAdvice
    public class GlobleExceptionHandler {
    
        //拦截异常 : 这个注解就可以拦截器 GlobleException 异常
        @ExceptionHandler(GlobleException.class)
        public JSONResult globleException(GlobleException e){
            e.printStackTrace();
            return JSONResult.error(e.getMessage());
        }
    
        //拦截器其他异常
        @ExceptionHandler(Exception.class)
        public JSONResult exception(Exception e){
            e.printStackTrace();
            return JSONResult.error("系统异常,正在殴打程序员...");
        }
    
    }

    该类中处理异常的方法后面会逐渐增加,用于处理不同的业务场景

其中 JSONResult为后端返回前端的格式 error为内部的方法

还可以将返回的消息用枚举类进行统一管理:

定义不同的自定义异常, 所有的异常消息信息都定义在枚举类中, 降低耦合, 修改信息只需要修改枚举类中的信息即可

@AllArgsConstructor
@Getter
public enum GlobalInstances {

    SYSTEM_EXCEPTION("9999","系统异常,正在殴打程序员..."),
    ACCOUNT_PASSWORD_EXCEPTION("1001","账号或密码错误");

    private String code;
    private String message;

/*    GlobalInstances(String code, String message) {
        this.code = code;
        this.message = message;
    }*/
}

那么异常处理器中的方法可变为: (@ExceptionHandler内部可以捕获多个异常)

    @ExceptionHandler({RuntimeException.class, ArrayIndexOutOfBoundsException.class})
    public JSONResult globalExceptionHandler(RuntimeException e){
        e.printStackTrace();

        return JSONResult.error(
                GlobalInstances.SYSTEM_EXCEPTION.getMessage(),
                GlobalInstances.SYSTEM_EXCEPTION.getCode()
        );
    }

二.断言工具

大量的if(条件)throw new Exception("异常") 代码太过冗余,通过断言工具进行抽取,仿照Spring的Assert。

2.1.定义工具

整合到工具包AssertUtil中:

手机号校验断言, 格式不对, 抛出异常:

//手机的正则表达式    
private static final Pattern CHINA_PATTERN_PHONE = Pattern.compile("^((13[0-9])|(14[0,1,4-9])|(15[0-3,5-9])|(16[2,5,6,7])|(17[0-8])|(18[0-9])|(19[0-3,5-9]))\\d{8}$");
 
/**--------------------------------------------------------
    手机号断言
    --------------------------------------------------------**/
    public static void isPhone(String phone,String message){
        Matcher m = CHINA_PATTERN_PHONE.matcher(phone); //将手机号放入匹配器对象
        if(!m.matches()){ //进行正则匹配,返回boolean值
            throw new RuntimeException(message);
        }
    }

不为空断言, 为空抛异常:

    /**--------------------------------------------------------
    断言 不为空,如果为空,抛异常
    --------------------------------------------------------**/
    public static void isNotEmpty(String text, String message) {
        if (text == null || text.trim().length() == 0) {
            throw new RuntimeException(message);
        }
    }


    /**--------------------------------------------------------
    断言对象为空
    --------------------------------------------------------**/
    public static void isNull(Object obj , String message){
        if(obj != null){
            throw new RuntimeException(message);
        }
    }
    public static void isNotNull(Object obj , String message){
        if(obj == null){
            throw new RuntimeException(message);
        }
    }

判断false和true断言:

    /**--------------------------------------------------------
     断言false,如果为true,我报错
     --------------------------------------------------------**/
    public static void isFalse(boolean isFalse , String message){
        if(isFalse){
            throw new RuntimeException(message);
        }
    }
    public static void isTrue(boolean isTrue , String message){
        if(!isTrue){
            throw new RuntimeException(message);
        }
    }

比较字符串断言:

    /**--------------------------------------------------------
     断言两个字符串一致
     --------------------------------------------------------**/
    public static void isEquals(String s1,String s2 , String message){
        isNotEmpty(s1, "不可为空");
        isNotEmpty(s2, "不可为空");
        if(!s1.equals(s2)){
            throw new RuntimeException(message);
        }
    }
    public static void isEqualsTrim(String s1,String s2 , String message){
        isNotEmpty(s1, "不可为空");
        isNotEmpty(s2, "不可为空");
        if(!s1.trim().equals(s2.trim())){
            throw new RuntimeException(message);
        }
    }

    public static void isEqualsIgnoreCase(String s1,String s2 , String message){
        isNotEmpty(s1, "不可为空");
        isNotEmpty(s2, "不可为空");
        if(!s1.trim().equalsIgnoreCase(s2.trim())){
            throw new RuntimeException(message);
        }
    }
2.2.使用断言工具
AssertUtil.isNotEmpty(username,"用户名不能为空")

三.参数校验Validation --> (JSR303)

参数校验是我们程序开发中必不可少的过程。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。

那么如何优雅的对参数进行校验呢?JSR303就是为了解决这个问题出现的,本篇文章主要是介绍 JSR303,Hibernate Validator 等校验工具的使用,以及自定义校验注解的使用。

3.1.相关注解

Bean Validation 中内置的 constraint (限制,约束)

Constraint详细信息
@Null被注释的元素必须为 null
@NotNull被注释的元素必须不为 null
@AssertTrue被注释的元素必须为 true
@AssertFalse被注释的元素必须为 false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

Constraint详细信息
@Email被注释的元素必须是电子邮箱地址
@Length被注释的字符串的大小必须在指定的范围内
@NotEmpty被注释的字符串的必须非空
@Range被注释的元素必须在合适的范围内
3.2.对象校验
1.在参数实体类的字段上注解

在实体类上, 用约束注解标注需要校验的字段

@NotEmpty(message = "手机不可为空")
private String phone;
2.在参数实体类前注解

开启校验: 使用@Valid开启校验功能

校验功能需要开启, 因为防止不不要的校验

只在需要校验的方法形参前 添加该注解

public JSONResult register(@RequestBody @Valid RegisterParamDto dto)

@Valid 或者 @Validated都可以标识该类需要进行校验,在类上也可以加该注解。

3.3.参数校验

单个参数也可以直接在参数前加上限制注解:

@Valid
@RestController
public class UserController{
 ? ?
 ? ?@RequestMapping("/user/add")
 ? ?public JSONResult add(@NotEmpty(message = "不可为空") String username, String password){
 ? ? ?  ...省略...
 ?  }
}
文章来源:https://blog.csdn.net/2201_75971147/article/details/135106697
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。