我有一个带有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
}
}
}
我正在尝试让Spring验证由拦截器生成的Device参数。但每次我尝试时,它都会验证令牌参数。
我已尝试使用@InitBinder
指定验证程序@Validated
而不是@Valid
并注册MethodValidationPostProcessor
类。到目前为止没有运气。
根本不调用验证器,或者在验证Device参数时验证tokens参数。
我正在使用Spring 4.1.6和Hibernate验证器5.1.3。
任何人都可以提供任何关于我做错的线索吗?我整个下午都在网上试图解决这个问题。无法相信春天的验证区域仍然像5年前一样混乱: - (
答案 0 :(得分:6)
确定。经过两天的各种变化后,现在已经解决了这个问题。如果Spring的验证让你做到了一件事 - 它会提出一系列令人难以置信的事情,这些事情是行不通的!但回到我的解决方案。
基本上我需要的是一种手动创建请求映射参数,验证它们然后确保无论是成功还是失败的方法,调用者总是收到自定义JSON响应。这样做比我想象的要困难得多,因为尽管博客文章和stackoverflow答案数量很多,但我从未找到完整的解决方案。因此,我努力概述实现我想要的每一个难题。
注意:在下面的代码示例中,我概括了事物的名称,以帮助澄清什么是自定义的,而不是。
虽然我读过的几篇博客文章谈到了MethodValidationPostProcessor
这样的各种类,但最后我发现除了@EnableWebMvc
注释之外我不需要任何设置。事实证明,默认的解析器等是我需要的。
我的最终请求映射签名如下所示:
@RequestMapping(...)
public MyMsgObject handleRequest (
@Valid final MyHeaderObj myHeaderObj,
@RequestBody final MyRequestPayload myRequestPayload
) {...}
你会在这里注意到,与我发现的每篇博文和样本不同,我有两个对象被传递给该方法。第一个是我想从头部动态生成的对象。第二个是来自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;
}
}
这里发生了两件事。第一个是注册验证器。请注意,我们必须检查参数的类型。这是因为为@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 ...");
}
}
我还没有真正理解的一件事是supports(Class<?> clazz)
方法。我原以为Spring会使用这个方法测试参数来决定是否应该应用这个验证器。但它并没有。因此,@InitBinder
中的所有代码都决定何时应用此验证器。
这是最大的一段代码。在这里,我们需要生成要传递给MyHeaderObj
的{{1}}对象。 Spring会自动检测这个类。
@RequestMapping
这个类的主要工作是使用构建参数所需的任何方法(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;
}
}
}
}
)。一旦构建它然后继续调用Spring验证器来检查这个实例。如果出现问题(通过检查返回的错误检测到),则会抛出myHeaderObj
可以检测和处理的异常。
请注意@ExceptionHandler
方法。这是我在许多Spring的课程中找到的代码。它的工作是检测是否有任何参数具有validateIfApplicable(WebDataBinder binder, MethodParameter methodParam)
或@Validated
注释,如果有,则调用相关的验证器。默认情况下,Spring不会为这样的自定义参数处理程序执行此操作,因此我们可以添加此功能。认真的春天????没有AbstractSomething ????
最后,我还需要捕捉更明确的例外情况。例如,上面抛出的@Valid
。所以我在这里创建了第二个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;
}
}
注释。如果没有这个,Spring将只执行与抛出的异常匹配的第一个异常处理程序。无论是否有更好的匹配处理程序。因此,我们使用此批注来确保此异常处理程序优先于一般异常处理程序。
此解决方案适合我。我不确定我是否得到了最好的解决方案,而且我可能没有找到可以提供帮助的Spring课程。我希望这可以帮助任何有相同或类似问题的人。