SpringBoot项目Validation组件+全局异常处理进行各种参数验证问题,比如,写登录模块各种条件的判断等等
SpringBoot
为什么要使用Validation组件和@Valid进行参数验证
SpringBoot项目中,会经常写接口类,比如:登录或者注册模块会有大量的校验工作。在写接口时经常要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码来做判断,如下:
这样的代码如果按正常代码逻辑来说,是没有什么问题的,不过按优雅来说,简直糟糕透了。不仅不优雅,而且如果存在大量的验证逻辑,这会使代码看起来乱糟糟,大大降低代码可读性,那么有没有更好的方法能够简化这个过程呢?
答案当然是有,推荐的是使用 @Valid 注解来帮助我们简化验证逻辑。
@Valid的相关注解
下面是 @Valid 相关的注解,在实体类中不同的属性上添加不同的注解,就能实现不同数据的效验功能。
使用@Valid进行参数校验步骤
整个过程如下图所示,用户访问接口,然后进行参数效验,因为 @Valid 不支持平面的参数效验(直接写在参数中字段的效验)所以基于 GET 请求的参数还是按照原先方式进行效验,而 POST 则可以以实体对象为参数,可以使用 @Valid 方式进行效验。如果效验通过,则进入业务逻辑,否则抛出异常,交由全局异常处理器进行处理。
SpringBoot实战
@Valid
实体类
package com.cheney.seckill.vo;
import com.cheney.seckill.validation.IsMobile;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginVo {
@IsMobile(require = true)
private String mobile;
@NotNull
@Length(min = 32,max = 32)
private String password;
}
Controller
@Controller
@RequestMapping("/login")
public class LoginController {
@Autowired
private IUserService iUserService;
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/doLogin")
@ResponseBody
public RespBean doLogin(@Valid LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) throws InterruptedException {
RespBean respBean = iUserService.doLogin(loginVo,request,response);
Thread.sleep(2000);
return respBean;
}
}
注意: 形参上必须要加@Valid注解
进行测试
自定义校验
引入validation组件依赖
<!-- validation组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
自定义校验注解
/**
* 验证手机号
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式错误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义校验注解的校验器
/**
* 自定义IsMobile注解的校验器
*/
public class IsMobileValidator implements ConstraintValidator<IsMobile,String> {
private boolean require;
@Override
public void initialize(IsMobile constraintAnnotation) {
require = constraintAnnotation.require();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
//判断require是否符合手机格式
if (StringUtils.isEmpty(s)){
if (require)
//手机号为空。要求必填
return false;
else
//手机号为空,但是我也不要求必填
return true;
}else {
//1手机号不为空
return ValidatorUtil.isMobile(s);
}
}
}
自定义校验规则
/**
* 校验工具类
*/
public class ValidatorUtil {
private static final Pattern mobile_pattern = Pattern.compile("[1]([3-9])[0-9]{9}$");
public static boolean isMobile(String mobile){
if (StringUtils.isEmpty(mobile)) {
return false;
}
Matcher matcher = mobile_pattern.matcher(mobile);
return matcher.matches();
}
}
异常处理
-
我们知道,系统中异常包括:编译时异常和运行时异常 RuntimeException ,前者通过捕获异常从而获 取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
-
在开发中,不管是 dao层、service层还是controller层,都有可能抛出异常,在Springmvc中,能将所有类型的异常处理,从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。
SpringBoot全局异常处理方式主要两种:
- 使用 @ControllerAdvice 和 @ExceptionHandler 注解。
- 使用 ErrorController类 来实现
区别:
-
@ControllerAdvice 方式只能处理控制器抛出的异常。此时请求已经进入控制器中。
-
ErrorController类 方式可以处理所有的异常,包括未进入控制器的错误,比如404,401等错误
-
如果应用中两者共同存在,则 @ControllerAdvice 方式处理控制器抛出的异常,ErrorController类 方式处理未进入控制器的异常。
-
@ControllerAdvice 方式可以定义多个拦截方法,拦截不同的异常类,并且可以获取抛出的异常 信息,自由度更大。
GlobalException
/**
* 自定义全局异常
* 我们抛出的所有异常都是该异常
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GlobalException extends RuntimeException{
private RespBeanEnum respBeanEnum;
}
GlobalExceptionHandler
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public RespBean handler(Exception e) throws Exception {
//将异常转换为RespBean对象
//异常有很多种。可能是自己的异常,可能是系统的等等
if (e instanceof GlobalException){
GlobalException ge = (GlobalException) e;
return RespBean.error(ge.getRespBeanEnum());
}else if (e instanceof BindException){
BindException be = (BindException) e;
String msg = be.getBindingResult().getAllErrors().get(0).getDefaultMessage();
return new RespBean(500502L,msg,null);
}else {
//throw e;
return RespBean.error(RespBeanEnum.ERROR);
}
}
}
这样就可以省去各种健壮性判断,并且通过全局异常来前端异常提示,而不是一个异常页面。但是对开发中调试bug不太友好,因为没有异常信息。