前面一篇博文介绍了springMVC实现文件的上传

这里介绍springMVC实现文件的下载的两种方式:

有朋友问为什么他点击下载没有反应,检查发现,他jsp中就写了个url确不写参数filePath。

不多说,我先把jsp代码贴出来:(这里动态获取文件的绝对路径filePath)

<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>文件下载</title>

</head>
<body>
	<div><h3>${message }</h3></div>
	<div><a href="/spring_mvc_annotation/download?filePath=${user.url }"  target="_blank">点击下载</a></div>
</body>
</html>



一、基于ResponseEntity的下载

package com.hfxt.controller;

import java.io.File;
import java.net.URLDecoder;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping(value="/")
public class LoginController {
	
	@RequestMapping(value="/download")
	public ResponseEntity<byte[]> download(String filePath){
		HttpHeaders headers = new HttpHeaders();
		byte[] buf = new byte[1024000];
		try {
			File file = new File(URLDecoder.decode(filePath, "UTF-8"));//url解码
			String fileName = new String("liupch.txt".getBytes("UTF-8"), "iso-8859-1");// 为了解决中文名称乱码问题
			headers.setContentDispositionFormData("attachment", fileName);
			headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
			FileUtils.readFileToByteArray(file);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return new ResponseEntity<byte[]>(buf,headers, HttpStatus.CREATED);//3个参数:请求体、请求头和状态码 
	}
	
}


二、java通用下载

1、controller层

注意:如果是简单项目,不需要记录下载详情的话,其中DownloadRecord就不需要声明,注释掉的内容可以删除,并且公共类DownloadRecord不需要定义。

package com.hfxt.controller;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.hfxt.entity.User;
@Controller
@RequestMapping(value="/")
public class LoginController {
	//下载
	@RequestMapping(value="/download")
	public static void download(String filePath,HttpServletRequest request, HttpServletResponse response) 
			throws Exception {
		String path=URLDecoder.decode(filePath, "utf-8");//解码
		String fileName=path.substring(path.lastIndexOf("\\")+1);//截取文件名称
		
		// 声明本次下载状态的记录对象
		/*DownloadRecord downloadRecord = new DownloadRecord(fileName, filePath,request);*/
		
		// 设置响应头和客户端保存文件名
		response.setCharacterEncoding("utf-8");
		response.setContentType("multipart/form-data");
		response.setHeader("Content-Disposition", "attachment;fileName="+ fileName);
		
		// 用于记录以完成的下载的数据量,单位是byte
		//long downloadedLength = 0l;
		
		try {
			// 打开本地文件流
			InputStream inputStream = new FileInputStream(path);
			// 激活下载操作
			OutputStream os = response.getOutputStream();

			// 循环写入输出流
			byte[] b = new byte[2048];
			int length;
			while ((length = inputStream.read(b)) > 0) {
				os.write(b, 0, length);
				//downloadedLength += b.length;
			}
			// 这里主要关闭。
			os.close();
			inputStream.close();
		} catch (Exception e) {
			/*downloadRecord.setStatus(DownloadRecord.STATUS_ERROR);*/
			throw e;
		}
		// 存储记录
		/*downloadRecord.setStatus(DownloadRecord.STATUS_SUCCESS);
		downloadRecord.setEndTime(new Timestamp(System.currentTimeMillis()));
		downloadRecord.setLength(downloadedLength);*/
	}
}

2、公共类DownloadRecord(简单项目可以省略不定义,若使用,将controller中注释内容放开)

public class DownloadRecord {
    public static final int STATUS_ERROR = 0;
    public static final int STATUS_SUCCESS = 1;
    private String uid;
    private String ip;
    private int port;
    private String ua;
    private String fileName;
    private String filePath;
    private long length;
    private int status;
    private Timestamp startTime;
    private Timestamp endTime;

    public DownloadRecord() {
    }

    public DownloadRecord(String fileName, String filePath, 
    HttpServletRequest request) {
        this.uid = UUID.randomUUID().toString().replace("-","");
        this.fileName = fileName;
        this.filePath = filePath;
        this.ip = request.getRemoteAddr();
        this.port = request.getRemotePort();
        this.ua = this.ua = request.getHeader("user-agent");
        this.startTime = new Timestamp(System.currentTimeMillis());
    }

    /** getter and setter **/
}

三、两种方法的比较

  • 基于ResponseEntity的实现的局限性还是很大,从代码中可以看出这种下载方式是一种一次性读取的下载方式,在文件较大的时候会直接抛出内存溢出(我自己亲测一个1.8G的文件在执行下载操作的时候直接抛出了内存溢出)。还有就是这种方式在进行下载统计的时候也存在局限性,无法统计在下载失败的情况已完成下载量,因此限制了对下载的功能扩展。虽然这种实现方式有局限性,但是也有着优点——简洁。在很多时候我们并不需要那么复杂的下载功能时,这种实现就应该是首选了。
  • 然而java通用下载实现在功能上比第一种实现更加丰富,对下载的文件大小无限制(循环读取一定量的字节写入到输出流中,因此不会造成内存溢出,但是在下载人数过多的时候应该还是出现一些异常,不过下载量较大的文件一般都会使用ftp服务器来做吧),另外因为是这种实现方式是基于循环写入的方式进行下载,在每次将字节块写入到输出流中的时都会进行输出流的合法性检测,在因为用户取消或者网络原因造成socket断开的时候,系统会抛出SocketWriteException,系统可以捕捉这个过程中抛出的异常,当捕捉到异常的时候我们可以记录当前已经传输的数据量,这样就可以完成下载状态和对应状态下载量和速度之类的数据记录。另外这种方式实现方式还可以实现一种断点续载的功能。