@Valid annotation is ignored when applied to Multi

2019-06-26 06:18发布

问题:

This is my controller. It accepts a multipart/form-data request with two fields, form and file. The form field is a MyObject, the file field is a MultipartFile. Both variables are annotated with @Valid, and accordingly, I would expect Spring to invoke the Validator class of each respective field. However, this only happens with MyObject, and not with 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);
    }
}

My MyObjectValidator is triggered, but my FileValidator is not triggered. Both classes implement the Spring Validator interface. MyObjectValidator.supports(Class<?> aClass) is called, whereas FileValidator.supports(Class<?> aClass) is never called. Apart from that, my Controller is functioning perfectly, and diligently saves objects to my repo.

What could be the issue here? I've read similar questions, and common mistakes are to not use an appropriate argument inside the @InitBinder annotation, or to set the @InitBinder methods to private instead of public, but neither of this applies to my case.

This ugly workaround does what it's supposed to, but it is un-Spring-like. I call my FileValidator manually inside the Controller.createMyObject method, instead of letting Spring call it automatically through the @Valid annotation.

@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);
}

EDIT: I have included my Validator classes on request.

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.
    }

EDIT: I made some changes to my code like NiVeR suggested, removing the varargs parameter and changing my FileValidator.supports(Class<?> aClass) accordingly, but the behavior is still the same.

In Controller.java:

@PostMapping("myObject")
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public MyObject createMyObject(@RequestPart("form") @Valid MyObject myObject, @RequestPart("file") @Valid MultipartFile file) {
    return repo.save(myObject);
}

In FileValidator.java:

public boolean supports(Class<?> aClass) {
    return MultipartFile.class.isAssignableFrom(aClass);
}

回答1:

I believe that the problem is related to the variadic Multipart... parameter. In the supports method of the validator you are checking for array of Multipart but I suspect that's not the correct way. Just as a trial, I would make the Multipart a single object parameter (and change the validator accordingly) to test if it works in this way.



回答2:

@Magnus I think you have to add annotation in all the Validator Class eg.

@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.
    }
}