Spring MVC Bean Validator

2016/01/01 Java

介绍

首先,需明确的Bean Validation是一个JAVA规范(JSR-303/JSR-349 Validator),并不是专属于Spring的一个概念,Spring MVC只是让该规范可以更加友好的运用到Spring MVC应用中。只要你把Bean Validation Provider的jar包添加到classpath下,Spring就可以自动的侦测到,并把Bean Validator机制应用到所有的Controller中。 目前常用的Bean Validator是Hibernate Validator。栗子:

假设我们又一个服务叫做Propagate, 是一个操作, Request是一个AuditDeatail对象,其中包含userId,dateTime两个Property,即谁在什么时候做了这个操作。但是,应用的限制条件是userId为一个小于10位的字符串,dateTime的格式必须为“yyyy-MM-dd HH:mm:ss”,并且不能是未来的时间。如果验证失败则返回对应的错误信息。

@Test
    public void testPropagateWithIllegalUserId() throws Exception {
        mockMvc.perform(post("/propagate")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .content("{\"userId\":\"xianlinbox2\",\"dateTime\":\"2013-12-25 10:10:00\"}")
        )
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("001"))
                .andExpect(jsonPath("$.message").value("Invalid Request:\nuserId must less than 10 digits\n"));
    }

    @Test
    public void testPropagateWithFutureDateTime() throws Exception {
        mockMvc.perform(post("/propagate")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .content("{\"userId\":\"xianlinbox\",\"dateTime\":\"2014-12-25 10:10:00\"}")
        )
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("001"))
                .andExpect(jsonPath("$.message").value("Invalid Request:\nDateTime should not in future\n"));
    }


    @Test
    public void testPropagateWithWrongFormatDateTime() throws Exception {
        mockMvc.perform(post("/propagate")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .content("{\"userId\":\"xianlinbox\",\"dateTime\":\"2014-12-25\"}")
        )
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("001"))
                .andExpect(jsonPath("$.message").value("Invalid format: \"2014-12-25\" is too short"));
    }

    @Test
    public void testPropagateWithBothIllegalUserIdAndIllegalDateTime() throws Exception {
        mockMvc.perform(post("/propagate")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .content("{\"userId\":\"xianlinbox2\",\"dateTime\":\"2014-12-25 10:10:00\"}")
        )
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("001"))
                .andExpect(jsonPath("$.message").value(containsString("DateTime should not in future")))
                .andExpect(jsonPath("$.message").value(containsString("userId must less than 10 digits")));
    }

实现代码

首先,把Hibernate Validator相关的依赖包添加到classpath下:

  • org.hibernate:hibernate-validator:5.0.2.Final
  • javax.el:javax.el-api:3.0.0
  • org.glassfish.web:el-impl:2.2

然后,添加服务,在需要Bean Validator的参数上添加@Valid注解

@RequestMapping(value = "/propagate", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.ACCEPTED)
public AuditDetail propagate(@Valid @RequestBody AuditDetail operationBy) {
    return operationBy;
}

接着,创建Bean,并为property添加想要的限制条件,其中的CustomDateSerializer,CustomDateSerializer类是辅助json序列化的。

public class AuditDetail {

    @NotNull
    @Length(max = 10, message = "userId must less than 10 digits")
    private String userId;

    @Past(message = "DateTime should not in future")
    private DateTime dateTime;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @JsonSerialize(using = CustomDateSerializer.class)
    public DateTime getDateTime() {
        return dateTime;
    }

    @JsonDeserialize(using = CustomDateSerializer.class)
    public void setDateTime(DateTime dateTime) {
        this.dateTime = dateTime;
    }
}

public class CustomDateSerializer extends JsonSerializer<DateTime> {
    @Override
    public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonProcessingException {
       jgen.writeString(value.toString("yyyy-MM-dd HH:mm:ss"));
    }
}

public class CustomDateDeserializer extends JsonDeserializer<DateTime> {
    @Override
    public DateTime deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return DateTime.parse(jp.getText(), DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
    }
}

经过上面的步骤,应用已经具备了验证Property的功能,但是,验证失败了我们如何捕获失败,并生成响应的异常呢? 同样的,需要通过异常处理机制来做这个工作,在SpringMVC中,当Json序列化,反序列化失败的时候,会抛出HttpMessageNotReadableException异常, 当Bean validation失败的时候,会抛出MethodArgumentNotValidException异常,因此,只需要在ExceptionHandler类中添加处理对应异常的方法即可。

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        String errorMesssage = "Invalid Request:\n";

        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            errorMesssage += fieldError.getDefaultMessage() + "\n";
        }
        return new ErrorResponse("001", errorMesssage);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
        return new ErrorResponse("001", ex.getRootCause().getMessage());
    }

参考资料:

  1. Hibernate Validator 验证规则

Search

    Table of Contents