前言
自从我接触了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;
}