當我們實現一個文件下載功能時,大多數人是通過Strust等框架實現的。Strust框架把底層的文件下載細節隱藏了起來,使我們不得其要領。下面我通過一個程序示例來再現通過Servlet下載文件的細節和原理。
程序源碼
web.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <!--可以配置一些Listener 或 Filter 來觀察一些對象的生命周期 --> <servlet> <servlet-name>download_servlet</servlet-name> <servlet-class>edu.shao.webapp.sample.DownloadServlet</servlet-class> <init-param> <param-name>fileRoot</param-name> <param-value>d:/</param-value> </init-param> <init-param> <param-name>contentType</param-name> <param-value>application/octet-stream</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>download_servlet</servlet-name> <url-pattern>/download</url-pattern> </servlet-mapping> </web-app>
DownloadServlet.java
1 package edu.shao.webapp.sample; 2 3 import java.io.BufferedInputStream; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.IOException; 7 import java.net.URLEncoder; 8 9 import javax.servlet.ServletConfig; 10 import javax.servlet.ServletException; 11 import javax.servlet.ServletOutputStream; 12 import javax.servlet.http.HttpServlet; 13 import javax.servlet.http.HttpServletRequest; 14 import javax.servlet.http.HttpServletResponse; 15 16 import org.apache.logging.log4j.LogManager; 17 import org.apache.logging.log4j.Logger; 18 19 public class DownloadServlet extends HttpServlet{ 20 private static final long serialVersionUID = 1L; 21 public static Logger logger=LogManager.getLogger(DownloadServlet.class); 22 23 private String contentType; 24 private String enc="UTF-8"; 25 private String fileRoot; 26 27 @Override 28 public void init(ServletConfig config) throws ServletException { 29 contentType = config.getInitParameter("contentType"); 30 fileRoot = config.getInitParameter("fileRoot"); 31 } 32 33 @Override 34 public void doGet(HttpServletRequest req, HttpServletResponse resp) 35 throws ServletException, IOException { 36 logger.debug("Do Get Method."); 37 String fileName=req.getParameter("fileName"); 38 String filePath=fileRoot+File.separator+fileName; 39 40 File downloadFile=new File(filePath); 41 if (downloadFile.exists()) { 42 logger.info("File exist"); 43 44 resp.setContentType(contentType); 45 Long length=downloadFile.length(); 46 resp.setContentLength(length.intValue()); 47 fileName = URLEncoder.encode(downloadFile.getName(), enc); 48 resp.addHeader("Content-Disposition", "attachment; filename=" + fileName); 49 50 ServletOutputStream servletOutputStream=resp.getOutputStream(); 51 FileInputStream fileInputStream=new FileInputStream(downloadFile); 52 BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream); 53 int size=0; 54 byte[] b=new byte[4096]; 55 while ((size=bufferedInputStream.read(b))!=-1) { 56 logger.info("write to output stream.."); 57 servletOutputStream.write(b, 0, size); 58 } 59 servletOutputStream.flush(); 60 servletOutputStream.close(); 61 bufferedInputStream.close(); 62 }else { 63 logger.info("File is not exist"); 64 } 65 } 66 67 }
解釋:
1、contentType用於定義用戶的瀏覽器如何顯示將要加載的數據,或者如何處理將要加載的數據。如網頁的contentType是text/html,jpeg圖片的contentType是image/jpeg。如果不知道文件類型,可以設置為二進制流文件 application/octet-stream
2、contentType和fileRoot(待下載文件的目錄)通過web.xml配置。
3、resp.setContentType()、resp.setContentLength()、resp.addHeader("Content-Disposition", "attachment; filename=" + fileName); 三個調用設置一些必要的響應報頭(reponse header)。
運行結果:
1、瀏覽器地址欄輸入:http://localhost:8080/test/download?fileName=test.mkv 回車(文件“d:/test.mkv”的大小為700M)
2、瀏覽器彈出下載提示窗口,但未點擊保存。控制台先打印幾條日志,然后停滯,說明已經向輸出流寫入了4kB左右(程序中byte數組的大小為4096B)的數據了。
3、選擇保存位置后,點擊保存。此時控制台開始瘋狂地輸出log。。。
4、最后下載完成,此次Request結束。
小結:
文件下載的原理非常簡單,就是把數據從一個輸入流中讀出數據,再寫入一個輸出流。這里的輸入流是FileInputStream(為了提高速度,對其包裝了一個裝飾類BufferedInputStream,以提供緩沖功能),輸出流是ServletOutputStream。
源碼中的定義,public abstract class ServletOutputStream extends OutputStream。說明ServletOutputStream繼承了OutputStream,它和FileOutputStream等輸出流是一樣的。