Это мой контроллер. Он принимает запрос multipart/form-data
с двумя полями, form
и file
. Поле form
— это MyObject
, поле file
— это MultipartFile
. Обе переменные аннотированы с помощью @Valid
, и, соответственно, я ожидаю, что Spring вызовет класс Validator
для каждого соответствующего поля. Однако это происходит только с MyObject
и не с MultipartFile
.
@RequestMapping("/api")
@RestController
public class Controller {
private MyObjectRepository repo;
private MyObjectValidator myObjectValidator;
private FileValidator fileValidator;
@Autowired
public myObjectController(MyObjectRepository repo, MyObjectValidator myObjectValidator,
FileValidator fileValidator) {
this.repo = repo;
this.myObjectValidator = myObjectValidator;
this.fileValidator = fileValidator;
}
@InitBinder("form")
public void initMyObjectBinder(WebDataBinder binder) {
binder.setValidator(this.myObjectValidator);
}
@InitBinder("file")
public void initFileBinder(WebDataBinder binder) {
binder.setValidator(this.fileValidator);
}
@PostMapping("myObject")
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public MyObject createMyObject(@RequestPart("form") @Valid MyObject myObject,
@RequestPart("file") @Valid MultipartFile... file) {
return repo.save(myObject);
}
}
Мой MyObjectValidator
срабатывает, а мой FileValidator
не срабатывает. Оба класса реализуют интерфейс Spring Validator
. MyObjectValidator.supports(Class<?> aClass)
вызывается, тогда как FileValidator.supports(Class<?> aClass)
никогда не вызывается. Кроме того, мой Controller
работает отлично и старательно сохраняет объекты в моем repo
.
В чем тут может быть дело? Я читал подобные вопросы, и распространенные ошибки заключаются в том, чтобы не использовать соответствующий аргумент внутри аннотации @InitBinder
или установить для методов @InitBinder
значение private
вместо public
, но ни то, ни другое не относится к моему случаю.
Этот уродливый обходной путь делает то, что должен, но он не похож на Spring. Я вызываю свой FileValidator
вручную внутри метода Controller.createMyObject
вместо того, чтобы позволить Spring вызывать его автоматически через аннотацию @Valid
.
@PostMapping("myObject")
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public MyObject createMyObject(@RequestPart("form") @Valid MyObject myObject,
@RequestPart("file") @Valid MultipartFile... file) {
if (fileValidator.supports(file.getClass())) {
Errors errors = new BeanPropertyBindingResult(file, "Uploaded file.");
fileValidator.validate(file,errors);
if (errors.hasErrors()) {
throw new BadRequestException();
}
}
return repo.save(myObject);
}
РЕДАКТИРОВАТЬ: я включил свои классы Validator
по запросу.
import org.springframework.validation.Validator;
public abstract class AbstractValidator implements Validator {
// One shared method here.
}
public class FileValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { // This method is never triggered.
boolean isSingleFile = MultipartFile.class.isAssignableFrom(aClass); // This line has a breakpoint, it is never triggered in the debugger.
boolean isFileArray = aClass.equals(MultipartFile[].class);
return (isSingleFile || isFileArray);
}
public void validate(Object o, Errors e) {
//Several validation methods go here.
}
public class MyObjectValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { // This method is triggered.
return (MyObject.class.equals(aClass)); // This line has a breakpoint, and it is always triggered in the debugger.
}
public void validate(Object o, Errors e) {
// Several validation methods go here.
}
РЕДАКТИРОВАТЬ: я внес некоторые изменения в свой код, как предложил NiVeR, удалив параметр varargs и соответствующим образом изменив FileValidator.supports(Class<?> aClass)
, но поведение осталось прежним.
В Контроллере.java:
@PostMapping("myObject")
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public MyObject createMyObject(@RequestPart("form") @Valid MyObject myObject, @RequestPart("file") @Valid MultipartFile file) {
return repo.save(myObject);
}
В FileValidator.java:
public boolean supports(Class<?> aClass) {
return MultipartFile.class.isAssignableFrom(aClass);
}