Spring Boot中使用JSR-303实现请求参数校验
作者:Miaow.Y.Hu
JSR-303是Java中的一个规范,用于实现请求参数校验。它定义了一组注解,可以应用于JavaBean的字段上,用于验证输入参数的合法性。下面是一些常用的JSR-303注解及其介绍:
@NotNull
:用于验证字段值不能为null。@NotEmpty
:用于验证字符串字段不能为空。@NotBlank
:用于验证字符串字段不能为空,并且长度必须大于0。@Min
:用于验证数字字段的最小值。@Max
:用于验证数字字段的最大值。@Size
:用于验证字符串、集合或数组字段的长度或大小。@Pattern
:用于验证字符串字段是否匹配指定的正则表达式。@Email
:用于验证字符串字段是否符合Email格式。@Range
:用于验证数字字段的取值范围。@Valid
:用于嵌套验证,可以对对象中的字段进行递归验证。
其他如下图所示:
通过在JavaBean的字段上添加这些注解,可以在接收请求参数时进行自动校验。如果校验失败,可以通过异常处理机制来处理校验错误。
需要注意的是,JSR-303只提供了基本的验证注解,如果需要更复杂的校验逻辑,可以自定义注解或使用第三方库,如Hibernate Validator等。
Hibernate Validator附加的constraint:
至于我们为啥使用JSR-303来校验我们的参数,主要是为了解决我们在实际开发过程中,前后端的参数校验导致的问题。比如:
- 我们依靠前端框架解决参数校验,但是缺少服务器的参数校验,这种情况常见于需要同时进行开发前后端的时候,虽然程序的正常使用不会有问题,到那时开发者会忽略非正常的操作,比如绕过前端程序,直接模拟客户端请求,这样就绕过了我们对前端预设的各种限制,直击我们后端的数据访问接口,进而使得我们的后端系统存在严重的安全隐患。
- 大量的利用if/else语句嵌套实现,使得我们的校验逻辑晦涩难懂,并不利于我们对整个系统的维护。
JSR的定义标准
验证触发时机:
- JSR-303校验标准定义了两个触发校验的时机:
- 在对象被持久化之前(例如,保存到数据库之前)。
- 在对象被修改之后(例如,更新数据库中的记录后)。
- 校验约束注解:JSR-303标准提供了一组注解,可以用于对Java对象的属性进行校验。这些注解包括但不限于
@NotNull
、@NotEmpty
、@Min
、@Max
、@Size
、@Pattern
、@Email
等。开发人员可以根据需求选择适当的注解来定义校验规则。 - 校验组:JSR-303允许将校验规则分组,以便在特定情况下选择性地执行校验。开发人员可以为每个校验注解指定一个或多个校验组,然后在校验时选择要执行的校验组。
- 嵌套校验:JSR-303允许对复杂对象进行嵌套校验,即在校验一个对象时,也会对其关联的其他对象进行校验。这样可以确保整个对象图的完整性和合法性。
- 自定义校验:JSR-303还允许开发人员定义自己的校验注解和校验器,以满足特定的校验需求。通过实现 ConstraintValidator 接口来自定义校验器,并在自定义注解中使用该校验器。
- 校验结果:JSR-303校验结果以校验异常的形式返回。当校验失败时,会抛出 ConstraintViolationException 异常,其中包含了校验失败的详细信息,例如校验失败的属性、校验失败的值、校验错误消息等。
接下来我们将围绕我们在Spring Boot的实体类中,使用JSR-303校验。值得注意的是,JSR-303校验我们一般都是对Java的实体类对象进行校验,主要检验在我们的实体类对象的属性上。
还是利用我们==>上一篇 <===的相关依赖文件,这篇我就不在重复导入相关依赖文件了。本篇着重文件主要正在User类和UserController,所用的依赖pom.xml文件和application.properties均与上篇一样,就不在重复描述了。
PS: 你可以看到我在依赖中均采用了lombok,但是我却并未使用这个玩意,原因在于,我使用了lombok
@ApiModel(description = "用户实体") public class User { @ApiModelProperty("用户编号") private Long id; @NotNull //校验定义的字段不能为空 @Size(min = 2, max = 5) @ApiModelProperty("用户姓名") private String name; @NotNull @Max(100) @Min(10) @ApiModelProperty("用户年龄") private Integer age; @NotNull @Email @ApiModelProperty("用户邮箱") private String email; public User(Long id, String name, Integer age, String email) { this.id = id; this.name = name; this.age = age; this.email = email; } public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", email='" + email + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(age, user.age) && Objects.equals(email, user.email); } @Override public int hashCode() { return Objects.hash(id, name, age, email); } }
@Api(tags = "用户管理") @RestController @RequestMapping(value = "/users") // 通过这里配置使下面的映射都在/users下 public class UserController { // 创建线程安全的Map,模拟users信息的存储 static Map<Long, User> users = Collections.synchronizedMap(new HashMap<>()); @GetMapping("/") @ApiOperation(value = "获取用户列表") public List<User> getUserList() { List<User> r = new ArrayList<>(users.values()); return r; } @PostMapping("/") @ApiOperation(value = "创建用户", notes = "根据User对象创建用户") public String postUser(@Valid @RequestBody User user) { users.put(user.getId(), user); return "success"; } @GetMapping("/{id}") @ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息") public User getUser(@PathVariable Long id) { return users.get(id); } @PutMapping("/{id}") @ApiImplicitParam(paramType = "path", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1") @ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息") public String putUser(@PathVariable Long id, @RequestBody User user) { User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @DeleteMapping("/{id}") @ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象") public String deleteUser(@PathVariable Long id) { users.remove(id); return "success"; } }
接下来启动项目,当然启动类还是需要加:@EnableSwagger2Doc
这个注解
之后,我们可以通过使用相关工具比如Postman等测试工具发起,也可以使用curl发起,比如:
curl -X POST \ http://localhost:8080/users/ \ -H 'Content-Type: application/json' \ -H 'Postman-Token: 114db0f0-bdce-4ba5-baf6-01e5104a68a3' \ -H 'cache-control: no-cache' \ -d '{ "name": "abcdefg", "age": 8, "email": "aaaa" }'
得到相关信息:
{ "timestamp": "2019-10-05T06:24:30.518+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "Size.user.name", "Size.name", "Size.java.lang.String", "Size" ], "arguments": [ { "codes": [ "user.name", "name" ], "arguments": null, "defaultMessage": "name", "code": "name" }, 5, 2 ], "defaultMessage": "个数必须在2和5之间", "objectName": "user", "field": "name", "rejectedValue": "abcdefg", "bindingFailure": false, "code": "Size" }, { "codes": [ "Min.user.age", "Min.age", "Min.java.lang.Integer", "Min" ], "arguments": [ { "codes": [ "user.age", "age" ], "arguments": null, "defaultMessage": "age", "code": "age" }, 10 ], "defaultMessage": "最小不能小于10", "objectName": "user", "field": "age", "rejectedValue": 8, "bindingFailure": false, "code": "Min" }, { "codes": [ "Email.user.email", "Email.email", "Email.java.lang.String", "Email" ], "arguments": [ { "codes": [ "user.email", "email" ], "arguments": null, "defaultMessage": "email", "code": "email" }, [], { "defaultMessage": ".*", "codes": [ ".*" ], "arguments": null } ], "defaultMessage": "不是一个合法的电子邮件地址", "objectName": "user", "field": "email", "rejectedValue": "aaaa", "bindingFailure": false, "code": "Email" } ], "message": "Validation failed for object='user'. Error count: 3", "path": "/users/" }
其中返回名称的各参数含义如下:
timestamp
:请求时间status
:HTTP返回的状态码,这里返回400,即:请求无效、错误的请求,通常参数校验不通过均为400error
:HTTP返回的错误描述,这里对应的就是400状态的错误描述:Bad Requesterrors
:具体错误原因,是一个数组类型;因为错误校验可能存在多个字段的错误,比如这里因为定义了两个参数不能为Null,所以存在两条错误记录信息message
:概要错误消息,返回内容中很容易可以知道,这里的错误原因是对user对象的校验失败,其中错误数量为2,而具体的错误信息就定义在上面的errors数组中path
:请求路径
从errors数组中各个错误明细,知道各个字段的defaultMessage,可以看到很清晰的错误描述。
浏览器访问:
http://localhost:8080/swagger-ui.html
我们可以看到校验的部分限制。
到此这篇关于Spring Boot中使用JSR-303实现请求参数校验的文章就介绍到这了,更多相关springboot JSR-303请求参数校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!