文档章节

Spring MVC的文件下载(更优雅的方式进行)

阿信sxq
 阿信sxq
发布于 2017/05/13 17:36
字数 1002
阅读 2023
收藏 69

这是对另一篇博客的升级改造

背景

前一段时间写了一篇博客介绍springmvc的文件下载实现方法,里面使用的是先读进内存,然后构造响应头实现的下载,那种方法看起来没有问题,但是实际上有一个可能出现的问题————要下载的文件很大的情况,在那种情况下有可能出现OOM,而且使用那种方法进行设计的时候,接口也不是很好————下载文件,但是返回了一个字节数组,应该返回“文件”啊,并且文件名还要单独请求。

所以综合那篇博客评论里面有人提出来的改进方法,对下载文件的实现进行了调整。

底层文件信息的提供

新建一个“下载文件信息”的类,用于保存下载文件的相关信息。

import java.io.File;

/**
 * 下载文件的信息模型
 *
 * @author 阿信sxq
 *
 */
public class DownloadFileInfoVO {

    private String fileName;
    private File file;

    public DownloadFileInfoVO() {}

    public DownloadFileInfoVO(String fileName, File file) {
        this.fileName = fileName;
        this.file = file;
    }

    //setter、getter

}

可能有人会问为什么“文件名”要单独保存,File里面不是有文件名吗?这是应对文件存储时使用的文件名和下载给用户看的文件名不一致的情况,这种情况非常常见。

接下来就是获取这个“信息”了,根据实际业务进行编写,这里来一个简单的

    public DownloadFileInfoVO downloadFile(String fileName) {
        if (isBackupFileExists(fileName)) {
            File file = new File(BACKUP_FILE_PATH, fileName);
            return new DownloadFileInfoVO(fileName, file);
        } else {
            throw new NotFoundException();
        }
    }

这里文件的实际文件名和返回的文件名一致,不一致的情景例如:

    public DownloadFileInfoVO dowloadFile(Integer fileId) {
        if (voiceFileDao.exists(fileId)) {
            VoiceFile voiceFile = voiceFileDao.findOne(fileId);
            String fileName = voiceFile.getFileName() + "." + voiceFile.getFileFormat();
            File file = new File(VoiceFile.FILE_PATH, fileName);
            String showName = voiceFile.getShowName() + "." + voiceFile.getFileFormat();
            return new DownloadFileInfoVO(showName, file);
        } else {
            throw new NotFoundException();
        }
    }

异常情况

这两个代码段里面都有抛出一个自定义异常,实际中还有更多的异常,比如需要即时生成文件的请求完全有可能生成出错,关于这些一场的处理可以查看前一篇博客,里面有介绍怎么处理

控制器方法

