Spring MVC -- 上传文件 彻底解决springMVC中文乱码

Servlet技术出现以前,文件上传的编程仍然是一项很困难的任务,它涉及在服务器端解析原始的HTTP响应。为了减轻编程的痛苦,开发人员借助于商业的文件上传组件。值得庆幸的是,2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,它很快成为了Java Web应用程序员的利器。

经过很多年,Servlet的设计人员才意识到文件文件上传的重要性,并终于成为Servlet 3.0的内置特性。Servlet 3.0的开发人员不再需要将Commons FileUpload组件导入到他们的项目中去。

为此,在Spring MVC中处理文件上传有两种情况:

  • 在Servlet 3.0版本以下,使用Apache Commons FileUpload组件;
  • 在Servlet 30.版本以上,利用Servlet 3.0及其更高版本的内置支持,如果要将应用程序部署到支持Servlet3.0及其更高版本的容器中,则只能使用这种方法。

无论使用哪个版本的Servlet,都要利用相同的API来处理已经上传的文件。本篇博客将会介绍如何在需要支持文件上传的Spring MVC应用中使用Commons FileUpload和Servlet 3.0文件上传特性。

一 前端编程

为了上传文件,必须将HTML表格enctype属性值设置为multipart/form-data,像下面这样:

<form  action="action" method="post" enctype="multipart/form-data">
   select a file <input type="file" name="fieldName"/>
   <input type="submit" value="Upload"/>
</form>

表格中必须包含类型为file的一个input元素,它会显示成一个按钮,单击时,它会打开一个对话框,用来选择文件。

在HTML 5之前,如果想要上传多个文件,必须使用多个类型为file的input元素。但是在HTML 5中,通过在input元素中引入multiple属性,使得多个文件的上传变得更加简单。在HTML 5中编写以下任意一行代码,便可以生成一个按钮来选择多个文件:

<input type="file" name="fieldName" multiple/>
<input type="file" name="fieldName" multiple="multiple"/>
<input type="file" name="fieldName" multiple=""/>

二 MultipartFile接口

在Spring MVC中处理已经上传的文件十分容易。上传到Spring MVC应用程序中的文件会被包含在一个MultipartFile对象中。我们唯一的任务就是,用类型MultipartFile的属性编写一个domain类。

org.springframework.web.multipart.MultipartFile接口源代码如下:

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.multipart;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;

import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;

/**
 * A representation of an uploaded file received in a multipart request.
 *
 * <p>The file contents are either stored in memory or temporarily on disk.
 * In either case, the user is responsible for copying file contents to a
 * session-level or persistent store as and if desired. The temporary storage
 * will be cleared at the end of request processing.
 *
 * @author Juergen Hoeller
 * @author Trevor D. Cook
 * @since 29.09.2003
 * @see org.springframework.web.multipart.MultipartHttpServletRequest
 * @see org.springframework.web.multipart.MultipartResolver
 */
public interface MultipartFile extends InputStreamSource {

    /**
     * Return the name of the parameter in the multipart form.
     * @return the name of the parameter (never {@code null} or empty)
     */
    String getName();

    /**
     * Return the original filename in the client's filesystem.
     * <p>This may contain path information depending on the browser used,
     * but it typically will not with any other than Opera.
     * @return the original filename, or the empty String if no file has been chosen
     * in the multipart form, or {@code null} if not defined or not available
     * @see org.apache.commons.fileupload.FileItem#getName()
     * @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename
     */
    @Nullable
    String getOriginalFilename();

    /**
     * Return the content type of the file.
     * @return the content type, or {@code null} if not defined
     * (or no file has been chosen in the multipart form)
     */
    @Nullable
    String getContentType();

    /**
     * Return whether the uploaded file is empty, that is, either no file has
     * been chosen in the multipart form or the chosen file has no content.
     */
    boolean isEmpty();

    /**
     * Return the size of the file in bytes.
     * @return the size of the file, or 0 if empty
     */
    long getSize();

    /**
     * Return the contents of the file as an array of bytes.
     * @return the contents of the file as bytes, or an empty byte array if empty
     * @throws IOException in case of access errors (if the temporary store fails)
     */
    byte[] getBytes() throws IOException;

