Validated 注解完成 Spring Boot 参数校验
1. @Valid 和 @Validated
@Valid 注解,是 Bean Validation 所定义,可以添加在普通方法、构造方法、方法参数、方法返回、成员变量上,表示它们需要进行约束校验。
@Validated 注解,是 Spring Validation 所定义,可以添加在类、方法参数、普通方法上,表示它们需要进行约束校验。并且,@Validated 具有 value 属性,支持分组校验。
- 声明式校验
@Validated
- 分组检验
@Validated
- 嵌套校验
@Valid
2. 常用注解
3. 快速入门
- 创建Maven项目
- 修改pom.xml
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <groupId>com.c3stones</groupId> 6 <artifactId>spring-boot-validated-demo</artifactId> 7 <version>0.0.1-SNAPSHOT</version> 8 <name>spring-boot-validated-demo</name> 9 <description>Spring Boot Validated Demo</description> 10 11 <parent> 12 <groupId>org.springframework.boot</groupId> 13 <artifactId>spring-boot-starter-parent</artifactId> 14 <version>2.1.4.RELEASE</version> 15 </parent> 16 17 <properties> 18 <java.version>1.8</java.version> 19 </properties> 20 21 <dependencies> 22 <dependency> 23 <groupId>org.projectlombok</groupId> 24 <artifactId>lombok</artifactId> 25 </dependency> 26 <dependency> 27 <groupId>org.springframework.boot</groupId> 28 <artifactId>spring-boot-starter-web</artifactId> 29 </dependency> 30 <dependency> 31 <groupId>org.springframework.boot</groupId> 32 <artifactId>spring-boot-starter-test</artifactId> 33 <scope>test</scope> 34 </dependency> 35 </dependencies> 36 37 </project>
- 创建启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
- 创建DTO
1 import javax.validation.constraints.NotEmpty; 2 import javax.validation.constraints.Pattern; 3 4 import org.hibernate.validator.constraints.Length; 5 6 import lombok.Data; 7 8 /** 9 * 用户保存DTO 10 * 11 */ 12 @Data 13 public class UserSaveDto { 14 15 /** 16 * 用户名称 17 */ 18 @NotEmpty(message = "用户名称不能为空") 19 @Length(min = 6, max = 12, message = "账号长度为 6-12 位") 20 @Pattern(regexp = "^[A-Za-z0-9]+$", message = "用户名称格式为数字或字母") 21 private String username; 22 23 /** 24 * 密码 25 */ 26 @NotEmpty(message = "密码不能为空") 27 @Length(min = 6, max = 18, message = "密码长度为 6-18 位") 28 private String password; 29 }
- 创建Controller
1 import javax.validation.Valid; 2 import javax.validation.constraints.Min; 3 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.validation.annotation.Validated; 7 import org.springframework.web.bind.annotation.GetMapping; 8 import org.springframework.web.bind.annotation.PostMapping; 9 import org.springframework.web.bind.annotation.RequestMapping; 10 import org.springframework.web.bind.annotation.RequestParam; 11 import org.springframework.web.bind.annotation.RestController; 12 13 import com.c3stones.dto.UserSaveDto; 14 15 @RestController 16 @RequestMapping("/user") 17 @Validated 18 public class UserController { 19 20 private Logger logger = LoggerFactory.getLogger(getClass()); 21 22 @GetMapping("/get") 23 public String get(@RequestParam("id") @Min(value = 1L, message = "id必须大于0") Integer id) { 24 logger.info("获取用户信息,id:" + id); 25 return "get success"; 26 } 27 28 @PostMapping("/save") 29 public String save(@Valid UserSaveDto saveDto) { 30 logger.info("保存用户信息:", saveDto.toString()); 31 return "save success"; 32 } 33 34 }
- 启动项目
- 测试get方法
1 curl -X GET "http://localhost:8080/user/get?id=0" 2 #返回: 3 { 4 "timestamp": "2020-05-20T03:27:23.667+0000", 5 "status": 500, 6 "error": "Internal Server Error", 7 "message": "get.id: id必须大于0", 8 "path": "/user/get" 9 } 10 11 curl -X GET "http://localhost:8080/user/get?id=1" 12 #返回: 13 get success
- 测试save方法
1 curl -X POST "http://localhost:8080/user/save?username=test&password=123" 2 #返回: 3 { 4 "timestamp": "2020-05-20T03:29:46.684+0000", 5 "status": 400, 6 "error": "Bad Request", 7 "errors": [ 8 { 9 "codes": [ 10 "Length.userSaveDto.username", 11 "Length.username", 12 "Length.java.lang.String", 13 "Length" 14 ], 15 "arguments": [ 16 { 17 "codes": [ 18 "userSaveDto.username", 19 "username" 20 ], 21 "arguments": null, 22 "defaultMessage": "username", 23 "code": "username" 24 }, 25 12, 26 6 27 ], 28 "defaultMessage": "账号长度为 6-12 位", 29 "objectName": "userSaveDto", 30 "field": "username", 31 "rejectedValue": "test", 32 "bindingFailure": false, 33 "code": "Length" 34 }, 35 { 36 "codes": [ 37 "Length.userSaveDto.password", 38 "Length.password", 39 "Length.java.lang.String", 40 "Length" 41 ], 42 "arguments": [ 43 { 44 "codes": [ 45 "userSaveDto.password", 46 "password" 47 ], 48 "arguments": null, 49 "defaultMessage": "password", 50 "code": "password" 51 }, 52 18, 53 6 54 ], 55 "defaultMessage": "密码长度为 6-18 位", 56 "objectName": "userSaveDto", 57 "field": "password", 58 "rejectedValue": "123", 59 "bindingFailure": false, 60 "code": "Length" 61 } 62 ], 63 "message": "Validation failed for object='userSaveDto'. Error count: 2", 64 "path": "/user/save" 65 } 66 67 curl -X POST "http://localhost:8080/user/save?username=test001&password=123456" 68 #返回: 69 save success
4. 分组校验
- 编写实体类
1 import javax.validation.constraints.NotEmpty; 2 import javax.validation.constraints.NotNull; 3 4 import lombok.AllArgsConstructor; 5 import lombok.Data; 6 import lombok.NoArgsConstructor; 7 8 /** 9 * 用户 10 * 11 */ 12 @Data 13 @NoArgsConstructor 14 @AllArgsConstructor 15 public class User { 16 17 /** 18 * 用户保存分组 19 * 20 */ 21 public interface UserSaveGroup { 22 } 23 24 /** 25 * 用户更新分组 26 * 27 */ 28 public interface UserUpdateGroup { 29 } 30 31 /** 32 * ID 33 */ 34 @NotNull(message = "ID不能为空", groups = { UserUpdateGroup.class }) 35 private Integer id; 36 37 /** 38 * 用户名称 39 */ 40 @NotEmpty(message = "用户名称不能为空", groups = { UserSaveGroup.class, UserUpdateGroup.class }) 41 private String username; 42 43 /** 44 * 密码 45 */ 46 @NotEmpty(message = "密码不能为空", groups = { UserSaveGroup.class, UserUpdateGroup.class }) 47 private String password; 48 }
- Controller添加方法
1 @PostMapping("/saveUser") 2 public String saveUser(@Validated({ UserSaveGroup.class }) User user) { 3 logger.info("保存用户信息:", user.toString()); 4 return "saveUser success"; 5 } 6 7 @PutMapping("/updateUser") 8 public String updateUser(@Validated({ UserUpdateGroup.class }) User user) { 9 logger.info("更新用户信息:", user.toString()); 10 return "updateUser success"; 11 }
- 测试UserSaveGroup分组
1 curl -X POST "http://localhost:8080/user/saveUser" 2 #返回: 3 { 4 "timestamp": "2020-05-20T03:45:15.357+0000", 5 "status": 400, 6 "error": "Bad Request", 7 "errors": [ 8 { 9 "codes": [ 10 "NotEmpty.user.password", 11 "NotEmpty.password", 12 "NotEmpty.java.lang.String", 13 "NotEmpty" 14 ], 15 "arguments": [ 16 { 17 "codes": [ 18 "user.password", 19 "password" 20 ], 21 "arguments": null, 22 "defaultMessage": "password", 23 "code": "password" 24 } 25 ], 26 "defaultMessage": "密码不能为空", 27 "objectName": "user", 28 "field": "password", 29 "rejectedValue": null, 30 "bindingFailure": false, 31 "code": "NotEmpty" 32 }, 33 { 34 "codes": [ 35 "NotEmpty.user.username", 36 "NotEmpty.username", 37 "NotEmpty.java.lang.String", 38 "NotEmpty" 39 ], 40 "arguments": [ 41 { 42 "codes": [ 43 "user.username", 44 "username" 45 ], 46 "arguments": null, 47 "defaultMessage": "username", 48 "code": "username" 49 } 50 ], 51 "defaultMessage": "用户名称不能为空", 52 "objectName": "user", 53 "field": "username", 54 "rejectedValue": null, 55 "bindingFailure": false, 56 "code": "NotEmpty" 57 } 58 ], 59 "message": "Validation failed for object='user'. Error count: 2", 60 "path": "/user/saveUser" 61 } 62 63 curl -X POST "http://localhost:8080/user/saveUser?username=zhangsan&password=123456" 64 #返回: 65 saveUser success
- 测试UserUpdateGroup分组
1 curl -X PUT "http://localhost:8080/user/updateUser" 2 #返回: 3 { 4 "timestamp": "2020-05-20T03:52:01.350+0000", 5 "status": 400, 6 "error": "Bad Request", 7 "errors": [ 8 { 9 "codes": [ 10 "NotEmpty.user.username", 11 "NotEmpty.username", 12 "NotEmpty.java.lang.String", 13 "NotEmpty" 14 ], 15 "arguments": [ 16 { 17 "codes": [ 18 "user.username", 19 "username" 20 ], 21 "arguments": null, 22 "defaultMessage": "username", 23 "code": "username" 24 } 25 ], 26 "defaultMessage": "用户名称不能为空", 27 "objectName": "user", 28 "field": "username", 29 "rejectedValue": null, 30 "bindingFailure": false, 31 "code": "NotEmpty" 32 }, 33 { 34 "codes": [ 35 "NotNull.user.id", 36 "NotNull.id", 37 "NotNull.java.lang.Integer", 38 "NotNull" 39 ], 40 "arguments": [ 41 { 42 "codes": [ 43 "user.id", 44 "id" 45 ], 46 "arguments": null, 47 "defaultMessage": "id", 48 "code": "id" 49 } 50 ], 51 "defaultMessage": "ID不能为空", 52 "objectName": "user", 53 "field": "id", 54 "rejectedValue": null, 55 "bindingFailure": false, 56 "code": "NotNull" 57 }, 58 { 59 "codes": [ 60 "NotEmpty.user.password", 61 "NotEmpty.password", 62 "NotEmpty.java.lang.String", 63 "NotEmpty" 64 ], 65 "arguments": [ 66 { 67 "codes": [ 68 "user.password", 69 "password" 70 ], 71 "arguments": null, 72 "defaultMessage": "password", 73 "code": "password" 74 } 75 ], 76 "defaultMessage": "密码不能为空", 77 "objectName": "user", 78 "field": "password", 79 "rejectedValue": null, 80 "bindingFailure": false, 81 "code": "NotEmpty" 82 } 83 ], 84 "message": "Validation failed for object='user'. Error count: 3", 85 "path": "/user/updateUser" 86 } 87 88 curl -X PUT "http://localhost:8080/user/updateUser?id=1001&username=zhangsan&password=123123" 89 #返回: 90 updateUser success
5. 国际化配置
- 配置文件中添加
1 spring: 2 # i18 message 配置,对应 MessageSourceProperties 配置类 3 messages: 4 basename: i18n/messages # 文件路径基础名 5 encoding: UTF-8 # 使用 UTF-8 编码
- 在项目resource目录下创建i18n文件夹
- 在i18n中添加国际化配置文件
messages.properties
1 Goods.id.NotNull=商品ID不能为空
messages_en.properties
1 Goods.id.NotNull=Goods id cannot be empty
messages_ja.properties
1 Goods.id.NotNull=商品IDは空にできません
- 创建实体
1 import javax.validation.constraints.NotNull; 2 3 import lombok.AllArgsConstructor; 4 import lombok.Data; 5 import lombok.NoArgsConstructor; 6 7 /** 8 * 商品 9 * 10 */ 11 @Data 12 @NoArgsConstructor 13 @AllArgsConstructor 14 public class Goods { 15 16 /** 17 * 商品ID 18 */ 19 @NotNull(message = "{Goods.id.NotNull}") 20 private Integer id; 21 22 /** 23 * 商品名称 24 */ 25 private String name; 26 }
- 创建Controller
1 import javax.validation.Valid; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 import org.springframework.validation.annotation.Validated; 6 import org.springframework.web.bind.annotation.PostMapping; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RestController; 9 10 import com.c3stones.entity.Goods;
11 12 @RestController 13 @RequestMapping("/goods") 14 @Validated 15 public class GoodsController { 16 17 private Logger logger = LoggerFactory.getLogger(getClass()); 18 19 @PostMapping("/save") 20 public String save(@Valid Goods goods) { 21 logger.info("保存商品信息:", goods.toString()); 22 return "save success"; 23 } 24 25 }
- 测试(通过头部指定 Accept-Language 参数)
1 #测试默认中文 2 curl -X POST "http://localhost:8080/goods/save" 3 #返回: 4 { 5 "timestamp": "2020-05-20T04:15:59.417+0000", 6 "status": 400, 7 "error": "Bad Request", 8 "errors": [ 9 { 10 "codes": [ 11 "NotNull.goods.id", 12 "NotNull.id", 13 "NotNull.java.lang.Integer", 14 "NotNull" 15 ], 16 "arguments": [ 17 { 18 "codes": [ 19 "goods.id", 20 "id" 21 ], 22 "arguments": null, 23 "defaultMessage": "id", 24 "code": "id" 25 } 26 ], 27 "defaultMessage": "商品ID不能为空", 28 "objectName": "goods", 29 "field": "id", 30 "rejectedValue": null, 31 "bindingFailure": false, 32 "code": "NotNull" 33 } 34 ], 35 "message": "Validation failed for object='goods'. Error count: 1", 36 "path": "/goods/save" 37 } 38 39 #测试英文 40 curl -X POST -H "Accept-Language:en" "http://localhost:8080/goods/save" 41 #返回: 42 { 43 "timestamp": "2020-05-20T04:16:15.078+0000", 44 "status": 400, 45 "error": "Bad Request", 46 "errors": [ 47 { 48 "codes": [ 49 "NotNull.goods.id", 50 "NotNull.id", 51 "NotNull.java.lang.Integer", 52 "NotNull" 53 ], 54 "arguments": [ 55 { 56 "codes": [ 57 "goods.id", 58 "id" 59 ], 60 "arguments": null, 61 "defaultMessage": "id", 62 "code": "id" 63 } 64 ], 65 "defaultMessage": "Goods id cannot be empty", 66 "objectName": "goods", 67 "field": "id", 68 "rejectedValue": null, 69 "bindingFailure": false, 70 "code": "NotNull" 71 } 72 ], 73 "message": "Validation failed for object='goods'. Error count: 1", 74 "path": "/goods/save" 75 } 76 77 #测试日文 78 curl -X POST -H "Accept-Language:ja" "http://localhost:8080/goods/save" 79 #返回: 80 { 81 "timestamp": "2020-05-20T04:16:35.011+0000", 82 "status": 400, 83 "error": "Bad Request", 84 "errors": [ 85 { 86 "codes": [ 87 "NotNull.goods.id", 88 "NotNull.id", 89 "NotNull.java.lang.Integer", 90 "NotNull" 91 ], 92 "arguments": [ 93 { 94 "codes": [ 95 "goods.id", 96 "id" 97 ], 98 "arguments": null, 99 "defaultMessage": "id", 100 "code": "id" 101 } 102 ], 103 "defaultMessage": "商品IDは空にできません", 104 "objectName": "goods", 105 "field": "id", 106 "rejectedValue": null, 107 "bindingFailure": false, 108 "code": "NotNull" 109 } 110 ], 111 "message": "Validation failed for object='goods'. Error count: 1", 112 "path": "/goods/save" 113 }