在我的另一篇博客中(SpringMVC),学习了如何使用Spring MVC结合Hibernate的校验框架validation(它和hibernate没有任何关系)对参数进行校验。在实际项目中,参数的校验逻辑可能比较复杂,这时我们可以自定义注解来实现参数校验,下面是一个简单的例子。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wuy</groupId>
<artifactId>custom-validation</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>custom-validation</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Web起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 测试相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- hibernate validator相关依赖 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.3.2.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- lombok简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8080
程序启动类
package com.wuy.customvalidation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CustomValidationApplication {
public static void main(String[] args) {
SpringApplication.run(CustomValidationApplication.class, args);
}
}
User.java
package com.wuy.customvalidation.entity;
import com.wuy.customvalidation.validation.Phone;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Data
public class User implements Serializable {
private static final long serialVersionUID = -1L;
@NotBlank(message = "用户名不能为空")
private String userName;
@Phone(message = "手机号不正确")
private String phone;
}
这个类就是我们要进行验证的POJO,@Phone是我们自定义的注解,用于对phone这个字段进行验证。我们看看@Phone的源码。
编写自定义注解
package com.wuy.customvalidation.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
@Constraint(validatedBy = PhoneConstraintValidator.class)
public @interface Phone {
String message() default "Phone num is invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Documented、@Retention、@Target是元注解,这里不做深入,message方法指定验证失败时的错误信息,默认是“Phone num is invalid”。我们主要看看@Constraint注解,它有一个validatedBy属性,用于指定具体进行验证的类,PhoneConstraintValidator的源码如下:
package com.wuy.customvalidation.validation;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneConstraintValidator implements ConstraintValidator<Phone, String> {
@Override
public void initialize(Phone constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isEmpty(value)) {
return false;
}
return value.matches("^\\d{11}$");
}
}
PhoneConstraintValidator类实现了ConstraintValidator接口,第一个泛型指定自定义的注解Phone这个类,第二个泛型指定要验证的参数的类型,这里是String。initialize方法用于做一些初始化,具体的验证逻辑写在isValid方法里面,这个方法返回false表示验证失败,返回true表示验证成功。这里的验证逻辑是,手机号符合11位数字即可。
编写Controller
代码如下:
package com.wuy.customvalidation.controller;
import com.alibaba.fastjson.JSON;
import com.wuy.customvalidation.entity.User;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.stream.Collectors;
@RestController
public class TestController {
@RequestMapping("/save")
@ResponseBody
public String save(@Valid User user, BindingResult errors) {
if (errors.hasErrors()) {
errors.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.forEach(System.out::println);
return JSON.toJSONString(errors.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining("; ")));
}
return JSON.toJSONString(user);
}
}
@Valid注解用于标注需要进行验证的POJO,BindingResult用于捕获验证失败的信息。注意:@Valid和BindingResult是配对出现,并且顺序是固定的(一前一后)。方法中,首先判断是否有错误信息,如果有,先是将错误信息全部打印到控制台,然后将错误信息以“;”分割后返回给调用者;如果验证通过,则直接返回User对象。这里用到了Java 8的Stream API,关于Java 8的新特性,可以参考我相应的文章。
至此,自定义注解进行参数校验即完成。