spring 自定义注解

翻看公司代码,看到了自定义的注解,查了查,再次记录一下,还是太菜
下面是我的实现

1. 自定义注解

package com.test;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;

/**
 * Documented 注解表明这个注解应该被 javadoc工具记录
 */
@Documented
/**
 *注解会被保留到那个阶段 有三个取值
 * SOURCE    只在源代码级别保留,编译时就会被忽略
 * CLASS     编译时被保留,在class文件中存在,但JVM将会忽略
 * RUNTIME   运行时被JVM或其他使用反射机制的代码所读取和使用
 */
@Retention(RetentionPolicy.RUNTIME)
/**
 * 说明了Annotation所修饰的对象范围
 */
@Target({FIELD, ANNOTATION_TYPE, PARAMETER})
/**
 * 表示处理的这个注解的类是哪一个
 * 我这里的是 myValidateINtercepter处理
 */
@Constraint(validatedBy = myValidateINtercepter.class)
@interface Enums{
    String enumList() default ""; //1
    String message() default "值不在字典内"; //2
    String field() default ""; //3

    /**
     * 下面的这俩是必须的。我第一次没加 payload ,就报错了 ,报错为 Enums contains Constraint annotation, but does not contain a payload parameter.
     */
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}
  • 补充说明
  • groups,payload 是必须添加的,要不然就会报错,我也不知为啥就报错。反正必须要添加,下面贴出我的报错信息

Spring MVC JSR303校验_java

上面代码里面,注解里面的 1,2,3 字段是根据自己的业务来的 望知晓

2. 自定义校验类

实现ConstraintValidator接口,一个参数为标注的枚举类型,第二个参数为需要校验的类型

package com.test;

import com.test.Enums;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;

/**
 * @description:
 * @author: liuchen
 * @date: 2020/7/29
 **/
public class myValidateINtercepter implements ConstraintValidator<Enums,String> {
    private String enumList;
    private String field;
    private Enums constraintAnnotation;

    /**
     * 做初始化工作
     * @param constraintAnnotation   自己定义的注解
     */
    @Override
    public void initialize(Enums constraintAnnotation) {
        this.enumList = constraintAnnotation.enumList();
        this.field = constraintAnnotation.field();
        this.constraintAnnotation = constraintAnnotation;
    }

    /**
     * 做校验
     * @param s                             待校验的值
     * @param constraintValidatorContext    上下文
     * @return
     */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        //检查 代校验的值合法否?
        if (Arrays.asList(enumList.split(",")).contains(s)) {
            //合法,通过
            return true;
        }else{
            /**
             * 禁用默认的消息模板
             */
            constraintValidatorContext.disableDefaultConstraintViolation();
            /**
             * 设置自己的消息模板
             */
            constraintValidatorContext.buildConstraintViolationWithTemplate(String.format("%s 当前值不在字段范围内,字典范围为[%s]",s,enumList))
                    .addConstraintViolation();
            //不合法,不通过
            return false;
        }
    }
}

关于constraintValidatorContext 的解释

  • 这种东西debug一下,看的清清楚楚。下面贴出我的debug的结果

Spring MVC JSR303校验_java_02

3. 使用示例

3.1 实体类

/**
 * 实体类
 */
@Data
class Student{
    //原生注解
    @Min(value = 0,message = "id不能小于0")
    private Integer id;
    @NotBlank(message = "不能为空字符串")
    private String name;
    private Double age;
    /*1*/@Valid
    private List<Address> list;
    @Valid
    private Address address;
    /*3*/
    public interface AGroup{
    }
    public interface BGroup{
    }
}
@Data
class Address{
    @NotBlank()
    /*2*/@Enums(enumList = "1,2",groups = Student.AGroup.class)
    private  String province;
    @Enums(enumList = "1,2,3",groups = Student.BGroup.class)
    private  String city;
}

