天天看点

spring boot 自定义注解以及validator的使用

1.注解的概念

  1. 注解是一种元数据模式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
  2. 注解不会对所修饰的代码产生直接的影响

2.注解的使用范围

  1. 类、接口、枚举:@Controller 标记controller层、@Service 标记service层、@Mapper 标记DAO,自动映射
  2. 属性 @Autowired 自动装配
  3. 方法 @Override 表示重写
  4. 方法形式参数 @Param 设置传递参数的名称
  5. 构造方法
  6. 局部变量
  7. 注解类型 @Target 标记此注解类的使用范围

3.注解的基本语法

  1. 注解类型的声明:注解在Java中,与类、接口、枚举类似,因此声明语法基本一致,只是所使用的关键字有所不同,其使用@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
public @interface TestAnnotation{
}
           
  1. 注解类型的实现部分:其实现部分只能定义注解类型元素(annotation type element)。
//class的一维数组类型
Class<? extends Payload>[] payload() default { };
//基本数据类型
int ret() default 7;
//String类型
String message() default "message params error";
           
  1. 定义注解类型的特点:

    1.访问修饰符必须为public,不写默认为public 。

    2.该元素类型只能是基本数据类型,String、Class、枚举类型、注解类型(体现了注解的嵌套效果),以及上述类型的一维数组。

    3.()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊语法

    4.default代表默认的值,值必须和第2点定义的类型一致

    5.如果没有默认值,代表后续使用注解时必须给该类型元素赋值

4.自定义注解所使用到的元注解

  1. @Target注解,专门用来限定某个自定义注解能被应用到哪些Java元素上面
//只适用于属性
@Target(ElementType.FIELD)
//属性和方法都适用
@Target({ElementType.METHOD, ElementType.TYPE})
           

其中ElementType支持的类别为

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}
           
  1. @Retention,用来修饰自定义注解的生命力,有下面三种方式

    1.Java源文件阶段

@Retention(RetentionPolicy.SOURCE)
           

如果一个注解被定义为SOURCE,则它会被限定在Java源文件中。也就是说这个注解既不会参与编译也不会在运行期起任何作用,这个注解就和注释一样,只能被阅读Java文件的人看到。

2.编译class文件阶段

@Retention(RetentionPolicy.CLASS)
           

如果一个注解被定义为CLASS,那么它将被编译到Class文件中,编译器可以在编译时根据注解做一些处理动作,但是运行JVM时会忽略它,我们在运行期也不能读取到。在默认情况下,自定义注解会使用此属性

3.运行期阶段

@Retention(RetentionPolicy.RUNTIME)
           

如果一个注解被定义为RUNTIME,那么这个注解可以用在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发红的自定义RUNTIME

  1. @Document,用来指定自定义注解能否随着被定义的java文件生成到JavaDoc文档中。
  2. @Inherited,是指某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。此注解只对那些@Target被定义为ElementType.的自定义注解起作用

5.使用场景

1.可以和Validator配合使用来进行请求字段的非空校验

2.可以和aop配合通过反射来进行日志管理

3.可以和aop配合通过反射来控制某些特定接口的权限管理

6.使用样例

样例使用自定义注解和Validator来配合进行请求字段的非空校验

1.添加pom依赖

如果是单纯使用自定义注解,则不需要添加pom依赖,如果要配合validator使用,则需要添加validator的依赖

<!-- custom validator start -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.7.Final</version>
</dependency>
<!-- custom validator end -->
           

2.生成自定义注解类

//仅仅用于请求字段的非空校验
@Target(ElementType.FIELD)
//需要jvm运行时使用
@Retention(RetentionPolicy.RUNTIME)
//使用validator时必须添加,指定与NotNullValidator自定义校验器配合使用
@Constraint(validatedBy = NotNullValidator.class)
//@interface 代表此类是注解类
public @interface CheckParamNullAnnotation {
    //使用validator时必须添加,主要是将validator进行分类,不同的group中会执行不同的validator操作
    Class<?>[] groups() default { };
    //使用validator时必须添加,主要针对bean的
    Class<? extends Payload>[] payload() default { };
    //使用validator时必须添加,定制化的提示信息
    String message() default "message params error";
    //自定义注解类型,如果不适用validator,只需要写这一个注解类型即可。
    int ret() default 7;
}
           