    /**
     * Return an InputStream to read the contents of the file from.
     * <p>The user is responsible for closing the returned stream.
     * @return the contents of the file as stream, or an empty stream if empty
     * @throws IOException in case of access errors (if the temporary store fails)
     */
    @Override
    InputStream getInputStream() throws IOException;

    /**
     * Return a Resource representation of this MultipartFile. This can be used
     * as input to the {@code RestTemplate} or the {@code WebClient} to expose
     * content length and the filename along with the InputStream.
     * @return this MultipartFile adapted to the Resource contract
     * @since 5.1
     */
    default Resource getResource() {
        return new MultipartFileResource(this);
    }

    /**
     * Transfer the received file to the given destination file.
     * <p>This may either move the file in the filesystem, copy the file in the
     * filesystem, or save memory-held contents to the destination file. If the
     * destination file already exists, it will be deleted first.
     * <p>If the target file has been moved in the filesystem, this operation
     * cannot be invoked again afterwards. Therefore, call this method just once
     * in order to work with any storage mechanism.
     * <p><b>NOTE:</b> Depending on the underlying provider, temporary storage
     * may be container-dependent, including the base directory for relative
     * destinations specified here (e.g. with Servlet 3.0 multipart handling).
     * For absolute destinations, the target file may get renamed/moved from its
     * temporary location or newly copied, even if a temporary copy already exists.
     * @param dest the destination file (typically absolute)
     * @throws IOException in case of reading or writing errors
     * @throws IllegalStateException if the file has already been moved
     * in the filesystem and is not available anymore for another transfer
     * @see org.apache.commons.fileupload.FileItem#write(File)
     * @see javax.servlet.http.Part#write(String)
     */
    void transferTo(File dest) throws IOException, IllegalStateException;

    /**
     * Transfer the received file to the given destination file.
     * <p>The default implementation simply copies the file input stream.
     * @since 5.1
     * @see #getInputStream()
     * @see #transferTo(File)
      */
    default void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
    }

}
View Code

该接口具有以下方法:

    /**
     * Return the contents of the file as an array of bytes.
     * @return the contents of the file as bytes, or an empty byte array if empty
     * @throws IOException in case of access errors (if the temporary store fails)
     */
    byte[] getBytes() throws IOException;

它以字节数组的形式返回文件的内容。

    /**
     * Return the content type of the file.
     * @return the content type, or {@code null} if not defined
     * (or no file has been chosen in the multipart form)
     */
    @Nullable
    String getContentType();

它返回文件的内容类型。

    /**
     * Return an InputStream to read the contents of the file from.
     * <p>The user is responsible for closing the returned stream.
     * @return the contents of the file as stream, or an empty stream if empty
     * @throws IOException in case of access errors (if the temporary store fails)
     */
    @Override
    InputStream getInputStream() throws IOException;

它返回一个InputStream ,从中读取文件的内容。

    /**
     * Return the name of the parameter in the multipart form.
     * @return the name of the parameter (never {@code null} or empty)
     */
    String getName();

它以多部分的形式返回参数的名称。

    /**
     * Return the original filename in the client's filesystem.
     * <p>This may contain path information depending on the browser used,
     * but it typically will not with any other than Opera.
     * @return the original filename, or the empty String if no file has been chosen
     * in the multipart form, or {@code null} if not defined or not available
     * @see org.apache.commons.fileupload.FileItem#getName()
     * @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename
     */
    @Nullable
    String getOriginalFilename();

它返回客户端文件系统中文件的原始文件名称。

    /**
     * Return the size of the file in bytes.
     * @return the size of the file, or 0 if empty
     */
    long getSize();

