本示例使用JDK1.8, Spring Boot 1.5.9.RELEASE, hibernate-validator-5.3.6.Final。

1.功能需求

在提交的表单服务端需要验证提交的表单,验证的主要功能主要有:

  1. 对提交的表单字段进行常用验证
    例如:字段是否为空,字段长度

  2. 自定义错误信息,消息的国际化支持
    例如:需要显示个性的消息

  3. 自定义验证方式
    例如:需要根据一定的逻辑验证

假设:
现有一学生对象,需要进去添加操作,需要对其字段有名字,年龄和生日,先有如下验证:
a.所有字段必须有值
b.年龄在18-130岁
c.名字不能重复

2.常用验证

hibernate-validator-.jarvalidation-api-.jar 有为我们提供一些常用的验证注解:

1
2
3
4
@NotNull
@NotBlank
@Size(min=, max=)
......

更多方法:
https://docs.jboss.org/hibernate/validator/5.3/reference/en-US/html_single/

3.项目结构

针对如上的需求,创建 spring-boot-hibernate-validation-demo 项目:

4.自定义验证注解

1.定义业务逻辑的类和方法:StudentService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

package com.devnp.springboothibernatevalidationdemo.service;

import org.springframework.stereotype.Component;

/**
* @author duliu
*
*/
@Component("studentService")
public class StudentService {

public boolean nameUniqueCheck(String name) {

if("Tom".equals(name))
return false;

return true ;
}
}

2.创建验证的注解:StudentNameUnique.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.devnp.springboothibernatevalidationdemo.validation;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ METHOD, FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {StudentNameUniqueValidation.class })
public @interface StudentNameUnique {

String message() default "{stu.name.unique}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };



}

3.实现验证:StudentNameUniqueValidation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

package com.devnp.springboothibernatevalidationdemo.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.springframework.beans.factory.annotation.Autowired;

import com.devnp.springboothibernatevalidationdemo.service.StudentService;

/**
* @author duliu
*
*/
public class StudentNameUniqueValidation implements ConstraintValidator<StudentNameUnique, Object>{


@Autowired
private StudentService studentService ;

@Override
public void initialize(StudentNameUnique constraintAnnotation) {
// TODO Auto-generated method stub
System.out.println("Student Name Unique Validation Init...");
}

@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
// TODO Auto-generated method stub
return studentService.nameUniqueCheck((String) value);
}

}

5.验证对象 Object

先有一学生对象,对其的属性设置相应注解:StudentDto.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

package com.devnp.springboothibernatevalidationdemo.dto;

import java.io.Serializable;
import java.util.Date;

import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;

import com.devnp.springboothibernatevalidationdemo.validation.StudentNameUnique;

/**
* @author duliu
*
*/
public class StudentDto implements Serializable{

/**
*
*/
private static final long serialVersionUID = 1L;

/**
*
*/
@NotBlank(message="{stu.name.not.blank}")
@StudentNameUnique
private String stuName ;


/**
* Student age should be > 18 and < 130
*/
@Range(min= 18, max=130, message="{stu.age.range}")
@NotNull(message="{stu.age.not.blank}")
private Integer stuAge ;

/**
*
*/
@NotNull(message="{stu.birth.not.blank}")
private Date stuBirth ;


public StudentDto() {
super();
}

public StudentDto(String stuName, Integer stuAge, Date stuBirth) {
super();
this.stuName = stuName;
this.stuAge = stuAge;
this.stuBirth = stuBirth;
}

public String getStuName() {
return stuName;
}

public void setStuName(String stuName) {
this.stuName = stuName;
}

public Integer getStuAge() {
return stuAge;
}

public void setStuAge(Integer stuAge) {
this.stuAge = stuAge;
}

public Date getStuBirth() {
return stuBirth;
}

public void setStuBirth(Date stuBirth) {
this.stuBirth = stuBirth;
}

@Override
public String toString() {
return "StudentDto [stuName=" + stuName + ", stuAge=" + stuAge + ", stuBirth=" + stuBirth + "]";
}

}

6.消息配置

关于Spring Boot对国际化的配置:
Spring Boot 国际化 (Spring Boot Internationalization Demo)

1..properties文件

StudentMessages.properties

