天天看點

【商城秒殺項目】-- 使用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:

【商城秒殺項目】-- 使用JSR303進行參數校驗、全局異常處理

Hibernate Validator 附加的constraint:

【商城秒殺項目】-- 使用JSR303進行參數校驗、全局異常處理

詳細介紹:https://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html

JSR-303的使用

本項目在登入的時候使用了JSR-303進行參數校驗,用法如下:先在實體類的屬性上打上@NotNull、@Length(min=32)等注解,可避免重複的校驗代碼以及代碼備援;然後在需要驗證的參數前面打上@Valid注解,那麼此注解就會自動對該實體類進行參數校驗,具體校驗規則在該實體類内部實作

【商城秒殺項目】-- 使用JSR303進行參數校驗、全局異常處理
【商城秒殺項目】-- 使用JSR303進行參數校驗、全局異常處理

自定義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;
	}
}
           

全局異常處理使用場景:先檢查異常類型,若業務中發現異常直接抛出我們自定義的異常即可

例如:

【商城秒殺項目】-- 使用JSR303進行參數校驗、全局異常處理

JSR-303結合全局異常使用的校驗效果:

【商城秒殺項目】-- 使用JSR303進行參數校驗、全局異常處理

繼續閱讀