You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Controller中无法初始化Errors接口的参数验证问题

问题:Spring Controller中XML参数转换后的验证难题

我在项目里做参数验证时碰到了个麻烦: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:06:33