1
2
3
4
5
6
7
return.msg = 请求成功

stu.name.not.blank = 学生姓名不能为空
stu.name.unique = 学生姓名不能重复
stu.age.not.blank = 学生年龄不能为空
stu.age.range = 学生年龄应该在18到130岁
stu.birth.not.blank = 学生生日不能为空

StudentMessages_zh_CN.properties

1
2
3
4
5
6
7
return.msg = 请求成功

stu.name.not.blank = 学生姓名不能为空
stu.name.unique = 学生姓名不能重复
stu.age.not.blank = 学生年龄不能为空
stu.age.range = 学生年龄应该在18到130岁
stu.birth.not.blank = 学生生日不能为空

StudentMessages_en_US.properties

1
2
3
4
5
6
7
return.msg = Success

stu.name.not.blank = Student Name cannot be blank.
stu.name.unique = Student Name must be Unique.
stu.age.not.blank = Student Age cannot be blank.
stu.age.range = Student Age should be 18 to 130.
stu.birth.not.blank = Student Birth cannot be blank.

2.配置
将消息文件添加到Spring Boot中:

1
2
3
4
5
spring:
messages:
fallback-to-system-locale: false
encoding: UTF-8
basename: com/devnp/message/StudentMessages

设置加载和国际化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.devnp.springboothibernatevalidationdemo.config;

import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;

/**
* @author duliu
*
*/
@Configuration
public class MessageConfig extends WebMvcConfigurerAdapter {

@Autowired
private MessageSource messageSource;

@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();

localValidatorFactoryBean.setValidationMessageSource(messageSource);

return localValidatorFactoryBean;
}

@Override
public Validator getValidator() {
return validator();
}

@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.CHINESE);
return slr;
}

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}

}

7.控制器 Controller

表单提交的控制器:StudentController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.devnp.springboothibernatevalidationdemo.web;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.devnp.springboothibernatevalidationdemo.dto.StudentDto;

/**
* @author duliu
*
*/
@RestController
@RequestMapping("/student")
public class StudentController {

@Autowired
private MessageSource messageSource;

@RequestMapping(path="/add", method= {RequestMethod.POST})
public String add(@Valid @RequestBody StudentDto studentDto, BindingResult bindingResult) {

if(bindingResult.hasErrors()) {
bindingResult.getAllErrors().stream().forEach(error -> {System.out.println(error.getDefaultMessage());});
}

System.out.println(studentDto);

return messageSource.getMessage("return.msg", null, LocaleContextHolder.getLocale()) ;
}
}

8.测试

撰写单元测试的类和方法:StudentControllerTests.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

package com.devnp.springboothibernatevalidationdemo.web;

import java.util.Date;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

/**
* @author duliu
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentControllerTests {

@Autowired
private WebApplicationContext context;

private MockMvc mvc;

@Before
public void before() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}

@Test
public void testAdd() throws Exception {

Date date = new Date();

String enResult = this.mvc
.perform(MockMvcRequestBuilders.post("/student/add?lang=en_US").contentType(MediaType.APPLICATION_JSON_UTF8)
.content("{\"stuName\":\"jack\",\"stuAge\":null, \"stuBirth\":\"" + date.getTime() + "\"}"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn().getResponse().getContentAsString();

System.out.println("Result : " + enResult);

String zhResult = this.mvc
.perform(MockMvcRequestBuilders.post("/student/add?lang=zh_CN").contentType(MediaType.APPLICATION_JSON_UTF8)
.content("{\"stuName\":\"Tom\",\"stuAge\":12, \"stuBirth\":\"" + date.getTime() + "\"}"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn().getResponse().getContentAsString();

System.out.println("结果 : " + zhResult);
}

}

运行结果:

1
2
3
4
5
6
7
8
9
Student Name Unique Validation Init...
Student Age cannot be blank.
StudentDto [stuName=jack, stuAge=null, stuBirth=Mon Apr 09 15:24:51 SGT 2018]
Result : Success

学生姓名不能重复
学生年龄应该在18到130岁
StudentDto [stuName=Tom, stuAge=12, stuBirth=Mon Apr 09 15:24:51 SGT 2018]
结果 : 请求成功

9.代码下载

spring-boot-hibernate-validation-demo.zip