Аннотация @Valid игнорируется при применении к объекту MultipartFile.

Это мой контроллер. Он принимает запрос 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);
}

person Magnus    schedule 17.07.2018    source источник
comment
Вы также должны показать коды валидаторов.   -  person NiVeR    schedule 17.07.2018
comment
@NiVeR, я отредактировал вопрос.   -  person Magnus    schedule 17.07.2018


Ответы (2)


Я считаю, что проблема связана с вариативным параметром Multipart.... В методе supports валидатора вы проверяете массив Multipart, но я подозреваю, что это неправильный способ. В качестве пробы я бы сделал Multipart единственным параметром объекта (и соответствующим образом изменил валидатор), чтобы проверить, работает ли он таким образом.

person NiVeR    schedule 17.07.2018
comment
Я попробовал это сейчас, но это не имело никакого значения. Я добавил свой вопрос с предложенными вами изменениями. Для справки: мой метод supports в FileValidator проверяет не только массив, но и один объект MultipartFile. Если ввод является одним из этих двух типов, значение будет принято. В любом случае, мой метод supports даже не вызывается. Точка останова внутри этого метода никогда не срабатывает. - person Magnus; 17.07.2018
comment
Хорошо, вторая идея: создайте класс-оболочку, который включает Multipart в качестве свойства, и вместо этого реализуйте валидатор в этом другом классе (также передавая его в качестве параметра контроллеру). - person NiVeR; 17.07.2018
comment
Кажется сложным создать HTTP-запрос, который мой контроллер Spring примет и преобразует в класс-оболочку. Это то, что я должен использовать в качестве решения или только для диагностики? Есть ли простой способ сделать это? Кстати, я только что отредактировал свой вопрос с помощью уродливого обходного пути, который вроде как работает, но не похож на Spring. Этот обходной путь также подтверждает мою гипотезу о том, что с самим FileValidator все в порядке, а только с привязкой. - person Magnus; 17.07.2018
comment
stackoverflow.com/questions/7161215/ - person NiVeR; 17.07.2018

@Magnus Я думаю, вам нужно добавить аннотацию во все классы Validator, например.

@Component
public class MyObjectValidator extends AbstractValidator {

    public boolean supports(Class<?> aClass) { //This method is triggered.
        return (MyObject.class.equals(aClass));
    }

    public void validate(Object o, Errors e) {
        //Several validation methods goes here.
    }
}
person hardik beladiya    schedule 17.07.2018
comment
Закончил тестировать. Сначала я установил @Component в своих FileValidator и MyObjectValidator, а затем также установил его в AbstractValidator. Нет эффекта. Затем я попытался удалить @Component из FileValidator и MyObjectValidator, но оставил его в AbstractValidator. Это все еще не имело никакого значения. - person Magnus; 17.07.2018
comment
Магнус, я думаю, вы забыли добавить @Controller в класс контроллера, можете ли вы сделать это один раз, если это сработает для вас. - person hardik beladiya; 17.07.2018
comment
Я случайно пропустил это в вопросе, но аннотация @Controller есть. Смотрите мою правку. Как уже упоминалось, это работающий контроллер, за исключением того, что класс FileValidator никогда не вызывается. - person Magnus; 17.07.2018