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

Spring验证不断验证错误的参数,spring,bean-validation,Spring,Bean Validation,我有一个控制器,其web方法如下所示: public Response registerDevice( @Valid final Device device, @RequestBody final Tokens tokens ) {...} public class DeviceValidator implements Validator { @Override public boolean supports(Class<?> clazz) {

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

public Response registerDevice(
    @Valid final Device device, 
    @RequestBody final Tokens tokens
) {...}
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
        }
    }
}
@RequestMapping(...)
public MyMsgObject handleRequest (
    @Valid final MyHeaderObj myHeaderObj, 
    @RequestBody final MyRequestPayload myRequestPayload
    ) {...}
以及一个如下所示的验证器:

public Response registerDevice(
    @Valid final Device device, 
    @RequestBody final Tokens tokens
) {...}
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
        }
    }
}
@RequestMapping(...)
public MyMsgObject handleRequest (
    @Valid final MyHeaderObj myHeaderObj, 
    @RequestBody final MyRequestPayload myRequestPayload
    ) {...}
公共类设备验证器实现验证器{
@凌驾
公共布尔支持(类clazz){
返回设备.class.isAssignableFrom(clazz);
}
@凌驾
公共无效验证(对象目标、错误){
//变魔术
}
}
}
我试图让Spring验证由拦截器生成的设备参数。但每次我尝试时,它都会验证tokens参数

我尝试使用
@InitBinder
指定验证器,
@Validated
而不是
@Validated
,并注册
方法验证后处理器
类。到目前为止没有运气

要么根本不调用验证器,要么在验证设备参数时验证tokens参数

我正在使用Spring4.1.6和HibernateValidator 5.1.3


有人能提供我做错了什么的线索吗?我整个下午都在网上搜索,想把这件事弄清楚。我不敢相信spring的验证领域仍然像5年前一样混乱:-(

好的。经过两天的各种变化,我们现在已经解决了它。如果spring的验证让你做一件事的话——它提出了一系列难以置信的不起作用的事情!但是回到我的解决方案上来

基本上,我需要的是一种手动创建请求映射参数的方法,验证它们,然后确保无论成功还是失败,调用方始终收到自定义JSON响应。这样做比我想象的要困难得多,因为尽管有大量的博客文章和stackoverflow答案,我从未找到一个完整的解决方案。所以我努力勾勒出实现我想要的每一块拼图

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

配置 虽然我读过几篇博文,其中谈到了各种类,如
方法验证后处理器
,但最终我发现除了
@EnableWebMvc
注释之外,我不需要任何设置。默认的解析器等证明是我所需要的

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

public Response registerDevice(
    @Valid final Device device, 
    @RequestBody final Tokens tokens
) {...}
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
        }
    }
}
@RequestMapping(...)
public MyMsgObject handleRequest (
    @Valid final MyHeaderObj myHeaderObj, 
    @RequestBody final MyRequestPayload myRequestPayload
    ) {...}
这里您会注意到,与我找到的几乎每一篇博客文章和示例不同,我有两个对象被传递给该方法。第一个是我希望从标题动态生成的对象。第二个是JSON负载中的反序列化对象。其他对象也可以很容易地被包含,例如路径参数等。请尝试其他方法如果不使用下面的代码,您将遇到各种各样的奇怪和奇妙的错误

引起我所有痛苦的棘手部分是,我想验证
myHeaderObj
实例,而不是验证
myRequestPayload
实例。这让我很头疼

还要注意
MyMsgObject
result对象。这里我想返回一个将序列化为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;
    }
}
这里有两件事。第一件事是注册验证器。请注意,我们必须检查参数的类型。这是因为对
@RequestMapping
的每个参数都调用
@InitBinder
,我们只需要
MyHeaderObj
参数上的验证器。如果我们不这样做,将抛出异常en-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 ..."); 
    }
}
这个类的主要工作是使用它需要的任何方法来构建参数(
myHeaderObj
)。一旦构建,它就会继续调用Spring验证器来检查这个实例。如果有问题(通过检查返回的错误来检测),然后抛出一个异常,
@ExceptionHandler
可以检测并处理该异常

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

最后一部分,显式异常捕获 最后,我还需要捕获更明确的异常。例如上面抛出的
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;
    }
}
虽然表面上类似于一般的异常处理程序。但有一个不同。我们需要指定
@顺序(Ordered.HIGHEST\u优先级)
annotation。如果没有此注释,Spring将只执行与抛出的异常匹配的第一个异常处理程序。无论是否有更好的匹配处理程序。因此,我们使用此注释来确保此异常处理程序优先于常规处理程序

总结 这个解决方案对我来说很好。我不确定我是否有最好的解决方案,可能还有一些Spring类我还没有找到可以帮助我的。我希望这能帮助任何有相同或相同问题的人