@Valid Запрос JSON с BindingResult вызывает исключение IllegalStateException
У меня есть служба REST, которая принимает запрос JSON. Я хочу проверить правильность значений запроса JSON. Как это сделать?
В Spring 3.1.0 RELEASE я знаю, что нужно убедиться, что они используют последние классы поддержки, перечисленные в 3.1.13. Новые классы поддержки на основе HandlerMethod для аннотированного контроллера Обработка
Старые объекты: AnnotationMethodHandlerAdapter
. Я хочу убедиться, что использую последние, такие как RequestMappingHandlerAdapter
.
Это потому, что я надеюсь, что он исправляет проблему, когда я вижу это:
java.lang.IllegalStateException: Errors/BindingResult аргумент, объявленный без предшествующего атрибута модели. Проверьте свою подпись метода обработчика!
Мой @Controller
метод обработчика и связанный с ним код:
@Autowired FooValidator fooValidator;
@RequestMapping(value="/somepath/foo", method=RequestMethod.POST)
public @ResponseBody Map<String, String> fooBar(
@Valid @RequestBody Map<String, String> specificRequest,
BindingResult results) {
out("fooBar called");
// get vin from JSON (reportRequest)
return null;
}
@InitBinder("specificRequest") // possible to leave off for global behavior
protected void initBinder(WebDataBinder binder){
binder.setValidator(fooValidator);
}
FooValidator
выглядит следующим образом:
@Component
public class FooValidator implements Validator {
public boolean supports(Class<?> clazz) {
out("supports called ");
return Map.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
out("validate called ");
}
private void out(String msg) {
System.out.println("****** " + getClass().getName() + ": " + msg);
}
}
Если я удалю BindingResult
, все будет хорошо, но я не смогу сказать, проверен ли JSON.
Я не сильно привязан к концепции использования Map<String, String>
для запроса JSON или использования отдельного валидатора в отличие от Custom Bean с аннотацией проверки (как это сделать для запроса JSON?). Независимо от того, что может подтвердить запрос JSON.
Ответы
Ответ 1
Мне нужно было сделать что-то подобное однажды. Я просто упростил свою жизнь, создав объект Java, который JSON может преобразовать и использовать GSON
для преобразования.
Это было честно просто:
@Autowired
private Gson gson;
@RequestMapping(value = "/path/info", method = RequestMethod.POST)
public String myMethod(@RequestParam(value = "data") String data,
Model model,
@Valid MyCustomObject myObj,
BindingResult result) {
//myObj does not contain any validation information.
//we are just using it as as bean to take advantage of the spring mvc framework.
//data contains the json string.
myObj = gson.fromJson(data, MyCustomObject.class);
//validate the object any way you want.
//Simplest approach would be to create your own custom validator
//to do this in Spring or even simpler would be just to do it manually here.
new MyCustomObjValidator().validate(myObj, result);
if (result.hasErrors()) {
return myErrorView;
}
return mySuccessView;
}
Выполняйте всю свою проверку в своем пользовательском классе Validator
:
public class MyCustomObjValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MyCustomObj.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
MyCustomObj c = (MyCustomObj) target;
Date startDate = c.getStartDate();
Date endDate = c.getEndDate();
if (startDate == null) {
errors.rejectValue("startDate", "validation.required");
}
if (endDate == null) {
errors.rejectValue("endDate", "validation.required");
}
if(startDate != null && endDate != null && endDate.before(startDate)){
errors.rejectValue("endDate", "validation.notbefore.startdate");
}
}
}
MyCustomObject
не содержит аннотации для проверки, потому что иначе Spring попытается проверить эти поля в этом объекте, которые в настоящее время пустые, потому что все данные находятся в строке JSON String, это может быть, например,:
public class MyCustomObject implements Serializable {
private Date startDate;
private Date endDate;
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public void setStartDate(Date theDate) {
this.startDate = theDate;
}
public void setEndDate(Date theDate) {
this.endDate = theDate;
}
}
Ответ 2
3.1.17 @Valid On @RequestBody Controller Method Аргументы говорит, что:
Аргумент метода @RequestBody
может быть аннотирован с помощью @Valid
, чтобы вызвать автоматическую проверку, аналогичную поддержке аргументов метода @ModelAttribute
. Результирующий MethodArgumentNotValidException
обрабатывается в DefaultHandlerExceptionResolver
и выводит код ответа 400
.
Другими словами, если вы используете @Valid @RequestBody
, то Spring отклонит недопустимый запрос до того, как он дойдет до вызова вашего метода. если вы вызываете метод, вы можете предположить, что тело запроса действительно.
BindingResult
используется для проверки объектов form/command, а не @RequestBody
.
Ответ 3
Попробуйте использовать следующее:
@Autowired
private FooValidator fooValidator;
@InitBinder("specificRequest") // possible to leave off for global behavior
protected void initBinder(WebDataBinder binder){
binder.setValidator(fooValidator);
}
@ModelAttribute("specificRequest")
public Map<String, String> getModel() {
return new HashMap<String, String>();
}
Это позволит вашему контроллеру сериализовать запрос в том типе, который вы указали.
Я должен сказать, что я обычно не делаю услугу (автоматически) валидатора, но это может быть лучше.
Теперь ваш обработчик выглядит следующим образом:
@RequestMapping(value="/somepath/foo", method=RequestMethod.POST)
public @ResponseBody Map<String, String> fooBar(
@Valid @ModelAttribute("specificRequest")
Map<String, String> specificRequest, BindingResult results) {
out("fooBar called");
// get vin from JSON (reportRequest)
return null;
}
Насколько мне известно, это прекрасно работает и устраняет ошибку, которую вы получаете.