控制器接收请求之后获取下载文件的信息,然后生成下载文件的响应,@Controller返回要使用@ResponseBody注解,否则会因为找不到视图文件出错,当然,如果是@RestController就不存在这个问题了

    @RequestMapping(value = "/download-backup", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<Resource> downloadBackupFile(@RequestParam String fileName) {
        DownloadFileInfoVO fileInfo = backupService.downloadFile(fileName);
        return downloadResponse(fileInfo);
    }

生成下载响应信息

由于一个奇葩的浏览器————IE的存在,响应的时候需要对它单独处理,同事响应给用户的文件名中有可能包含一些不是英文和数字的字符,如汉语,也需要进行处理

    protected ResponseEntity<org.springframework.core.io.Resource> downloadResponse(
            DownloadFileInfoVO fileInfo) {
        File file = fileInfo.getFile();
        String fileName = fileInfo.getFileName();
        org.springframework.core.io.Resource body = new FileSystemResource(file);

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        String header = request.getHeader("User-Agent").toUpperCase();
        HttpStatus status = HttpStatus.CREATED;
        try {
            if (header.contains("MSIE") || header.contains("TRIDENT") || header.contains("EDGE")) {
                fileName = URLEncoder.encode(fileName, "UTF-8");
                fileName = fileName.replace("+", "%20");    // IE下载文件名空格变+号问题
                status = HttpStatus.OK;
            } else {
                fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
            }
        } catch (UnsupportedEncodingException e) {}

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", fileName);
        headers.setContentLength(file.length());

        return new ResponseEntity<org.springframework.core.io.Resource>(body, headers, status);
    }

这里使用的Resource是完整限定名,那是因为要和进行对象注入的注解@Resource冲突,这样写才不会冲突。

同时,也有人说使用FileSystemResource似乎在某些情况下也有问题,需要使用UrlResource才不会有问题,这个倒是没有去验证

小结

修改为这样的方式实现文件下载之后,各个接口更加便于理解,稳定性增加,是一种更好的解决方法。

写程序就是这样,不断追求更好的解决方法,永远不满足于“能够运行”!

© 著作权归作者所有

共有 人打赏支持
阿信sxq

阿信sxq

粉丝 226
博文 82
码字总数 72407
作品 1
成都
后端工程师
私信 提问
加载中

评论(5)

阿信sxq
阿信sxq

引用来自“如梦技术”的评论

2点小建议:
1. 这里可以采用Charset,而非字符串的UTF-8。
jdk7里面用StandardCharsets,低版本的可以自己用Charset.forName。
fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
2. URLEncoder.encode可以换成spring里面的UriUtils.encode,就不用自己替换+号了。
恩,待会儿看看
如梦技术
如梦技术
2点小建议:
1. 这里可以采用Charset,而非字符串的UTF-8。
jdk7里面用StandardCharsets,低版本的可以自己用Charset.forName。
fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
2. URLEncoder.encode可以换成spring里面的UriUtils.encode,就不用自己替换+号了。
如梦技术
如梦技术
阿信sxq
阿信sxq

引用来自“bblzjp”的评论

很详细:thumbsup:

回复@bblzjp : 谢谢
开源中国花式抖腿冠军
开源中国花式抖腿冠军
很详细:thumbsup:
Spring、Spring Boot与Spring MVC

总论 Spring框架就像一个家族,有众多衍生产品例如boot、security、jpa等等。但他们的基础都是Spring的ioc和aop。ioc提供了依赖注入的容器,aop解决了面向横切面的编程;然后在此二者的基础上...

临江仙卜算子
05/08
0
2
有关Spring3.x 整合myBatis3.1的轻量级框架简要说明

主题:有关Spring3.x 整合myBatis3.1的轻量级框架 对于现在主流的j2ee企业级开发而言,ssh(struts+hibernate+spring)依然是一个事实的标准。 由struts充当的mvc调度控制;hibernate的orm持...

后海
2013/02/01
0
0
fastupload-springmvc 0.5.5 发布

fastupload-springmvc是利用fastupload开源组件Spring MVC框架写的文件上传插件,使用此插件,能在Controller中以注解的方式来获取上传的文件,然后对其进行操作。比使用fastupload核心API更...

仪山湖
2013/04/15
1K
8
【微服务】使用spring cloud搭建微服务框架,整理学习资料

写在前面   使用spring cloud搭建微服务框架,是我最近最主要的工作之一,一开始我使用bubbo加zookeeper制作了一个基于dubbo的微服务框架,然后被架构师否了,架构师曰:此物过时。随即,我...

grootzhang
06/27
0
0
Spring Boot干货系列: (一)优雅的入门篇

     前言   Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社区中热度一直很高,所以决定花时间来了解和学习,为自己做技术储备。   正文   首先声明,Spr...

后端编程嘟
2017/03/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

在ubuntu下选择crontab计划任务的编辑器

在ubuntu下,首次编辑crontab计划任务的时候,会提示让选择编辑器。由于对nano编辑器不是很熟悉,若是选择nova编辑的话,会有些麻烦。 可以重置编辑器,方法如下: [root@wang ~]# select-...

季书歌
35分钟前
4
0
在线BASE64加密解密、UrlEncode编码/解码、native/ascii在线转换工具 -toolfk程序员工具网

本文要推荐的[ToolFk]是一款程序员经常使用的线上免费测试工具箱,ToolFk 特色是专注于程序员日常的开发工具,不用安装任何软件,只要把内容贴上按一个执行按钮,就能获取到想要的内容结果。T...

toolfk
35分钟前
1
0
laravel命令

https://blog.csdn.net/aaroun/article/details/79140618

vio小黑
36分钟前
3
0
记录一个vue directive实现点击指令外部区域调用函数的方案

根据directive提供的API来写一个点击外部区域可以让其下拉列表消失的菜单 <div id="app" v-clock> <div class="main" v-clickoutside="handleClose"> <button @click="show = !show">点......

呵呵闯
40分钟前
3
0
Oracle一列的多行数据拼成一行显示字符

Oracle一列的多行数据拼成一行显示字符 oracle 提供了两个函数WMSYS.WM_CONCAT 和 ListAgg函数。 先介绍:WMSYS.WM_CONCAT 例: id name 1 aa 2 bb 3 cc 要的结果是"aa,bb,cc" select WMSYS...

voole
40分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部