Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Spring MVC inserting an empty object into what should be an empty list?

I'm uploading multiple files. This works fine when I actually select files to upload; they get into their List just fine, but when I click Upload without selecting a file, instead of getting an empty List, I get a List with an empty file in it. My validator is checking for the size of this List being 0, so it sneaks past it.

Yes, I can work around it and just check for an empty file, and yes I can disable the upload button when no file is selected, but I want to know why Spring does this and possibly how to stop it from doing it.

Here's the jsp:

<form:form commandName="documentsBean" enctype="multipart/form-data">
    <input type="hidden" name="submittedFormAction" value="attachDocumentSave"/>
    <input type="file" name="files" id="attachFiles" multiple/>
    <button type="submit">Attach</button>
</form:form>

The bean, nothing fancy here:

public class DocumentsBean
{
    private List<MultipartFile> files;
    public List<MultipartFile> getFiles(){
        return files;
    }
    public void setFiles(List<MultipartFile> files){
        this.files = files;
    }
}

The controller:

@RequestMapping( method = RequestMethod.POST, params = { "submittedFormAction=attachDocumentSave" })
public ModelAndView attachDocumentSave(HttpServletRequest request, @ModelAttribute("documentsBean") DocumentsBean documentsBean, BindingResult errors) throws Exception
{
    // At this point documentsBean.files is an ArrayList<E> with size = 1
}

When I drill down into documentsBean.files, I see elementData[0] is a CommonsMultipartFile, its fileName is an empty string and its size is 0.

What is going on?

like image 929
Zook Avatar asked Mar 13 '14 21:03

Zook


1 Answers

Here is your solution. The explanation is below.

Add an @InitBinder method that sets the following property. Ideally you would add it to a @ControllerAdvice class, but the controller with the corresponding @RequestMapping method works as well.

@InitBinder
public void init(WebDataBinder binder) {
    binder.setBindEmptyMultipartFiles(false);
}

You'll also need to change your DocumentsBean class like so

private List<MultipartFile> files = Arrays.asList();

So that the field isn't null.

Spring uses a ServletModelAttributeMethodProcessor to generate an argument for a @ModelAttribute annotated parameter like yours

@ModelAttribute("documentsBean") DocumentsBean documentsBean

This HandlerMethodArgumentResolver uses the request (and its form parameters) to generate an instance of whatever type your parameter is, by binding parameters to the instance class' instance fields. If it detects that the request has a content type of multipart/form-data, it will perform special binding.

This binding is done in WebDataBinder#bindMultipart(..). It retrieves all the MultipartFile objects created by the MulitpartResolver. If there is only one, it checks two things.

if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
    mpvs.add(key, value);
}

The first method call

Return[s] whether to bind empty MultipartFile parameters.

and the second checks if the MultipartFile has any body content or not. It won't if you submitted the form without selecting a file (or if you selected an empty file AFAIK).

If either of those expressions resolve to true, then Spring will provide your handler method with this empty MultipartFile holder. Otherwise, it will not bind that field, which is why you want a default value (so it doesn't remain null)

private List<MultipartFile> files = Arrays.asList();

Take a look at the javadoc of WebDataBinder#setBindEmptyMultipartFiles(boolean) which states

Set whether to bind empty MultipartFile parameters. Default is "true".

Turn this off if you want to keep an already bound MultipartFile when the user resubmits the form without choosing a different file. Else, the already bound MultipartFile will be replaced by an empty MultipartFile holder.

This is the property you should set to false in the @InitBinder method shown above.

like image 101
Sotirios Delimanolis Avatar answered Oct 29 '22 17:10

Sotirios Delimanolis