【问题标题】:@Valid Hibernate validation in a Spring REST controllerSpring REST 控制器中的@Valid Hibernate 验证
【发布时间】:2014-05-18 00:14:55
【问题描述】:

我正在尝试在带有 @Valid 注释的 Spring REST 控制器中使用休眠验证。

但是验证没有发生。

这是 Maven 构建的内容:

java.lang.AssertionError:预期状态: 但原为:

在尝试发布包含无效字段值的表单时,我应该得到一个错误的请求 http 状态,但表单发布得很好并且资源已创建。

我的验证测试尝试使用缺少的电子邮件值和太短的登录名值进行发布:

@Test
public void testValidation() throws Exception {
    HttpHeaders httpHeaders = Common.createAuthenticationHeaders("stephane" + ":" + PASSWORD);

MvcResult resultPost = this.mockMvc.perform(
        post("/admin").headers(httpHeaders)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON)
        .content("{ \"firstname\" : \"" + admin0.getFirstname() + "\", \"lastname\" : \"" + admin0.getLastname() + "\", \"email\" : \"" + admin0.getEmail() + "\", \"login\" : \"" + admin0.getLogin() + "\", \"password\" : \"" + admin0.getPassword() + "\", \"passwordSalt\" : \"" + admin0.getPasswordSalt() + "\" }")
    ).andDo(print())
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.firstname").value(admin0.getFirstname()))
    .andExpect(jsonPath("$.lastname").value(admin0.getLastname()))
    .andExpect(jsonPath("$.email").value(""))
    .andExpect(jsonPath("$.login").value("short"))
    .andExpect(jsonPath("$.password").value(admin0.getPassword()))
    .andExpect(jsonPath("$.passwordSalt").value(admin0.getPasswordSalt()))
    .andExpect(header().string("Location", Matchers.containsString("/admin/")))
    .andReturn();

}

我还有一个异常处理类:

@ControllerAdvice
public class AdminExceptionHandler {

    @ExceptionHandler(AdminNotFoundException.class)
    @ResponseBody
    public ResponseEntity<ErrorInfo> adminNotFoundException(HttpServletRequest request, AdminNotFoundException e) {
        String url = request.getRequestURL().toString();
        String errorMessage = localizeErrorMessage("error.admin.not.found", new Object[] { e.getAdminId() });
        return new ResponseEntity<ErrorInfo>(new ErrorInfo(url, errorMessage), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ResponseEntity<ErrorFormInfo> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
        String url = request.getRequestURL().toString();
        String errorMessage = localizeErrorMessage("error.admin.invalid.form.argument");
        ErrorFormInfo errorFormInfo = new ErrorFormInfo(url, errorMessage);
        BindingResult result = e.getBindingResult();
        List<FieldError> fieldErrors = result.getFieldErrors();
        errorFormInfo.getFieldErrors().addAll(populateFieldErrors(fieldErrors));
        return new ResponseEntity<ErrorFormInfo>(errorFormInfo, HttpStatus.BAD_REQUEST);
    }

    public List<ErrorFormField> populateFieldErrors(List<FieldError> fieldErrorList) {
        List<ErrorFormField> errorFormFields = new ArrayList<ErrorFormField>();
        StringBuilder errorMessage = new StringBuilder("");
        for (FieldError fieldError : fieldErrorList) {
            errorMessage.append(fieldError.getCode()).append(".");
            errorMessage.append(fieldError.getObjectName()).append(".");
            errorMessage.append(fieldError.getField());
            errorFormFields.add(new ErrorFormField(fieldError.getField(), localizeErrorMessage(errorMessage.toString())));
            errorMessage.delete(0, errorMessage.capacity());
        }
        return errorFormFields;
    }

    private String localizeErrorMessage(String errorCode) {
        return localizeErrorMessage(errorCode, null);
    }

    private String localizeErrorMessage(String errorCode, Object args[]) {
        Locale locale = LocaleContextHolder.getLocale();
        String errorMessage = messageSource.getMessage(errorCode, args, locale);
        return errorMessage;
    }

}

请注意,AdminNotFoundException 可以由其他一些测试很好地触发。

但是没有触发 MethodArgumentNotValidException。

这是控制器类:

@Controller
@ExposesResourceFor(Admin.class)
@RequestMapping("/admin")
public class AdminController {

