本文接上一篇《SpringMVC 使用验证框架 Bean Validation(上)》:
四、Controller 普通参数验证与视图错误信息的展示
对于 form 表单提交绑定到对象的验证方式,上面已经介绍了。但是在很多时候,我们是通过普通传参来调用接口的。
比如:http://localhost:8080/myproject/hello?name=Shanhy&age=27&password=pwd
那么对于这种情况,我们该如何校验 name、age、password 的值呢?
看下面的 Controller 代码:
五、JSON 请求响应错误信息
package org.springboot.sample.interceptor;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* JSON请求响应错误消息处理
*
* @author 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年4月17日
*/
public class JsonErrorMsgInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if(modelAndView == null)
return;
// 因为MappingJackson2JsonView默认会把BindingResult全部过滤掉。
// 所以我们要想将错误消息输出,要在这里自己处理好。
// 判断请求是否是.json、方法上是否有@ResponseBody注解,或者类上面是否有@RestController注解
// 表示为json请求
if (!request.getRequestURI().endsWith(".json")) {
HandlerMethod handlerMethod = (HandlerMethod)handler;
if(handlerMethod.getMethodAnnotation(ResponseBody.class) == null){
if(handlerMethod.getBeanType().getAnnotation(RestController.class) == null){
return;
}
}
}
Map<String, Object> modelMap = modelAndView.getModel();
if (modelMap != null) {
Map<String, String> errorMsg = null;
if(modelMap.containsKey("errorMsg")){
errorMsg = (Map<String, String>)modelMap.get("errorMsg");
}
if(errorMsg == null){
errorMsg = new HashMap<>();
modelMap.put("errorMsg", errorMsg);
}
for (Entry<String, Object> entry : modelMap.entrySet()) {
if (entry.getValue() instanceof BindingResult) {
BindingResult bindingResult = (BindingResult) entry.getValue();
if (bindingResult.hasErrors()) {
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMsg.put(fieldError.getObjectName() + "." + fieldError.getField(),
fieldError.getDefaultMessage());
}
}
}
}
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
注意配置该拦截器配置,使其生效。
效果:
六、错误信息的配置文件
默认在 ValidationMessages.properties 配置文件中(参考 《SpringMVC 使用验证框架 Bean Validation(上)》中的第二点有样例)。
如果想统一使用 messaeg.properties 配置文件中的配置,下面的配置提供参考:
<!-- 指定自己定义的validator -->
<mvc:annotation-driven validator="validator"/>
<!-- 以下 validator ConversionService 在使用 mvc:annotation-driven 会 自动注册-->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找 -->
<value>classpath:messages</value>
<value>classpath:org/hibernate/validator/ValidationMessages</value>
</list>
</property>
<property name="useCodeAsDefaultMessage" value="false"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="cacheSeconds" value="60"/>
</bean>
七、错误信息中使用EL表达式
请参考 《SpringMVC 使用验证框架 Bean Validation(上)》中的第二点。
八、方法级别的验证
首先注册 MethodValidationPostProcessor
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<!-- 可以引用自己的 validator 配置,在本文中(下面)可以找到 validator 的参考配置,如果不指定则系统使用默认的 -->
<property name="validator" ref="validator"/>
</bean>
某个 Service 的方法:
@Validated // 告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持
@Service
public class ValidatorTestService {
public @Length(min = 12, max = 16, message = "返回值长度应该为12-16")
String getContent(
@NotBlank(message = "name不能为空")
String name,
@Size(min = 5, max = 10, message="{password.length.illegal}")
String password) {
return name + ":" + password;
}
}
在 Controller 调用该方法测试:
/**
* 测试方法级别的验证(如果验证失败,则会抛出异常 ConstraintViolationException)
*
* @param name
* @param model
* @return
* @author SHANHY
* @create 2016年4月17日
*/
@RequestMapping("/test5")
@ResponseBody
public Model test5(String name, String password, Model model){
try {
String content = validatorTestService.getContent(name, password);
model.addAttribute("name", content);
} catch (ConstraintViolationException e) {
addErrorMessage(model, e);
}
return model;
}
/**
* 添加错误消息,建议将该方法提取为一个公共的方法使用。
*
* @param model
* @param e
* @author SHANHY
* @create 2016年5月4日
*/
protected void addErrorMessage(Model model, ConstraintViolationException e){
Map<String, String> errorMsg = new HashMap<>();
model.addAttribute("errorMsg", errorMsg);
for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
// 获得验证失败的类 constraintViolation.getLeafBean()
// 获得验证失败的值 constraintViolation.getInvalidValue()
// 获取参数值 constraintViolation.getExecutableParameters()
// 获得返回值 constraintViolation.getExecutableReturnValue()
errorMsg.put(constraintViolation.getLeafBean().getClass().getName() + "-" + constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
}
}
工具类
最后提供一个校验工具类,注意看第一个方法就行了:
/**
* Copyright (c) 2005-2012 springside.org.cn
*/
package org.springboot.sample.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
/**
* JSR303 Validator(Hibernate Validator)工具类.
*
* ConstraintViolation中包含propertyPath, message 和invalidValue等信息.
* 提供了各种convert方法,适合不同的i18n需求:
* 1. List<String>, String内容为message
* 2. List<String>, String内容为propertyPath + separator + message
* 3. Map<propertyPath, message>
*
* 详情见wiki: https://github.com/springside/springside4/wiki/HibernateValidator
*/
public class BeanValidatorUtils {
/**
* 调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException.
*
* 参数 Validator 可以直接注入,如:
*
* @Autowired
* protected Validator validator;
*
*/
public static void validateWithException(Validator validator, Object object, Class<?>... groups)
throws ConstraintViolationException {
Set<? extends ConstraintViolation<?>> constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
throw new ConstraintViolationException(constraintViolations);
// 调用处捕获异常后,获取错误信息的方法
// List<String> list = BeanValidatorUtils.extractPropertyAndMessageAsList(ex, ": ");
}
}
/**
* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>中为List<message>.
*/
public static List<String> extractMessage(ConstraintViolationException e) {
return extractMessage(e.getConstraintViolations());
}
/**
* 辅助方法, 转换Set<ConstraintViolation>为List<message>
*/
@SuppressWarnings("rawtypes")
public static List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {
List<String> errorMessages = new ArrayList<String>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getMessage());
}
return errorMessages;
}
/**
* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为Map<property, message>.
*/
public static Map<String, String> extractPropertyAndMessage(ConstraintViolationException e) {
return extractPropertyAndMessage(e.getConstraintViolations());
}
/**
* 辅助方法, 转换Set<ConstraintViolation>为Map<property, message>.
*/
@SuppressWarnings("rawtypes")
public static Map<String, String> extractPropertyAndMessage(Set<? extends ConstraintViolation> constraintViolations) {
Map<String, String> errorMessages = new HashMap<String, String>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.put(violation.getPropertyPath().toString(), violation.getMessage());
}
return errorMessages;
}
/**
* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath message>.
*/
public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");
}
/**
* 辅助方法, 转换Set<ConstraintViolations>为List<propertyPath message>.
*/
@SuppressWarnings("rawtypes")
public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations) {
return extractPropertyAndMessageAsList(constraintViolations, " ");
}
/**
* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath +separator+ message>.
*/
public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
}
/**
* 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+ message>.
*/
@SuppressWarnings("rawtypes")
public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations,
String separator) {
List<String> errorMessages = new ArrayList<String>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
}
return errorMessages;
}
}