它以字节为单位,返回文件的大小。

    /**
     * Transfer the received file to the given destination file.
     * <p>This may either move the file in the filesystem, copy the file in the
     * filesystem, or save memory-held contents to the destination file. If the
     * destination file already exists, it will be deleted first.
     * <p>If the target file has been moved in the filesystem, this operation
     * cannot be invoked again afterwards. Therefore, call this method just once
     * in order to work with any storage mechanism.
     * <p><b>NOTE:</b> Depending on the underlying provider, temporary storage
     * may be container-dependent, including the base directory for relative
     * destinations specified here (e.g. with Servlet 3.0 multipart handling).
     * For absolute destinations, the target file may get renamed/moved from its
     * temporary location or newly copied, even if a temporary copy already exists.
     * @param dest the destination file (typically absolute)
     * @throws IOException in case of reading or writing errors
     * @throws IllegalStateException if the file has already been moved
     * in the filesystem and is not available anymore for another transfer
     * @see org.apache.commons.fileupload.FileItem#write(File)
     * @see javax.servlet.http.Part#write(String)
     */
    void transferTo(File dest) throws IOException, IllegalStateException;

它将上传的文件保存到目标目录下。

    /**
     * Return whether the uploaded file is empty, that is, either no file has
     * been chosen in the multipart form or the chosen file has no content.
     */
    boolean isEmpty();

它表示被上传的文件是否为空(没有上传文件、或者文件内容为空)。

三 使用Commons Fileupload组件上传文件

只有实现了Servlet 3.0及其更高版本规范的Servlet容器,才支持文件上传。对于版本低于Servlet 3.0的容器,则需要Apache Commons Fileupload组件,commons-fileupload.jar包的下载路径如下:https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload

这是一个开源项目,因此是免费的,它会提供了源代码。为了让Commons Fileupload能够运行,还需要一个Apache Commins组件commons-io.jar,commons-io.jar包的下载路径如下:https://mvnrepository.com/artifact/commons-io/commons-io

下载完这两个JAR包,我们还需要做以下工作:

  • 将这两个JAR文件复制到应用程序的/WEB-INF/lib路径下;
  • 在Spring MVC配置文件中定义multipartResolver bean;
    <bean id="multipartResolver"
            class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="5000000000"/>
    </bean>

CommonsMultipartResolver类,实际上就是将org.apache.commons.fileupload.servlet.ServletFileUpload类和org.apache.commons.fileupload.disk.DiskFileItemFactory的功能进行了整合,具体代码如下:

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.multipart.commons;

import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest;
import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;

/**
 * Servlet-based {@link MultipartResolver} implementation for
 * <a href="https://commons.apache.org/proper/commons-fileupload">Apache Commons FileUpload</a>
 * 1.2 or above.
 *
 * <p>Provides "maxUploadSize", "maxInMemorySize" and "defaultEncoding" settings as
 * bean properties (inherited from {@link CommonsFileUploadSupport}). See corresponding
 * ServletFileUpload / DiskFileItemFactory properties ("sizeMax", "sizeThreshold",
 * "headerEncoding") for details in terms of defaults and accepted values.
 *
 * <p>Saves temporary files to the servlet container's temporary directory.
 * Needs to be initialized <i>either</i> by an application context <i>or</i>
 * via the constructor that takes a ServletContext (for standalone usage).
 *
 * @author Trevor D. Cook
 * @author Juergen Hoeller
 * @since 29.09.2003
 * @see #CommonsMultipartResolver(ServletContext)
 * @see #setResolveLazily
 * @see org.apache.commons.fileupload.servlet.ServletFileUpload
 * @see org.apache.commons.fileupload.disk.DiskFileItemFactory
 */
