API开发中经常会遇到一些对请求数据进行验证的情况,这时候如果使用注解就有两个好处,一是验证逻辑和业务逻辑分离,代码清晰,二是验证逻辑可以轻松复用,只需要在要验证的地方加上注解就可以。

Java提供了一些基本的验证注解,比如@NotNull@Size,但是更多情况下需要自定义验证逻辑,这时候就可以自己实现一个验证注解,方法很简单,仅需要两个东西:

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

自定义验证注解

考虑有一个API,接收一个Student对象,并希望对象里的age域的值是奇数,这时候就可以创建以下注解:

@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 {};
}

其中:

  • @Target指明这个注解要作用在什么地方,可以是对象、域、构造器等,因为要作用在age域上,因此这里选择FIELD
  • @Retention指明了注解的生命周期,可以有SOURCE(仅保存在源码中,会被编译器丢弃),CLASS(在class文件中可用,会被VM丢弃)以及RUNTIME(在运行期也被保留),这里选择了生命周期最长的RUNTIME
  • @Constraint是最关键的,它表示这个注解是一个验证注解,并且指定了一个实现验证逻辑的验证器
  • message()指明了验证失败后返回的消息,此方法为@Constraint要求
  • groups()payload()也为@Constraint要求,可默认为空,详细用途可以查看@Constraint文档

创建验证器

有了注解之后,就需要一个验证器来实现验证逻辑:

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;
}
}

其中:

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

应用注解

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

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;
}
}
@RestController
public class StudentResource {
@PostMapping("/student")
public String addStudent(@Valid @RequestBody Student student) {
return "Student Created";
}
}

在需要启用验证的地方加上@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"
}

也可以手动来处理错误,加上一个BindingResult来接收验证结果即可:

@RestController
public class StudentResource {
@PostMapping("/student")
public String addStudent(@Valid @RequestBody Student student, BindingResult validateResult) {
if (validateResult.hasErrors()) {
return validateResult.getAllErrors().get(0).getDefaultMessage();
}
return "Student Created";
}
}

这时候如果验证出错,便只会返回一个状态为200,内容为Age Must Be Odd的响应。

JAVA里自定义注解来进行数据验证的更多相关文章

  1. Java实现自定义注解开发

    Java实现自定义注解开发 一直都对注解开发挺好奇的,最近终于有时间自己实践了一把,记录一下 万一后期会用到呢 哈哈哈 首先我们了解一下自定义注解的标准示例,注解类使用 @interface 关键字修 ...

  2. Java Annotation自定义注解详解

    在开发过程中总能用到注解,但是从来没有自己定义过注解.最近赋闲在家,研究整理了一番,力求知其然知其所以然. 本文会尝试描述什么是注解,以及通过一个Demo来说明如何在程序中自定义注解.Demo没有实际 ...

  3. 反射+自定义注解---实现Excel数据列属性和JavaBean属性的自动映射

    简单粗暴,直奔主题.   需求:通过自定义注解和反射技术,将Excel文件中的数据自动映射到pojo类中,最终返回一个List<pojo>集合? 今天我只是通过一位使用者的身份来给各位分享 ...

  4. Java利用自定义注解、反射实现简单BaseDao

    在常见的ORM框架中,大都提供了使用注解方式来实现entity与数据库的映射,这里简单地使用自定义注解与反射来生成可执行的sql语句. 这是整体的目录结构,本来是为复习注解建立的项目^.^ 好的,首先 ...

  5. Java如何自定义注解

    本文主要是记录所学,以供后续参考.注解是Java 1.5引入的,Java自定义注解是通过运行时靠反射获取注解,注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定 ...

  6. Springboot--元注解及自定义注解(表单验证)

    本文简单说明一下元注解,然后对元注解中的@Retention做深入的讨论,在文章最后使用元注解写一个自定义注解来结尾. 一.结论: @Target:注解的作用目标 @Target(ElementTyp ...

  7. Java基于自定义注解的面向切面的实现

    目的:实现在任何想要切的地方添加一个注解就能实现面向切面编程 自定义注解类 @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retentio ...

  8. java/springboot自定义注解实现AOP

    java注解 即是注释了,百度解释:也叫元数据.一种代码级别的说明. 个人理解:就是内容可以被代码理解的注释,一般是一个类. 元数据 也叫元注解,是放在被定义的一个注解类的前面 ,是对注解一种限制. ...

  9. [转] Java @interface 自定义注解

    [From] http://blog.csdn.net/afterlife_qiye/article/details/53748973 1. 注解的好处 注解可以替代配置文件完成对某些功能的描述,减少 ...

随机推荐

  1. 编程经验点滴----巧妙解决 Oracle NClob 读写问题

    最近一个新项目中,尝试在 Oracle 数据库中使用 NCLOB 来保存大的 xml 字符串. 在代码自动生成工具(通过 JDBC 驱动程序,读数据库表结构,自动生成对应的 java 代码,包含增加. ...

  2. Linux修改挂载目录名称

    Local系统管理员新增了一个VG,将一个原挂载点/u02改为了/u02-old, 如下所示. [root@mylnx01 ~]# df -h Filesystem            Size  ...

  3. Java常考面试题(经典)

    什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机进程.Java源文件被编译成能被Java虚拟机执行的字节码文件. Java被设计 ...

  4. python第一百六十九天,第十九周作业

    FIRSTCRM 学员管理开发需求: 1.分讲师\学员\课程顾问角色, 2.学员可以属于多个班级,学员成绩按课程分别统计 3.每个班级至少包含一个或多个讲师 4.一个学员要有状态转化的过程 ,比如未报 ...

  5. 4.7Python数据处理篇之Matplotlib系列(七)---matplotlib原理分析

    目录 目录 前言 (一)总框架分析 (二)函数式的绘图 1.说明: 2.函数绘图的缺优点 3.绘图类的函数 4.操作类的函数 5.例子: (三)面向对象式的绘图 1.基本概念 2.基本对象 3.面向对 ...

  6. LeetCode算法题-Linked List Cycle(Java实现)

    这是悦乐书的第176次更新,第178篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第35题(顺位题号是141).给定一个链表,确定它是否有一个循环. 本次解题使用的开发工 ...

  7. puppet 横向扩展(一)

    目录 1. 概述 2. 实验环境 3. 实验步骤 3.1. 创建puppetmaster的rack环境 3.2. 配置文件设置 3.3. 补充说明 3.4. 测试配置结果 3.4.1. 默认的负载均衡 ...

  8. Beta冲刺博客汇总(麻瓜制造者)

    Beta冲刺博客 Beta冲刺(1/5)(麻瓜制造者) Beta冲刺(2/5)(麻瓜制造者) Beta冲刺(3/5)(麻瓜制造者) Beta冲刺(4/5)(麻瓜制造者) Beta冲刺(5/5)(麻瓜制 ...

  9. Linux 简介(day1)

    一.Linux 诞生于1991年 二.创始人:林纳斯.托瓦茨(Linus Torvalds) 三.logo:企鹅 四.Linux完整系统包括 1.Linux kernel (Linux 内核) 2.f ...

  10. Why do Kafka consumers connect to zookeeper, and producers get metadata from brokers?

    Why do Kafka consumers connect to zookeeper, and producers get metadata from brokers? Ask Question u ...