SprngMVC 学习(六)文件上传

Spring MVC同样支持文件上传功能,不过该功能默认未开启,因为可能有些开发者可能希望自己处理文件上传过程。

Spring的文件上传功能在org.springframework.web.multipart包下,有两个MultipartResolver实现用来支持文件上传功能:

  • 一个是基于Commons FileUpload
  • 另一个基于Servlet 3.0 multipart请求解析功能

这两个MultipartResolver差不多,一个需要添加Commons FileUpload的依赖,另一个需要在Servlet 3.0容器上运行。大家可以根据需要选择。

如果想要使用Spring的文件上传功能,需要在文件上下文中配置MultipartResolver。

定义MultipartResolver

使用Commons FileUpload MultipartResolver

在配置文件中添加如下一段,我们可以在Bean定义中配置上传文件大小等属性。

<!-- 文件上传 -->
    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

        <!-- 设置上传文件的最大尺寸位30M -->
        <property name="maxUploadSize" value="31457280" />
        <property name="defaultEncoding" value="UTF-8" />
        <!-- 是否延迟加载,在需要的时候才进行上传文件的解析 -->
        <property name="resolveLazily" value="true" />
        <!-- 文件上传的临时路径,文件上传完成后,临时目录中的临时文件会被自动清除 -->
        <property name="uploadTempDir" value="upload/temp" />
    </bean>

defaultEncoding必须和JSP的pageEncoding属性一致,以便正确读取表单的内容。

注意:引入commons-fileupload.jar、commons-io.jar 两个包。


使用Servlet 3.0 MultipartResolver

由于使用的是Servlet API提供的文件上传功能,所以文件大小等配置需要在web.xml中进行配置。我们需要在dispathcer-servlet中添加<multipart-config>标签,它有四个子标签来设置文件上传的属性。

这四个属性如下:

  • location ,临时文件的存放位置,这个路径必须是绝对路径。
  • fileSizeThreshold,文件起始值,大于该值文件才会被临时保存,单位是字节。
  • MaxFileSize,单个文件的最大值,单位是字节,不管上传几个文件,只要有一个文件大小超过该值就会抛出IllegalStateException。
  • maxRequestSize,文件上传请求的最大值,单位是字节,主要作用是当上传多个文件是配置整个请求的大小,当超出该值是抛出IllegalStateException。
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
    <multipart-config>
        <max-file-size>100000</max-file-size>
    </multipart-config>
</servlet>

然后我们在Spring配置文件中添加Servlet 3.0 MultipartResolver。

<bean id="multipartResolver"
        class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

上传文件

配置好了Multipart解析器之后,我们就可以接收文件了。

首先定义一个页面fileupload.jsp,用于上传文件并显示服务器中的文件。

注意:在表单中我们必须添加<code>enctype="multipart/form-data"</code>才能正确的上传文件。

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件上传</title>
    <meta charset="utf-8">

</head>
<body>
<h2>文件上传</h2>
<form action="<c:url value="/mvc/uploadfile.html"/>"
      method="post" enctype="multipart/form-data">
    <label for="file">文件</label>
    <input type="file" name="file" id="file"/>
    <br>
    <input type="submit" value="提交">${result}
</form>

</body>
</html>

然后就可以在控制器中获取文件了。由于MultipartFile和它对应的临时文件会在方法结束之后被Spring清除,所以我们必须在方法中将文件保存到合适的地方。

在请求方法中,我们可以像普通参数那样获取上传的文件,只不过文件对应的类型是MultipartFile,如果使用的是Servlet 3.0标准的,那么类型还可以是javax.servlet.http.Part。

下面写了两个处理方法,第一个将MultipartFile转化为File保存在web目录下的upload文件夹中,第二个方法用于获取保存在该文件夹的文件。

    @RequestMapping("/uploadfile.html")
    public String uploadFile(@RequestParam("file") MultipartFile multipartFile) throws IOException {
        
        // 获取到web的根目录
        String path = System.getProperty("tansungWeb.root");
        
        if (!multipartFile.isEmpty()) {
            // 获取到源文件名
            String filename = multipartFile.getOriginalFilename();
            // 获取文件的后缀名
            String suffix = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
            // 如果后缀为mp3的,一上传文件原名保存,否则以时间戳文文件名进行保存
            if (!suffix.equals("mp3")) {
                //FileUtils.copyInputStreamToFile(multipartFile.getInputStream(),new File(path + "//upload//", System.currentTimeMillis() + "." + suffix));
                multipartFile.transferTo(new File(path + "//upload//", System.currentTimeMillis() + "." + suffix));
            } else {
                FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), new File(path + "//upload//", filename));
            }
        }
        model.addAttribute("result", "上传成功!");
        return "fileupload";
    }
}