如果groups、payload和message没有添加的话会报下面的异常

javax.validation.ConstraintDefinitionException: HV000074: com.ding.platform.common.annotation.CheckParamNullAnnotation contains Constraint annotation, but does not contain a message parameter.
	at org.hibernate.validator.internal.metadata.core.ConstraintHelper.assertMessageParameterExists(ConstraintHelper.java:1060) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.core.ConstraintHelper.lambda$isConstraintAnnotation$3(ConstraintHelper.java:1006) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660) ~[na:1.8.0_131]
	at org.hibernate.validator.internal.metadata.core.ConstraintHelper.isConstraintAnnotation(ConstraintHelper.java:1005) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraintAnnotations(AnnotationMetaDataProvider.java:534) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraints(AnnotationMetaDataProvider.java:479) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findConstraints(AnnotationMetaDataProvider.java:461) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findPropertyMetaData(AnnotationMetaDataProvider.java:236) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getFieldMetaData(AnnotationMetaDataProvider.java:229) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.retrieveBeanConfiguration(AnnotationMetaDataProvider.java:129) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getBeanConfiguration(AnnotationMetaDataProvider.java:120) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanConfigurationForHierarchy(BeanMetaDataManagerImpl.java:234) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.createBeanMetaData(BeanMetaDataManagerImpl.java:201) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanMetaData(BeanMetaDataManagerImpl.java:165) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:156) ~[hibernate-validator-6.1.7.Final.jar:6.1.7.Final]
           

3.生成自定义校验器

自定义校验器需要实现ConstraintValidator接口,其中两个参数,一个是指定的自定义注解CheckParamNullAnnotation,一个是传入的值Object。

public class NotNullValidator implements ConstraintValidator<CheckParamNullAnnotation, Object> {

    @Override
    public boolean isValid(Objecto, ConstraintValidatorContext constraintValidatorContext) {
        //此时name是当前非空校验的字段名
        String name = String.valueOf(((ConstraintValidatorContextImpl) constraintValidatorContext).getConstraintViolationCreationContexts().get(0).getPath());
        System.out.println(name + " value is " + String.valueOf(o));
        //o是字段的值,我们需要校验是否为空
        if(null == o){
            //返回false代表校验失败
            return false;   
        }
        //返回true代表校验成功
        return true;
    }
}
           

4.生成请求类

public class TestReq {
	//在需要校验的字段上打上自定义注解
    @CheckParamNullAnnotation
    private String param1;

    public String getParam1() {
        return param1;
    }

    public void setParam1(String param1) {
        this.param1 = param1;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("TestReq{");
        sb.append("param1='").append(param1).append('\'');
        sb.append('}');
        return sb.toString();
    }
}
           

5.生成controller类

注意,在controller层需要校验的请求类前面一定要加 @Valid

@Controller
@ResponseBody
public class TestController {
    @GetMapping(value = "/value")
    //这边必须要加@Valid
    public String testController(@Valid TestReq req){
        return "123";
    }
}
           

6.验证结果

启动项目,当param1值为空时,结果如下

spring boot 自定义注解以及validator的使用

当param1值不为空时,结果如下

spring boot 自定义注解以及validator的使用

7.自定义响应

如果说不想要系统带的400响应,那么在controller类中的req参数后面添加BindingResult入参,它会自动接收校验器的校验结果,此时校验器不会直接返回400。特别要注意的是,BindingResult入参一定要紧跟在校验请求体的请求后面,也就是一定要在@Valid修饰的对象后面,中间只要有间隔,都会失败。

@Controller
@ResponseBody
public class TestController {
    @GetMapping(value = "/value")
    //这边必须要加@Valid
    public String testController(@Valid TestReq req, BindingResult binRes){
        return "123";
    }
}
           

当param1值为空时。其中errorCount代表请求消息体中有多少个字段校验失败。如果说req有两个参数,param1和param2,都打上非空注解。如果两个都为空,那么errorCount就是2,allError数组长度就是2,分别是param1校验失败和param2校验失败的信息。

spring boot 自定义注解以及validator的使用

当param1值不为空时。这边特别要注意,当errorCount为0的时候,allError是空的,所以不能取值。只有errorcount不为空时,才能取值allError。

spring boot 自定义注解以及validator的使用