public class CommonsMultipartResolver extends CommonsFileUploadSupport
        implements MultipartResolver, ServletContextAware {

    private boolean resolveLazily = false;


    /**
     * Constructor for use as bean. Determines the servlet container's
     * temporary directory via the ServletContext passed in as through the
     * ServletContextAware interface (typically by a WebApplicationContext).
     * @see #setServletContext
     * @see org.springframework.web.context.ServletContextAware
     * @see org.springframework.web.context.WebApplicationContext
     */
    public CommonsMultipartResolver() {
        super();
    }

    /**
     * Constructor for standalone usage. Determines the servlet container's
     * temporary directory via the given ServletContext.
     * @param servletContext the ServletContext to use
     */
    public CommonsMultipartResolver(ServletContext servletContext) {
        this();
        setServletContext(servletContext);
    }


    /**
     * Set whether to resolve the multipart request lazily at the time of
     * file or parameter access.
     * <p>Default is "false", resolving the multipart elements immediately, throwing
     * corresponding exceptions at the time of the {@link #resolveMultipart} call.
     * Switch this to "true" for lazy multipart parsing, throwing parse exceptions
     * once the application attempts to obtain multipart files or parameters.
     */
    public void setResolveLazily(boolean resolveLazily) {
        this.resolveLazily = resolveLazily;
    }

    /**
     * Initialize the underlying {@code org.apache.commons.fileupload.servlet.ServletFileUpload}
     * instance. Can be overridden to use a custom subclass, e.g. for testing purposes.
     * @param fileItemFactory the Commons FileItemFactory to use
     * @return the new ServletFileUpload instance
     */
    @Override
    protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
        return new ServletFileUpload(fileItemFactory);
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        if (!isUploadTempDirSpecified()) {
            getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext));
        }
    }


    @Override
    public boolean isMultipart(HttpServletRequest request) {
        return ServletFileUpload.isMultipartContent(request);
    }

    @Override
    public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
        Assert.notNull(request, "Request must not be null");
        if (this.resolveLazily) {
            return new DefaultMultipartHttpServletRequest(request) {
                @Override
                protected void initializeMultipart() {
                    MultipartParsingResult parsingResult = parseRequest(request);
                    setMultipartFiles(parsingResult.getMultipartFiles());
                    setMultipartParameters(parsingResult.getMultipartParameters());
                    setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
                }
            };
        }
        else {
            MultipartParsingResult parsingResult = parseRequest(request);
            return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
                    parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
        }
    }

    /**
     * Parse the given servlet request, resolving its multipart elements.
     * @param request the request to parse
     * @return the parsing result
     * @throws MultipartException if multipart resolution failed.
     */
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = determineEncoding(request);
        FileUpload fileUpload = prepareFileUpload(encoding);
        try {
            List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
            return parseFileItems(fileItems, encoding);
        }
        catch (FileUploadBase.SizeLimitExceededException ex) {
            throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
        }
        catch (FileUploadBase.FileSizeLimitExceededException ex) {
            throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
        }
        catch (FileUploadException ex) {
            throw new MultipartException("Failed to parse multipart servlet request", ex);
        }
    }

    /**
     * Determine the encoding for the given request.
     * Can be overridden in subclasses.
     * <p>The default implementation checks the request encoding,
     * falling back to the default encoding specified for this resolver.
     * @param request current HTTP request
     * @return the encoding for the request (never {@code null})
     * @see javax.servlet.ServletRequest#getCharacterEncoding
     * @see #setDefaultEncoding
     */
    protected String determineEncoding(HttpServletRequest request) {
        String encoding = request.getCharacterEncoding();
        if (encoding == null) {
            encoding = getDefaultEncoding();
        }
        return encoding;
    }

    @Override
    public void cleanupMultipart(MultipartHttpServletRequest request) {
        if (!(request instanceof AbstractMultipartHttpServletRequest) ||
                ((AbstractMultipartHttpServletRequest) request).isResolved()) {
            try {
                cleanupFileItems(request.getMultiFileMap());
            }
            catch (Throwable ex) {
                logger.warn("Failed to perform multipart cleanup for servlet request", ex);
            }
        }
    }

}
View Code

multipartResolver 对象则通过配置property元素来调用setter方法以设置属性值。我们可以通过setter方式注入的属性有:

  • maxUploadSize:控制上传单个文件的大小,单位是字节;
  • maxInMemorySize:设置上传文件时用到的临时文件的大小,单位是字节;
  • defaultEncoding:请求参数的默认编码方式。

这些属性被用来对上传文件进行设置。

此外,CommonsMultipartResolver类的还有一个非常重要的函数:

  /**
     * Parse the given servlet request, resolving its multipart elements.
     * @param request the request to parse
     * @return the parsing result
     * @throws MultipartException if multipart resolution failed.
     */
    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = determineEncoding(request);
        FileUpload fileUpload = prepareFileUpload(encoding);
        try {
            List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
            return parseFileItems(fileItems, encoding);
        }
        catch (FileUploadBase.SizeLimitExceededException ex) {
            throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
        }
        catch (FileUploadBase.FileSizeLimitExceededException ex) {
            throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
        }
        catch (FileUploadException ex) {
            throw new MultipartException("Failed to parse multipart servlet request", ex);
        }
    }

