Featured image of post SpringBoot项目Validation组件+全局异常处理进行各种参数验证问题

SpringBoot项目Validation组件+全局异常处理进行各种参数验证问题

SpringBoot项目Validation组件+全局异常处理进行各种参数验证问题

SpringBoot项目Validation组件+全局异常处理进行各种参数验证问题,比如,写登录模块各种条件的判断等等

SpringBoot

为什么要使用Validation组件和@Valid进行参数验证

SpringBoot项目中,会经常写接口类,比如:登录或者注册模块会有大量的校验工作。在写接口时经常要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码来做判断,如下: 2

这样的代码如果按正常代码逻辑来说,是没有什么问题的,不过按优雅来说,简直糟糕透了。不仅不优雅,而且如果存在大量的验证逻辑,这会使代码看起来乱糟糟,大大降低代码可读性,那么有没有更好的方法能够简化这个过程呢?

答案当然是有,推荐的是使用 @Valid 注解来帮助我们简化验证逻辑。

@Valid的相关注解

下面是 @Valid 相关的注解,在实体类中不同的属性上添加不同的注解,就能实现不同数据的效验功能。 3

使用@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不太友好,因为没有异常信息。