    @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<Admin> add(@RequestBody @Valid Admin admin, UriComponentsBuilder builder) {
        AdminCreatedEvent adminCreatedEvent = adminService.add(new CreateAdminEvent(admin.toEventAdmin()));
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setLocation(builder.path("/admin/{id}").buildAndExpand(adminCreatedEvent.getAdminId()).toUri());
        Admin createdAdmin = adminResourceAssembler.toResource(adminCreatedEvent.getEventAdmin());
        ResponseEntity<Admin> responseEntity = new ResponseEntity<Admin>(createdAdmin, responseHeaders, HttpStatus.CREATED);
        return responseEntity;
    }

}

依赖的版本是 Spring 的 3.2.5.RELEASE 和 hibernate-validator 的 5.1.1.Final 和 javax.el-api 的 3.0.0

在 Tomcat 7 服务器上手动运行并发出 curl post 请求时会出现同样的问题:

mvn clean install tomcat7:run -DskipTests

curl -H "Accept:application/json" --user joethebouncer:mignet http://localhost:8080/learnintouch-rest/admin -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d "{ \"firstname\" : \"Stephane\", \"lastname\" : \"Eybert\", \"email\" : \"\", \"login\" : \"short\", \"password\" : \"mignet\", \"passwordSalt\" : \"7bc7bf5f94fef7c7106afe5c3a40a2\" }"

有什么线索吗?

以下是完整的源代码,以防有人想尝试运行这个 Maven 构建: http://www.learnintouch.com/learnintouch-data.tar.gz http://www.learnintouch.com/learnintouch-rest.tar.gz

亲切的问候,

斯蒂芬·艾伯特

【问题讨论】:

  • 为什么控制器和控制器建议中都有initBinder?当你把它放在两者之一时会发生什么?
  • 好点,我刚刚从控制器中删除了一个。我在四处搜索后添加了它,但是是的,它太多了。删除它后,问题仍然完全相同。
  • 另外,在我的异常处理程序类中,我注入了一个 adminValidator 但从不使用它。并且绑定不是在注入的那个上完成的。那里很乱......在我从这个类中删除这个 adminValidator 后,问题仍然完全相同。
  • 不幸的是,我不知道为什么它没有按预期工作......
  • 我看到一些人在做一个明确的覆盖,而不是我尝试在stackoverflow.com/questions/16651160/… 使用@ExceptionHandler(MethodArgumentNotValidException.class),这让我想知道我尝试做的是否合法。

标签: spring validation rest


【解决方案1】:

您是否尝试过使用 Hibernate Validator 4.2.0.Final? Hibernate Validator 5 是 bean 验证 API 1.1 (JSR 349) 的参考实现,它与 JSR 303 的规范不同。

我假设 Spring Framework 3.2 仅支持 JSR 303,因为我无法从其参考手册中找到有关 bean 验证 API 1.1 的任何信息。

【讨论】:

  • @Petri 感谢您的评论!确实,我以前曾尝试过。所以我在你说完之后又回到了它。但是使用 hibernate-validator 4.2.0.Final 和 validation-api 1.1.0.Final 和 javax.el-api 3.0.0 会产生完全相同的问题。
【解决方案2】:

我尝试了您的代码,按照您提到的相同步骤操作,并且验证确实正确发生。

这是我使用与您发布的完全相同的 curl 命令得到的回复:

{"url":"http://localhost:8080/learnintouch-rest/admin","message":"The admin form arguments were invalid.","fieldErrors":[{"fieldName":"login","fieldEr ror":"Length.admin.login"},{"fieldName":"email","fieldError":"NotEmpty.admin.email"}]}

我建议您尝试清除 maven 存储库并从头开始构建项目。

我还应该提到,在您的 testValidation() 集成测试中,您使用的 admin0 对象实际上是有效的,因此它通过验证是有意义的 :)

【讨论】:

  • 谢谢。确实,我的测试是错误的。我将空电子邮件和短登录名的虚拟值放在响应的期望部分中,而不是在请求中发送的管理员中。当然,它确实验证了。蒙蔽我!我什至不必清除存储库。修复测试后构建工作正常,例如: .content("{ \"firstname\" : \"" + admin0.getFirstname() + "\", \"lastname\" : \"" + admin0.getLastname() + "\", \"email\" : \"\", \"login\" : \"short\", \"password\" : \"" + admin0.getPassword() + "\", \" passwordSalt\" : \"" + admin0.getPasswordSalt() + "\" }")
  • 当我美化代码并将 Admin 资源重命名为 AdminResource 资源时,这里有些东西咬了我。果然我忘记在属性文件中重命名 Length.admin.login 消息属性,以便将其命名为 Length.adminResource.login。我想知道是否有一种方法可以在代码中自定义该名称,而不是使用骆驼大小写的资源名称。
猜你喜欢
  • 2016-10-17
  • 2015-05-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 1970-01-01
  • 2018-06-23
  • 2016-04-28
  • 1970-01-01
相关资源
最近更新 更多