前言
自從我接觸了spring-boot框架,對於基於註解的開發方式越來越中意,無疑這是一種提升開發效率和簡化配置的開發方式。開發中對於方法入參數據校驗是必須的也是嚴謹的,一方面是爲了提升性能(對於不合法數據,或格式不正確的數據,不做處理,直接返回錯誤信息)。另一方面也是業務需求,對某些字段有着check。當我們使用spring框架來開發項目,其實spring-validation這個模塊也已經提供了一些常用校驗註解。例如@Size、@NotNull、@Email之類的格式或者非空校驗。
分析
既然spring-validation這個模塊已經提供一部分常用註解,但是在實際開發中的需求,並不能完全解決。
所以我們可以參照既有的註解的實現方式,照葫蘆畫瓢,也用註解來完成數據字段的校驗。
源碼分析
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotNull.List.class)
@Documented
@Constraint(
validatedBy = {}//1.注入校驗類
)
public @interface NotNull {
//2.定義message
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default {};//支持分組校驗
Class<? extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
NotNull[] value();
}
}
這個是validation包定義的@NotNull的註解接口的定義
此處需要注意我標註的兩處
1.Constraint中注入校驗類
2.校驗用的message
@Documented
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {
Class<? extends ConstraintValidator<?, ?>>[] validatedBy();//校驗類注入
}
此處是@Constraint註解接口的定義
validatedBy 方法中可以傳入ConstraintValidator的校驗類
public interface ConstraintValidator<A extends Annotation, T> {
default void initialize(A constraintAnnotation) {
}
boolean isValid(T var1, ConstraintValidatorContext var2);//校驗實現方法
}
ConstraintValidator校驗類接口,具體實現的校驗類需要實現這個ConstraintValidator接口,實現isValid這個抽象方法。
分析總結:實裝自定義註解校驗,需要定義一個註解接口用於標識字段,需要一個ConstraintValidator的實現類,用於判斷字段是否滿足校驗規則
實現
具體實現
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = IpCheckValidator.class)//校驗實現類綁定
public @interface IpCheck {
String message() default "{} ip address is not legal";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
IpCheck[] value();
}
}
我這裏是自定實現的ip地址check的註解
public class IpCheckValidator implements ConstraintValidator<IpCheck,String> {
//實現方法
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
String ipArry[] = value.split("\\.");
if(ipArry.length==4){
int ipArryInt[] = new int[4];
for (int i=0;i<4;i++){
try {
ipArryInt[i]=Integer.parseInt(ipArry[i]);
} catch (Exception e){
return false;
}
}
if(ipArryInt[0]>=1&&ipArryInt[0]<=255)
for (int j=1;j<4;j++){
if(ipArryInt[j]<0||ipArryInt[j]>255){
return false;
}
}
return true;
}
return false;
}
}
使用方式
@IpCheck
private String ipAddress;
最後的校驗信息存在BindingResult,通過hasErrors方法就可以判斷校驗是否通過
@RequestMapping(value = "/logon",method =RequestMethod.POST)
public BaseResponse userRegister(@RequestBody @Valid RegisterUserInfo userInfo,
BindingResult result){
//判斷校驗是否有誤
if(!result.hasErrors()){
//執行註冊
service.register(userInfo);
}else{
//拋出異常-提交參數格式不對
throw new APIException(HttpServletResponse.SC_BAD_REQUEST,
"request parameters formatter is wrong ");
}
return NORMAL_RESPONSE;
}