1. 概述
本文介绍 Spring Data REST 验证器的基础知识。如果需要先了解 Spring Data REST 的基本概念,建议先阅读这篇入门文章。
简单来说,Spring Data REST 允许我们通过 REST API 直接向数据库添加新数据,但在实际持久化前必须确保数据有效性。
本文基于现有项目继续开发,将复用已搭建的项目结构。
如果准备快速上手 Spring Data REST,以下是高效入门方案:
2. 使用验证器
自 Spring 3 起,框架提供了 Validator
接口用于对象验证。
2.1. 验证动机
在之前的文章中,我们定义了包含 name
和 email
两个属性的实体类。
创建新资源只需执行:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "name" : "Test", "email" : "test@example.com" }'
http://localhost:8080/users
此 POST 请求会将 JSON 对象存入数据库,返回结果:
{
"name" : "Test",
"email" : "test@example.com",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}
由于提供了有效数据,结果符合预期。但如果移除 name
属性或将其设为空字符串会怎样?
测试第一种场景,将 name
设为空字符串:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users
响应结果:
{
"name" : "",
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}
测试第二种场景,移除 name
属性:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "email" : "Baggins" }' http://localhost:8080/users
响应结果:
{
"name" : null,
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/2"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/2"
}
}
}
❌ 两个请求都成功返回了 201 状态码和对象链接。这种无法阻止不完整数据入库的行为显然不可接受。
2.2. Spring Data REST 事件
每次调用 Spring Data REST 接口时,导出器会生成以下事件:
-
BeforeCreateEvent
-
AfterCreateEvent
-
BeforeSaveEvent
-
AfterSaveEvent
-
BeforeLinkSaveEvent
-
AfterLinkSaveEvent
-
BeforeDeleteEvent
-
AfterDeleteEvent
所有事件处理方式类似,这里仅以 beforeCreateEvent
为例(对象入库前触发)。
2.3. 定义验证器
自定义验证器需实现 org.springframework.validation.Validator
接口:
public class WebsiteUserValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return WebsiteUser.class.equals(clazz);
}
@Override
public void validate(Object obj, Errors errors) {
WebsiteUser user = (WebsiteUser) obj;
if (checkInputString(user.getName())) {
errors.rejectValue("name", "name.empty");
}
if (checkInputString(user.getEmail())) {
errors.rejectValue("email", "email.empty");
}
}
private boolean checkInputString(String input) {
return (input == null || input.trim().length() == 0);
}
}
Errors
对象用于收集所有验证错误。通过 errors.rejectValue(字段名, 错误消息)
添加错误。
定义验证器后,需将其映射到特定事件。有三种注册方式:
✅ 方式一:使用 @Component
注解
@Component("beforeCreateWebsiteUserValidator")
public class WebsiteUserValidator implements Validator {
...
}
Spring Boot 会自动识别 beforeCreate
前缀和 WebsiteUser
类名。
✅ 方式二:使用 @Bean
注解
@Bean
public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
return new WebsiteUserValidator();
}
✅ 方式三:手动注册
@SpringBootApplication
public class SpringDataRestApplication implements RepositoryRestConfigurer {
public static void main(String[] args) {
SpringApplication.run(SpringDataRestApplication.class, args);
}
@Override
public void configureValidatingRepositoryEventListener(
ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new WebsiteUserValidator());
}
}
此方式无需在验证器类添加注解。
2.4. 事件发现 Bug
⚠️ 当前 Spring Data REST 存在事件发现 Bug,可能导致验证器未被触发。
临时解决方案:手动注册所有事件到 ValidatingRepositoryEventListener
:
@Configuration
public class ValidatorEventRegister implements InitializingBean {
@Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
@Autowired
private Map<String, Validator> validators;
@Override
public void afterPropertiesSet() throws Exception {
List<String> events = Arrays.asList("beforeCreate");
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
events.stream()
.filter(p -> entry.getKey().startsWith(p))
.findFirst()
.ifPresent(
p -> validatingRepositoryEventListener
.addValidator(p, entry.getValue()));
}
}
}
3. 测试验证
在 2.1 节中我们看到,没有验证器时缺失 name
的对象也能入库。现在添加验证器后测试:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "email" : "another@example.com" }' http://localhost:8080/users
响应变为:
{
"timestamp":1472510818701,
"status":406,
"error":"Not Acceptable",
"exception":"org.springframework.data.rest.core.
RepositoryConstraintViolationException",
"message":"Validation failed",
"path":"/users"
}
✅ 缺失数据被成功拦截,对象未入库。但错误信息不够友好,需自定义异常处理:
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
ResponseEntityExceptionHandler {
@ExceptionHandler({ RepositoryConstraintViolationException.class })
public ResponseEntity<Object> handleAccessDeniedException(
Exception ex, WebRequest request) {
RepositoryConstraintViolationException nevEx =
(RepositoryConstraintViolationException) ex;
String errors = nevEx.getErrors().getAllErrors().stream()
.map(p -> p.toString()).collect(Collectors.joining("\n"));
return new ResponseEntity<Object>(errors, new HttpHeaders(),
HttpStatus.PARTIAL_CONTENT);
}
}
现在响应会包含具体错误信息,便于调试。
4. 总结
本文验证了验证器在 Spring Data REST 中的必要性,它为数据插入提供了关键的安全保障。通过注解方式创建验证器简单高效,能有效防止脏数据入库。
完整代码示例可在 GitHub 项目 中获取。