通过parseRequest()函数解析form中的所有请求字段,并保存到List<FileItem>集合中,然后将集合转换为MultipartParsingResult类型返回:

    /**
     * Holder for a Map of Spring MultipartFiles and a Map of
     * multipart parameters.
     */
    protected static class MultipartParsingResult {

        private final MultiValueMap<String, MultipartFile> multipartFiles;

        private final Map<String, String[]> multipartParameters;

        private final Map<String, String> multipartParameterContentTypes;

        public MultipartParsingResult(MultiValueMap<String, MultipartFile> mpFiles,
                Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {

            this.multipartFiles = mpFiles;
            this.multipartParameters = mpParams;
            this.multipartParameterContentTypes = mpParamContentTypes;
        }

        public MultiValueMap<String, MultipartFile> getMultipartFiles() {
            return this.multipartFiles;
        }

        public Map<String, String[]> getMultipartParameters() {
            return this.multipartParameters;
        }

        public Map<String, String> getMultipartParameterContentTypes() {
            return this.multipartParameterContentTypes;
        }
    }
View Code

MultipartParsingResult类有个重要的属性:

private final MultiValueMap<String, MultipartFile> multipartFiles;

该Map的键值为String类型,保存的是表单类型为file的input元素的name属性值,值为MultipartFile接口类型,该类型保存了该input元素对应的上传文件。

 四 upload1示例

范例upload1展示了如何利用Apache Commons FileUpload处理已经上传的文件。这个范例在Servlet 3.0容器中也是有效的。upload1有一个domain包,包含Procudt类,它包含了一个MultipartFile对象列表。该示例介绍了如何进行产品图片的上传。

1、目录结构

下面展示upload1应用的目录结构:

注意:在lib中我们需要导入Apache Commons FileUpload组件。

2、Product类

Product类具有类型为List<MultipartFile>的imagea属性,这个属性用来保存上传的产品图片文件(可以是多个图片文件):

package domain;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.springframework.web.multipart.MultipartFile;

public class Product implements Serializable {
    private static final long serialVersionUID = 1;

    @NotNull
    @Size(min=1, max=10)
    private String name;

    private String description;
    private BigDecimal price;
    private List<MultipartFile> images;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    public List<MultipartFile> getImages() {
        return images;
    }
    public void setImages(List<MultipartFile> images) {
        this.images = images;
    }
}

3、控制器

package controller;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import domain.Product;

@Controller
public class ProductController {

    private static final Log logger = LogFactory
            .getLog(ProductController.class);

    //请求URL:/input-product 
    @RequestMapping(value = "/input-product")
    public String inputProduct(Model model) {
        model.addAttribute("product", new Product());
        return "ProductForm";
    }

    //请求URL:/save-product
    @RequestMapping(value = "/save-product")
    public String saveProduct(HttpServletRequest servletRequest,
            @ModelAttribute Product product, BindingResult bindingResult,
            Model model) {

        //获取上传的图片文件(可以多个文件)
        List<MultipartFile> files = product.getImages();

        //用于保存所有文件名
        List<String> fileNames = new ArrayList<String>();

        //检验是否有文件?
        if (null != files && files.size() > 0) {
            //遍历
            for (MultipartFile multipartFile : files) {
                //获取文件名
                String fileName = multipartFile.getOriginalFilename();
                fileNames.add(fileName);
                
                //获取应用/image虚拟路径在文件系统上对应的真实路径 + 文件名  并创建File对象
                File imageFile = new File(servletRequest.getServletContext()
                        .getRealPath("/image"), fileName);
                try {
                    //将上传的文件保存到目标目录下
                    multipartFile.transferTo(imageFile);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        // save product here
        model.addAttribute("product", product);
        return "ProductDetails";
    }

}

ProductController类有inputProduct()和saveProduct()两个请求处理方法。inputProduct()方法向浏览器发出一个产品表单,saveProduct()方法将已经上传的图片文件保存到应用程序的image目录下,文件名不改变。

注意:必须先创建好image文件夹。

4、配置文件

下面给出springmvc-config.xml文件的所有内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd     
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="controller" />
    <mvc:annotation-driven />

    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:resources mapping="/*.html" location="/" />
    <mvc:resources mapping="/image/**" location="/image/" />

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
    <bean id="multipartResolver"
            class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="5000000000"/>
    </bean>

</beans>

利用multipartResolver bean的maxUploadSize属性,可以设置能够接受的最大文件容量。如果没有设置这个属性,则没有最大文件容量限制。没有设置文件容量限制,并不意味着可以上传任意大小的文件。上传过大的文件时需要花费很长的时间,这样会导致服务器超时,为了处理超大文件的问题,可以利用HTML 5 File API将文件切片,然后再分别上传这些文件。

如果想对上传的文件类型进行过滤,那么我们可以需要先获取上传文件的名称,然后检测其扩展名。此外,我们也可以在前端使用js代码检测上传文件的扩展名。

部署描述符(web.xml文件):

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 
        xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"> 
            
     <!-- 配置编码方式过滤器,注意一点:要配置在所有过滤器的前面 -->
     <filter>
       <filter-name>CharacterEncodingFilter</filter-name>
       <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
       <init-param>
          <param-name>encoding</param-name>
          <param-value>utf-8</param-value>
       </init-param>
     </filter>
     <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
     </filter-mapping>
  
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/config/springmvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>    
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

注意,web.xml文件中我们配置了编码方式过滤器,将所有http请求的参数编码为UTF-8方式,与jsp中页面编码一致。

5、视图

用于上传图片文件的ProductForm.jsp页面如下所示:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>

<div id="global">
<form:form modelAttribute="product" action="save-product" method="post" enctype="multipart/form-data">
    <fieldset>
        <legend>Add a product</legend>
        <p>
            <label for="name">Product Name: </label>
            <form:input id="name" path="name" cssErrorClass="error"/>
            <form:errors path="name" cssClass="error"/>
        </p>
        <p>
            <label for="description">Description: </label>
            <form:input id="description" path="description"/>
        </p>
        <p>
            <label for="price">Price: </label>
            <form:input id="price" path="price" cssErrorClass="error"/>
        </p>
        <p>
            <label for="image">Product Image: </label>
            <input type="file" name="images[0]"/>
        </p>
        <p id="buttons">
            <input id="reset" type="reset" tabindex="4">
            <input id="submit" type="submit" tabindex="5" 
                value="Add Product">
        </p>
    </fieldset>
</form:form>
</div>
</body>
</html>

注意表单中类型为file的input元素,它将显示为一个按钮,用于选择要上传的文件。并且input元素的name属性指定为"images[0]",即绑定到表单支持对象product的images属性(List<MultipartFile>类型>)的第一个元素上。

如果想支持多个文件同时上传,只需将  <input type="file" name="images[0]"/>替换成如下:

  <input type="file" name="images" multiple/>

提交Product表单,将会调用saveProduct()方法,如果这个方法能够顺利执行,用户将会跳转到ProductDetails.jsp页面:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
    <h4>The product has been saved.</h4>
    <p>
        <h5>Details:</h5>
        Product Name: ${product.name}<br/>
        Description: ${product.description}<br/>
        Price: $${product.price}
        <p>Following files are uploaded successfully.</p>
        <ol>
        <c:forEach items="${product.images}" var="image">
            <li>${image.originalFilename}
            <img width="100" src="<c:url value="/image/"/>
            ${image.originalFilename}"/>
            </li>
        </c:forEach>
        </ol>
    </p>
</div>
</body>
</html>

该页面将会显示已经保存的Product的详细信息及其图片。

6、测试

将应用程序部署到tomcat服务器,并在网页输入以下URL:

http://localhost:8008/upload1/input-product

将会看到一个如图所示的Add Product表单,试着输入一些产品信息,并选择一个要上传的文件:

单击"Add Product"按钮,就可以看到如下所示的网页:

 

同时我们可以在tomcat服务器,upload1应用下的image目录下看到,已经上传的文件:

如果将ProductForm.jsp中的:<input type="file" name="images[0]"/>更改为如下代码:

<input type="file" name="images" multiple/>

那么就可以实现多个文件同时上传:

参考文章

[1]彻底解决springMVC中文乱码

猜你喜欢

转载自www.cnblogs.com/zyly/p/10880220.html