Spring Controller中无法初始化Errors接口的参数验证问题
我在项目里做参数验证时碰到了个麻烦:Controller接收String类型的XML参数,先校验非空后用JAXB转成ApiPubPortalPublicarPortal对象,接着调用parsingPublicacion方法做参数验证。但Errors是个接口没法直接初始化,而且Controller只能用指定参数,不能直接传Errors实例进去。相关代码如下:
控制器代码
@RequestMapping(value = "/publicar", method = { RequestMethod.GET, RequestMethod.POST }, produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody public ApiPubPortalPublicarOut publicar(@RequestParam(value = PARAM_XML) String xml, Locale locale) { // 这是返回对象,用来生成带验证信息的XML ApiPubPortalPublicarOut xmlTaxOut = new ApiPubPortalPublicarOut(); Errors result = null; // 这里出问题了,没法初始化 // 校验传入的XML是否为空 if ((xml == null) || (xml.length() == 0)) { xmlTaxOut.setDescription("xml is Empty!"); return xmlTaxOut; } else { try { // 把XML转成对象 JAXBContext jc = JAXBContext.newInstance(ApiPubPortalPublicarPortal.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); StreamSource streamSource = new StreamSource(new StringReader(xml)); JAXBElement<ApiPubPortalPublicarPortal> je = unmarshaller.unmarshal(streamSource, ApiPubPortalPublicarPortal.class); // 调用验证方法 parsingPublicacion(je.getValue(), result, locale); if(result.hasErrors()){ xmlTaxOut.setDescription(result.getAllErrors().toString()); return xmlTaxOut; } } catch(Exception e){ xmlTaxOut.setDescription("Error parsing!"); return xmlTaxOut; } } }
验证方法代码
private void parsingPublicacion(ApiPubPortalPublicarPortal portalPublicado, Errors e, Locale locale) { ApiPubPortalPublicarPortal pubPortal = portalPublicado; ValidationUtils.rejectIfEmptyOrWhitespace(e, "name", "name.empty"); if (pubPortal.getNombre().length() > 50){ e.rejectValue("name", "name.oversize"); } ValidationUtils.rejectIfEmptyOrWhitespace(e, "idLanguage.empty"); if ((pubPortal.getIdPortal() == 0)){ e.rejectValue("idLanguage", "idLanguage.zero"); } }
请问该怎么解决这个验证问题?
嘿,这个问题我之前做Spring XML参数验证时也碰到过!核心原因是Errors是个接口,不能直接new,而Spring MVC通常是在自动绑定请求参数的时候帮你创建好实例的,但你这里是手动用JAXB转的对象,所以得自己初始化一个Errors的实现类。推荐用Spring官方提供的BeanPropertyBindingResult,它就是专门用来处理Bean验证错误的默认实现。
下面给你一步步拆解修改方案:
1. 替换Errors的实例化方式
你原来的代码里Errors result = null;肯定会空指针,得在拿到JAXB转换后的对象之后,创建BeanPropertyBindingResult实例——它需要传入要验证的对象和一个标识名称(名称随便取,用来标记错误所属的Bean):
// 先拿到转换后的对象 ApiPubPortalPublicarPortal portal = je.getValue(); // 初始化Errors实例 Errors result = new BeanPropertyBindingResult(portal, "portalPublicado");
2. 调整Controller里的代码顺序
把原来的代码顺序调整一下,确保先完成XML到对象的转换,再初始化Errors,避免空指针:
try { // 1. XML转对象 JAXBContext jc = JAXBContext.newInstance(ApiPubPortalPublicarPortal.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); StreamSource streamSource = new StreamSource(new StringReader(xml)); JAXBElement<ApiPubPortalPublicarPortal> je = unmarshaller.unmarshal(streamSource, ApiPubPortalPublicarPortal.class); ApiPubPortalPublicarPortal portal = je.getValue(); // 2. 初始化Errors实例 Errors result = new BeanPropertyBindingResult(portal, "portalPublicado"); // 3. 调用验证方法 parsingPublicacion(portal, result, locale); // 4. 处理验证错误 if(result.hasErrors()){ // 这里可以优化错误信息的展示,比如遍历所有错误拼接 StringBuilder errorMsg = new StringBuilder(); for(ObjectError error : result.getAllErrors()){ errorMsg.append(error.getDefaultMessage()).append("; "); } xmlTaxOut.setDescription(errorMsg.toString()); return xmlTaxOut; } } catch(Exception e){ xmlTaxOut.setDescription("Error parsing!"); return xmlTaxOut; }
3. 修复验证方法里的小bug
看你的parsingPublicacion方法里有个明显的错误:ValidationUtils.rejectIfEmptyOrWhitespace(e, "idLanguage.empty");这里第二个参数应该是Bean的属性名称(比如idLanguage),而不是消息码!还有后面的idPortal验证也对应错了属性名,修正后的代码:
private void parsingPublicacion(ApiPubPortalPublicarPortal portalPublicado, Errors e, Locale locale) { ApiPubPortalPublicarPortal pubPortal = portalPublicado; // 注意:第二个参数是Bean里的属性名,比如你的Bean里是nombre,就写nombre ValidationUtils.rejectIfEmptyOrWhitespace(e, "nombre", "name.empty"); if (pubPortal.getNombre() != null && pubPortal.getNombre().length() > 50){ e.rejectValue("nombre", "name.oversize"); } // 修正第二个参数为属性名idLanguage ValidationUtils.rejectIfEmptyOrWhitespace(e, "idLanguage", "idLanguage.empty"); // 这里你原来判断的是getIdPortal,所以属性名应该是idPortal if (pubPortal.getIdPortal() == 0){ e.rejectValue("idPortal", "idLanguage.zero"); } }
这里要特别注意:ValidationUtils.rejectIfEmptyOrWhitespace的第二个参数必须和ApiPubPortalPublicarPortal类里的字段名完全一致,不然Spring找不到对应的属性,错误信息也不会正确生成。
4. 可选:用Spring Validator接口规范验证逻辑
如果后续你的验证逻辑越来越复杂,建议把验证代码抽离成一个实现Validator接口的类,这样代码更清晰,也方便复用:
public class PortalPublicadoValidator implements Validator { @Override public boolean supports(Class<?> clazz) { // 声明这个Validator支持验证ApiPubPortalPublicarPortal类型 return ApiPubPortalPublicarPortal.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { ApiPubPortalPublicarPortal pubPortal = (ApiPubPortalPublicarPortal) target; ValidationUtils.rejectIfEmptyOrWhitespace(errors, "nombre", "name.empty"); if (pubPortal.getNombre() != null && pubPortal.getNombre().length() > 50){ errors.rejectValue("nombre", "name.oversize"); } ValidationUtils.rejectIfEmptyOrWhitespace(errors, "idLanguage", "idLanguage.empty"); if (pubPortal.getIdPortal() == 0){ errors.rejectValue("idPortal", "idLanguage.zero"); } } }
然后在Controller里这样用:
// 创建Validator实例 PortalPublicadoValidator validator = new PortalPublicadoValidator(); // 执行验证 validator.validate(portal, result);
这种方式更符合Spring的设计理念,也便于后续扩展验证规则。
内容的提问来源于stack exchange,提问作者Kordh Rufan

