在开发中,为了保证业务的正确性我们会对业务参数进行校验,不满足要求的参数采用throw new XxxException 来抛出异常,阻止程序继续执行,比如:用户名不可为空,密码错误等。这类异常是需要展示给用户看的,还有一类是程序抛出来的一些未知的异常,如:SQL异常,空指针异常等等,这类异常不能直接抛给用户,而是应该统一捕获后,封装成统一的错误提示返回给用户,如“系统内部异常啦”。
为了把我们手动抛出的异常和系统内部出现的异常区分开,我们需要抛出自定义的异常。通过继承RuntimeException自定义异常。
//全局异常
@Data
public class GlobleException extends RuntimeException{
/**--------------------------------------------------------
传一个错误信息给异常对象
--------------------------------------------------------**/
public GlobleException(String message){
super(message);
}
}
在业务中使用自定义的异常来throw异常
统一捕获异常,使用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。
整合到工具包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);
}
}
AssertUtil.isNotEmpty(username,"用户名不能为空")
参数校验是我们程序开发中必不可少的过程。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。
那么如何优雅的对参数进行校验呢?JSR303就是为了解决这个问题出现的,本篇文章主要是介绍 JSR303,Hibernate Validator 等校验工具的使用,以及自定义校验注解的使用。
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 | 被注释的元素必须在合适的范围内 |
在实体类上, 用约束注解标注需要校验的字段
@NotEmpty(message = "手机不可为空")
private String phone;
开启校验: 使用@Valid开启校验功能
校验功能需要开启, 因为防止不不要的校验
只在需要校验的方法形参前 添加该注解
public JSONResult register(@RequestBody @Valid RegisterParamDto dto)
@Valid 或者 @Validated都可以标识该类需要进行校验,在类上也可以加该注解。
单个参数也可以直接在参数前加上限制注解:
@Valid
@RestController
public class UserController{
? ?
? ?@RequestMapping("/user/add")
? ?public JSONResult add(@NotEmpty(message = "不可为空") String username, String password){
? ? ? ...省略...
? }
}