Spring MVC会将上传文件绑定到MultipartFile对象中。MultipartFile提供了获取上传文件内容、文件名等内容,通过其transferTo()方法还可将文件存储到硬件中。

// 获取文件数据
byte[] getBytes();
// 获取文件MIME类型,如image/jprg等
String getContentType();
// 获取文件流
InputStream getInputStream();
// 获取表单中文件组件的名字
String getName();
// 获取上传文件的原名
String getOriginalFilename();
// 获取文件的字节大小,单位为byte
long getSize();
// 是否有上传的文件
boolean isEmpty();
// 可以使用该文件将上传文件保存到一个目标文件中
void transferTo(File dest);

将上传的文件列出来,实现下载的功能。

    @RequestMapping("/listfile")
    public String listFile(Model model)
            throws IOException, ServletException {
        // 获取上传文件的目录
        String uploadFilePath = System.getProperty("tansungWeb.root") + "upload";
        // 存储要下载的文件名
        Map<String, String> fileNameMap = new HashMap<String, String>();
        // 递归遍历filepath目录下的所有文件和目录,将文件的文件名存储到map集合中
        // File既可以代表一个文件也可以代表一个目录
        listfile(new File(uploadFilePath), fileNameMap);
        // 将Map集合发送到list.jsp页面进行显示
        model.addAttribute("fileNameMap", fileNameMap);
        
        return "list";
    }

    /**
     * @Method: listfile
     * @Description: 递归遍历指定目录下的所有文件
     * @param file
     *            即代表一个文件,也代表一个文件目录
     * @param map
     *            存储文件名的Map集合
     */
    public void listfile(File file, Map<String, String> map) {
        // 如果file代表的不是一个文件,而是一个目录
        if (!file.isFile()) {
            // 列出该目录下的所有文件和目录
            File files[] = file.listFiles();
            // 遍历files[]数组
            for (File f : files) {
                // 递归
                listfile(f, map);
            }
        } else {
            /*
             * 处理文件名,上传后的文件是以uuid_文件名的形式去重新命名的,去除文件名的uuid_部分
             * file.getName().indexOf("_")检索字符串中第一次出现"_"字符的位置,如果文件名类似于:
             * 9349249849-88343-8344_阿凡达.avi
             * 那么file.getName().substring(file.getName().indexOf("_")+1)
             * 处理之后就可以得到阿凡达.avi部分
             */
            String realName = file.getName().substring(file.getName().indexOf("_") + 1);
            // file.getName()得到的是文件的原始名称,这个名称是唯一的,因此可以作为key,realName是处理过后的名称,有可能会重复
            map.put(file.getName(), realName);
        }
    }

编写list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>  
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
            + path + "/";
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>下载列表显示页面</title>
</head>
<body>
    <!-- 遍历Map集合 -->  
    <c:forEach var="files" items="${fileNameMap}"> 
        
        <c:url value="/mvc/download" var="downurl">
            <c:param name="filename" value="${files.key}"></c:param>
        </c:url> 
        ${files.value}<a href="${downurl}">下载</a>  
         <!-- 
        <a href="<c:url value="/mvc/download?filename=${files.value}"/>">${files.value}</a>
         -->
        <br/>  
    </c:forEach>

</body>
</html>

下载文件

下载功能的实现

@RequestMapping("/download")
public void downloadFile(@RequestParam("filename") String filename, Model model, HttpServletRequest req, HttpServletResponse resp)
        throws IOException {

    filename = new String(filename.getBytes("iso8859-1"), "UTF-8");
    //上传的文件都是保存在/WEB-INF/upload目录下的子目录当中
    String fileSaveRootPath = System.getProperty("tansungWeb.root") + "upload";
    // 得到要下载的文件
    File file = new File(fileSaveRootPath + "\\" + filename);
    
    // 如果文件不存在
    if (!file.exists()) {
        model.addAttribute("message", "您要下载的资源已被删除!!");
    }
    // 处理文件名
    String realname = filename.substring(filename.indexOf("_") + 1);
    // 设置响应头,控制浏览器下载该文件
    resp.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
    // 读取要下载的文件,保存到文件输入流
    FileInputStream in = new FileInputStream(fileSaveRootPath + "\\" + filename);
    // 创建输出流
    OutputStream out = resp.getOutputStream();
    // 创建缓冲区
    byte buffer[] = new byte[1024];
    int len = 0;
    // 循环将输入流中的内容读取到缓冲区当中
    while ((len = in.read(buffer)) > 0) {
        // 输出缓冲区的内容到浏览器,实现文件下载
        out.write(buffer, 0, len);
    }
    // 关闭文件输入流
    in.close();
    // 关闭输出流
    out.close();
    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容