自定义校验注解

目录

1. 自定义校验注解步骤

2. 自定义校验注解

2.1 基本语法

2.2 几个元注解

3. 创建验证器

4. 使用注解

5. 利用反射获取注解


API开发中经常会遇到一些对请求数据进行验证的情况,这时候如果使用注解就有两个好处。

  • 一是验证逻辑和业务逻辑分离,代码清晰;
  • 二是验证逻辑可以轻松复用,只需要在要验证的地方加上注解就可以。

以后开发过程中,关于参数合法性的校验,如果有已实现的注解(例如@NotNull、@NotEmpty等)可以直接使用,没有现成的可用,尽量使用自定义注解,使得业务逻辑与校验分离,校验逻辑复用,代码结构清晰。

需求:接收一个Student对象,并希望对象里的age域的值是奇数。下面以此需求为例,解析自定义校验注解。

1. 自定义校验注解步骤

  1. 一个自定义的注解,并且指定验证器
  2. 一个验证器的实现

2. 自定义校验注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AgeValidator.class)
public @interface Odd {
    String message() default "Age Must Be Odd";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2.1 基本语法

注解里面定义的是:注解类型元素!

  1. 访问修饰符必须为public,不写默认为public;
  2. 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
  3. 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(使用的时候可以省略指定key,默认即为value);
  4. ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
  5. default代表默认值,值必须和第2点定义的类型一致;
  6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。

可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法

2.2 几个元注解

  • @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义,包括:TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE、ANNOTATION_TYPE等;
  • @Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。可以有RetentionPolicy.SOURCE(仅保存在源码中,会被编译器丢弃),RetentionPolicy.CLASS(在class文件中可用,会被VM丢弃)以及RetentionPolicy.RUNTIME(在运行期也被保留),这里选择了生命周期最长的RetentionPolicy.RUNTIME;
  • @Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
  • @Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
  • @Constraint是最关键的,它表示这个注解是一个验证注解,并且指定了一个实现验证逻辑的验证器
  • message()指明了验证失败后返回的消息,此方法为@Constraint要求
  • groups()payload()也为@Constraint要求,可默认为空,详细用途可以查看@Constraint文档

3. 创建验证器

public class AgeValidator implements ConstraintValidator<Odd,Integer> {
    @Override
    public void initialize(Odd constraintAnnotation) {
    }

    @Override
    public boolean isValid(Integer age, ConstraintValidatorContext constraintValidatorContext) {
        return age % 2 != 0;
    }
}

其中,

  • 验证器ConstraintValidator有两个类型参数,第一个是所属的注解,第二个是注解作用地方的类型,这里因为作用在age上,因此这里用了Integer;
  • initialize()可以在验证开始前调用注解里的方法,从而获取到一些注解里的参数,这里用不到;
  • isValid()就是判断是否合法的地方,参数age就是所验证字段的实际值;

4. 使用注解

注解和验证器创建好之后,就可以使用注解了

@RestController
public class StudentResource {
    @PostMapping("/student")
    public String addStudent(@Valid @RequestBody Student student) {
        return "Student Created";
    }
}
public class Student {
    @Odd
    private int age;
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在需要启用验证的地方加上@Valid注解,这时候如果请求里的Student年龄不是奇数,就会得到一个400响应,这时候注意设置全局异常处理,异常响应体如下:

{
    "timestamp": "2018-08-15T17:01:44.598+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Odd.student.age",
                "Odd.age",
                "Odd.int",
                "Odd"
            ],
            "arguments": [
                {
                    "codes": [
                        "student.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                }
            ],
            "defaultMessage": "Age Must Be Odd",
            "objectName": "student",
            "field": "age",
            "rejectedValue": 12,
            "bindingFailure": false,
            "code": "Odd"
        }
    ],
    "message": "Validation failed for object='student'. Error count: 1",
    "path": "/student"
}

5. 利用反射获取注解

public class TestAnnotation {
    public static void main(String[] args){
        try {
            //获取Student的Class对象
            Class stuClass = Class.forName("pojos.Student");

            //说明一下,这里形参不能写成Integer.class,应写为int.class
            Method stuMethod = stuClass.getMethod("study",int.class);

            if(stuMethod.isAnnotationPresent(TestAnnotation.class)){
                System.out.println("Student类上配置了TestAnnotation注解!");
                //获取该元素上指定类型的注解
                TestAnnotation testAnnotation = stuMethod.getAnnotation(TestAnnotation.class);
                System.out.println("name: " + TestAnnotation.name() + ", age: " + TestAnnotation.age()
                    + ", score: " + TestAnnotation.score()[0]);
            }else{
                System.out.println("Student类上没有配置TestAnnotation注解!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

其中,

  1.  如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果 是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型 上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!
  2. isAnnotationPresent(Class annotationClass) 方法是专门判 断该元素上是否配置有某个指定的注解;
  3. getAnnotation(Class<A> annotationClass) 方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
  4. 反射对象上还有一个方法 getAnnotations() ,该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 编写自定义校验注解时,可以使用 `@ReportAsSingleViolation` 注解指定只要有一个校验失败就返回结果,而不是等待所有的校验结果返回。同时,可以使用 `@javax.validation.constraints.Null` 和 `@javax.validation.constraints.NotNull` 注解来判断参数是否为 `null`。 具体来说,你可以按照以下步骤编写一个允许参数为空的自定义校验注解: 1. 定义注解 ```java @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = {MyValidator.class}) @ReportAsSingleViolation public @interface MyAnnotation { String message() default "my message"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` 2. 定义校验器 ```java public class MyValidator implements ConstraintValidator<MyAnnotation, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { // 允许参数为空,直接返回 true return true; } // 对非空参数进行校验 boolean isValid = // 校验逻辑 if (!isValid) { context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate("my message") .addConstraintViolation(); } return isValid; } } ``` 在校验,我们首先判断参数是否为 `null`,如果是,则直接返回 `true`,即认为校验通过。如果参数不为 `null`,则进行校验逻辑,如果校验失败,则使用 `context.buildConstraintViolationWithTemplate()` 方法构建校验失败的信息。 3. 在需要校验的参数上应用注解 ```java public void myMethod(@MyAnnotation String param) { // 方法逻辑 } ``` 在需要校验的参数上使用 `@MyAnnotation` 注解,即可触发自定义校验注解校验逻辑。如果参数为 `null`,则直接返回 `true`,否则进行校验逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值