关于Student的解释说明

  • 注释1 @Valid:表示被标注的属性也是需要校验的。
  • 说一下 @Valid和@Validated的区别
  • @Validated不能作用于字段,这个点开源码看@Target就可以看出来.
  • @Validated里面支持分组校验。@Valid不支持
  • 关于分组校验,请看 3.2 controller 注释1 和 这个例子中 注释2 中的groups 属性,在controller里面指定分组,校验规则就会按照对应的组来,如果不指定,就会校验那些没有指明组的属性
  • 注释3 表示不同的组,这其实就是一个接口,里面啥都不用写。

3.2 controller

package com.test;

import com.alibaba.fastjson.JSONObject;
import com.common.model.ApiResult;
import com.common.model.ResultCode;
import lombok.Data;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import javax.validation.Constraint;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;

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

/**
 * @description:
 * @author: liuchen
 * @date: 2020/7/29
 **/
@RestController
@RequestMapping("myTest")
public class MyTestcontroller {
        @PostMapping("/test")
    public ApiResult test(@RequestBody /*1*/@Validated(value = Student.BGroup.class) Student student ,/*2*/BindingResult result){
         /*3*/ //方式一  
        if(result.hasErrors()){
            //在绑定参数的时候出错啦
            StringBuffer returnResult = new StringBuffer();
            /*5*/List<ObjectError> allErrors = result.getAllErrors();
            allErrors.stream().forEach(error->returnResult.append(error.getDefaultMessage()));
            return new ApiResult(200,returnResult.toString());
        }
        System.out.println(JSONObject.toJSONString(student));
        return ApiResult.success();
    }
    /*4*/
    //方式二  定义一个统一的异常处理, 可以采用 @ControllerAdvice 的那种全局统一处理
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ApiResult handleException(MethodArgumentNotValidException bindResult){
        StringBuffer stringBuffer = new StringBuffer();
        List<ObjectError> allErrors = bindResult.getBindingResult().getAllErrors();
        allErrors.stream().forEach(error -> {System.out.println(String.format("code:%s,msg:%s",error.getCode(),error.getDefaultMessage()));stringBuffer.append(error.getDefaultMessage());});
        return new ApiResult(200,stringBuffer.toString());
    }
}

关于controller的解释说明

  • 注释1 指定了需要校验的组是 BGroup。属于AGroup的就不会被校验
  • 如果校验不通过,springmvc就会抛出异常MethodArgumentNotValidException,此时就有两种选择
  • 注释4 统一的异常处理,
  • 注释2 将BindingResult 入参,在业务方法里面处理。
  • 注释2 关于BindingResult。至于这个对象是怎么来的,就要你熟悉springmvc 的流程了。 在绑定参数和校验的时候就会有这个对象
  • 注释5 通过bindResult就可以得到所有的错误,还记得在自定义校验的时候设置的模板了吗2. 自定义校验类,从这里就能得到自定义的错误消息

4. 请求示例

{
  "id": 1,
  "name": "fdfdsfdsf",
  "age": 0.0,
  "list": [
    {
      "province": "1434343",
      "city": "1"
    }
  ],
  "address": {
    "province": "2",
    "city": "1"
  }
}

5. Validation.buildDefaultValidatorFactory()

这个东西就是 自己校验,springmvc帮我们封装了校验,这个方法就是 原生的校验

  • 校验代码
B name = new B(-1, "", 54545454.0);
        Set<ConstraintViolation<Object>> validateSet = Validation.buildDefaultValidatorFactory() //定义验证工厂
                .getValidator() //得到验证器
                .validate(name,a1.class); //验证 (对象,组)//这个组就是上面列子中的组

            String messages = validateSet.stream()
                    .map(ConstraintViolation::getMessage)
                    .reduce((m1, m2) -> m1 + "||      " + m2)
                    .orElse("参数输入有误!");
            throw new IllegalArgumentException(messages);
        }
  • 自己的实体
@Data
class B{
    @Min(value = 0,groups = a1.class,message = "不能小于0")
    private Integer id;
    @NotBlank(message = "不能为空",groups =b1.class )
    private String name;
    @Max(value = 100,message = "不能大于100")
    private Double age;

    public B( Integer id, String name, Double age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}
interface a1{

}
interface b1{

}

这就是 springmvc帮我们封装好的部分,原生的实现就那样
大体就是这样,以备之后使用