【商城秒杀项目】-- 使用JSR303进行参数校验、全局异常处理

什么是JSR-303

JSR是Java Specification Requests的缩写,意思是Java规范提案,是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务,JSR已成为Java界的一个重要标准

JSR-303是JAVA EE 6中的一项子规范,叫做Bean Validation,Hibernate Validator是Bean Validation的参考实现;Hibernate Validator提供了JSR-303规范中所有内置constraint的实现,除此之外还有一些附加的constraint。说白了,JSR-303就是后端进行数据校验的一种方式,使得验证逻辑从业务代码中脱离出来

Bean Validation 中内置的 constraint:

Hibernate Validator 附加的constraint:

详细介绍:https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html

JSR-303的使用

本项目在登录的时候使用了JSR-303进行参数校验,用法如下:先在实体类的属性上打上@NotNull、@Length(min=32)等注解,可避免重复的校验代码以及代码冗余;然后在需要验证的参数前面打上@Valid注解,那么此注解就会自动对该实体类进行参数校验,具体校验规则在该实体类内部实现

自定义JSR-303注解实现手机号码的校验

新建一个注解类IsMobile,并且引入相应的规则:

package com.javaxl.miaosha_05.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 自定义注解类:message() + groups() + payload()是必须的
 */
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@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里面需要一个@Constraint(validatedBy = { IsMobileValidator.class }),即IsMobileValidator,用来继承

新建一个IsMobileValidator类,需要实现ConstraintValidator校验器:

package com.javaxl.miaosha_05.validator;

import com.javaxl.miaosha_05.util.ValidatorUtil;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 注解校验器类:继承ConstraintValidator类<注解类,注解参数类型>,
 * 实现两个方法(initialize:初始化操作、isValid:逻辑处理)
 * IsMobile:自定义的注解
 * String:注解参数类型
 */
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
    //默认值为false,用于接收注解上自定义的required属性
    private boolean required = false;
    
    //1、初始化方法:通过该方法可以拿到我们的注解
    public void initialize(IsMobile constraintAnnotation) {
        required = constraintAnnotation.required();
    }
    
    //2、逻辑处理
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(required) {
            return ValidatorUtil.isMobile(value);
        }
        else {
            if(StringUtils.isEmpty(value)) {
                return true;
            }
            else {
                return ValidatorUtil.isMobile(value);
            }
        }
    }
}

手机号码验证逻辑工具类代码(ValidatorUtil.java):

package com.javaxl.miaosha_05.util;

import org.apache.commons.lang3.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ValidatorUtil {

    private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");

    public static boolean isMobile(String src) {
        if (StringUtils.isEmpty(src)) {
            return false;
        }
        Matcher m = mobile_pattern.matcher(src);
        return m.matches();
    }
}

全局异常处理

当使用了JSR-303校验器后,校验不通过时会产生一个BindException(org.springframework.validation.BindException)和一大串错误信息(其中就包括校验的处理信息);若要对异常进行处理,我们可以定义一个处理全局异常的拦截器

好处:可以实现对项目中所有产生的异常进行拦截,在同一个类中实现统一处理,避免异常漏处理的情况

新建GlobalExceptionHandler类:

package com.javaxl.miaosha_05.exception;

import com.javaxl.miaosha_05.result.CodeMsg;
import com.javaxl.miaosha_05.result.Result;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
    //拦截所有的异常
    @ExceptionHandler(value = Exception.class)
    public Result<String> exceptionHandler(Exception e) {
        e.printStackTrace();
        if (e instanceof GlobalException) {
            GlobalException ex = (GlobalException) e;
            return Result.error(ex.getCm());
        } else if (e instanceof BindException) {//是绑定异常的情况
            //强转
            BindException ex=(BindException) e;
            //获取错误信息
            List<ObjectError> errors = ex.getAllErrors();
            ObjectError error = errors.get(0);
            String msg = error.getDefaultMessage();
            return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
        } else {//不是绑定异常的情况
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }
}

注:

  • @ControllerAdvice是一个@Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping的方法,会对所有@RequestMapping方法进行检查、拦截,并进行异常处理。
  • @ExceptionHandler用于标注要被拦截的异常,value=Exception.class代表拦截所有的异常
  • @ResponseBody是为了方便输出,使得这个GlobalExceptionHandler类里面的方法跟我们Controller类一样是输出的信息,返回值Result类型可以携带信息,当参数校验不通过的时候,输出也是Result(CodeMsg),传给前端用于前端显示获取处理

新建一个全局异常类GlobalException,出现异常就可以直接抛这个异常;GlobalException继承Runtime类,重写构造函数,传入CodeMsg:

package com.javaxl.miaosha_05.exception;

import com.javaxl.miaosha_05.result.CodeMsg;

/**
 * 自定义异常类继承RuntimeException(运行时异常)
 */
public class GlobalException extends RuntimeException{

	private static final long serialVersionUID = 1L;
	
	private CodeMsg cm;
	
	public GlobalException(CodeMsg cm) {
		super(cm.toString());
		this.cm = cm;
	}

	public CodeMsg getCm() {
		return cm;
	}
}

全局异常处理使用场景:先检查异常类型,若业务中发现异常直接抛出我们自定义的异常即可

例如:

JSR-303结合全局异常使用的校验效果:

发布了133 篇原创文章 · 获赞 94 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_42687829/article/details/104442722