Spring验证不断验证错误的参数

dre*_*kka 1 spring bean-validation

我有一个带有web方法的控制器,如下所示:

public Response registerDevice(
    @Valid final Device device, 
    @RequestBody final Tokens tokens
) {...}
Run Code Online (Sandbox Code Playgroud)

一个看起来像这样的验证器:

public class DeviceValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return Device.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        // Do magic
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我试图让Spring验证由拦截器生成的Device参数.但每次我尝试时,它都会验证令牌参数.

我已经尝试使用@InitBinder指定验证器,@Validated而不是@Valid注册MethodValidationPostProcessor类.到目前为止没有运气.

根本不调用验证器,或者在验证Device参数时验证tokens参数.

我正在使用Spring 4.1.6和Hibernate验证器5.1.3.

谁能提供任何关于我做错的线索?我整个下午都在网上试图解决这个问题.不敢相信春天的验证区域仍然像5年前一样混乱:-(

dre*_*kka 6

好.经过两天的各种变化后,现在已经解决了这个问题.如果Spring的验证有一件事让你做 - 它会产生一系列令人难以置信的东西!但回到我的解决方案.

基本上我需要的是一种手动创建请求映射参数,验证它们然后确保无论是成功还是失败的方法,调用者总是收到自定义JSON响应.这样做比我想象的要困难得多,因为尽管博客文章和stackoverflow答案数量很多,但我从未找到完整的解决方案.因此,我一直在努力概述实现我想要的每一个难题.

注意:在下面的代码示例中,我概括了事物的名称,以帮助澄清什么是自定义,而不是.

组态

虽然我读过的几篇博客文章谈到了各种类,比如说MethodValidationPostProcessor,最后我发现除了@EnableWebMvc注释之外我不需要任何设置.默认的解析器等证明是我需要的.

请求映射

我的最终请求映射签名如下所示:

@RequestMapping(...)
public MyMsgObject handleRequest (
    @Valid final MyHeaderObj myHeaderObj, 
    @RequestBody final MyRequestPayload myRequestPayload
    ) {...}
Run Code Online (Sandbox Code Playgroud)

你会在这里注意到,与我发现的每篇博文和样本不同,我有两个对象被传递给该方法.第一个是我想从头部动态生成的对象.第二个是来自JSON有效负载的反序列化对象.其他对象可以很容易地被包括在内,例如路径参数等.如果没有下面的代码,请尝试这样的事情,你会得到各种奇怪和奇妙的错误.

引起我痛苦的棘手部分是我想要验证myHeaderObj实例,而不是验证myRequestPayload实例.这引起了相当头疼的问题.

还要注意MyMsgObject结果对象.在这里,我想返回一个将被序列化为JSON的对象.包括何时发生异常,因为此类包含除HttpStatus代码之外还需要填充的错误字段.

控制器建议

接下来,我创建了一个ControllerAdvice包含验证绑定和一般错误陷阱的类.

@ControllerAdvice
public class MyControllerAdvice {

    @Autowired
    private MyCustomValidator customValidator;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        if (binder.getTarget() == null) {
            // Plain arguments have a null target.
            return;
        }
        if (MyHeaderObj.class.isAssignableFrom(binder.getTarget().getClass())) {
            binder.addValidators(this.customValidator);
        }
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public MyMsgObject handleException(Exception e) {
        MyMsgObject myMsgObject = new MyMsgObject();
        myMsgObject.setStatus(MyStatus.Failure);
        myMsgObject.setMessage(e.getMessage());
        return myMsgObject;
    }
}
Run Code Online (Sandbox Code Playgroud)

这里发生了两件事.第一个是注册验证器.请注意,我们必须检查参数的类型.这是因为@InitBinder为每个参数调用@RequestMapping,我们只需要MyHeaderObj参数上的验证器.如果我们不这样做,当Spring尝试将验证器应用于它无效的参数时,将抛出异常.

第二件事是异常处理程序.我们必须使用它@ResponseBody来确保Spring将返回的对象视为要序列化的东西.否则我们将获得标准的HTML异常报告.

验证器

这里我们使用非常标准的验证器实现.

@Component
public class MyCustomValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return MyHeaderObj.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        ...
        errors.rejectValue("fieldName", "ErrorCode", "Invalid ..."); 
    }
}
Run Code Online (Sandbox Code Playgroud)

我仍然没有真正得到的一件事是supports(Class<?> clazz)方法.我原以为Spring会使用这个方法测试参数来决定是否应该应用这个验证器.但事实并非如此.因此,所有代码@InitBinder都决定何时应用此验证器.

参数处理程序

这是最大的代码.这里我们需要生成MyHeaderObj要传递给的对象@RequestMapping.Spring会自动检测这个类.

public class MyHeaderObjArgumentHandler implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return MyHeaderObj.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(
        MethodParameter parameter, 
        ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, 
        WebDataBinderFactory binderFactory) throws Exception {

        // Code to generate the instance of MyHeaderObj!
        MyHeaderObj myHeaderObj = ...;

        // Call validators if the argument has validation annotations.
        WebDataBinder binder = binderFactory.createBinder(webRequest, myHeaderObj, parameter.getParameterName());
        this.validateIfApplicable(binder, parameter);
        if (binder.getBindingResult().hasErrors()) {
            throw new MyCustomException(myHeaderObj);
        }
        return myHeaderObj;
    }

    protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
        Annotation[] annotations = methodParam.getParameterAnnotations();
        for (Annotation ann : annotations) {
            Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
            if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
                Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
                Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] { hints });
                binder.validate(validationHints);
                break;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这个类的主要工作是使用构建参数(myHeaderObj)所需的任何方法.一旦构建它然后继续调用Spring验证器来检查这个实例.如果出现问题(通过检查返回的错误检测到),则会抛出一个@ExceptionHandler可以检测和处理的异常.

注意validateIfApplicable(WebDataBinder binder, MethodParameter methodParam)方法.这是我在许多Spring的类中找到的代码.它的工作是检测是否有任何参数具有@Validated@Valid注释,如果有,则调用相关的验证器.默认情况下,Spring不会为这样的自定义参数处理程序执行此操作,因此我们可以添加此功能.认真的春天???? 没有AbstractSomething ????

最后一篇,明确的Exception捕获

最后,我还需要捕捉更明确的例外情况.例如MyCustomException上面抛出的.所以我在这里创建了第二个@ControllerAdvise.

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE) // Make sure we get the highest priority.
public class MyCustomExceptionHandler {

    @ExceptionHandler
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Response handleException(MyCustomException e) {
        MyMsgObject myMsgObject = new MyMsgObject();
        myMsgObject.setStatus(MyStatus.Failure);
        myMsgObject.setMessage(e.getMessage());
        return myMsgObject;
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然表面上类似于一般的异常处理程序.有一个不同.我们需要指定@Order(Ordered.HIGHEST_PRECEDENCE)注释.如果没有这个,Spring将只执行与抛出的异常匹配的第一个异常处理程序.无论是否有更好的匹配处理程序.因此,我们使用此批注来确保此异常处理程序优先于一般异常处理程序.

摘要

这个解决方案适合我.我不确定我是否有最好的解决方案,可能会有Spring课程,我找不到哪些可以提供帮助.我希望这可以帮助任何有相同或类似问题的人.