1. 程式人生 > >基於DiskFileItemFactory實現的檔案上傳和進度監聽

基於DiskFileItemFactory實現的檔案上傳和進度監聽

             10月1假期圓滿結束,在家裡躺了5天,啥也沒幹,回來的時候家裡還給我拿了螃蟹什麼的,昨天就讓我吃了,哈哈哈。

              這幾天一直想實現以下檔案的上傳和檔案上傳時顯示進度條,看了幾個部落格,照著自己敲了一下,將自己不明白的地方也查了百度。但仍然有幾處不太理解,我會將不明白的地方在本文的下方寫出,希望大家能幫我解答一下。

              下面就是實現的過程。

一.web.xml檔案

              我們後臺使用的是servlet來處理使用者請求,那麼就要在web.xml檔案中配置我們的servlet。在表單提交時,我們可以在action中填寫對應的setvlet對映名,就可以將表單資料傳送到servlet中。

<?xml version="1.0" encoding="UTF-8"?>

<web-app>
      <!-- 配置上傳檔案所需要的sevlet -->
    <servlet>
      <servlet-name>UploadServlet</servlet-name>
      <servlet-class>com.java.controller.UploadServlet</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>UploadServlet</servlet-name>
      <url-pattern>/UploadServlet.do</url-pattern>
    </servlet-mapping>
      
</web-app>

二.所需要的jar包

             後臺使用servlet,那麼我們就需要servlet-api.jar,並且由DiskFileItemFactory,我們還需另外兩個jar包。所需要的3個jar包如下。

三.UploadServlet

            這個servlet就是用來處理使用者上傳檔案的請求的,程式碼如下:

package com.java.controller;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;


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

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;

public class UploadServlet extends HttpServlet {
    /*這個class是一個servlet,那麼就能夠通過web.xml檔案中配置的對映路徑訪問。
     *訪問的時候按照請求的方式,來決定用哪一個方法來處理請求 
     * */
	
	//重寫doGet()方法和doPost()方法。
	@Override
	public void doGet(HttpServletRequest request,HttpServletResponse response) {
		
	}
	
	@Override
	public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException {
		//首先檢查表單是否支援檔案上傳,檢查請求頭Content-type:multipart/form-data。
		if(!ServletFileUpload.isMultipartContent(request)) {
			throw new RuntimeException("該表單不支援檔案上傳");
		}
		
		/*設定響應體屬性
        response.setContentType("text/html;charset=UTF-8"); 
        */
		
		//建立核心工廠
		DiskFileItemFactory diskFileItemFactory=new DiskFileItemFactory();
		//為這個製造上傳檔案物件的工廠設定屬性。
		diskFileItemFactory.setSizeThreshold(1024*3);    //設定工廠的記憶體緩衝區大小,預設為10kb。
		diskFileItemFactory.setRepository(new File("E:\\FileUpLoad"));             //設定工廠的臨時檔案目錄:當上傳檔案大於記憶體緩衝區時,使用臨時檔案目錄快取上傳的檔案。
	    //通過工廠建立檔案上傳解析器,核心類
		ServletFileUpload servletFileUpload=new ServletFileUpload(diskFileItemFactory);
		//為這個上傳解析器設定屬性
		servletFileUpload.setSizeMax(1024*1024*2);     //設定上傳資料的最大值。所有檔案。
		servletFileUpload.setFileSizeMax(1024*1024*2);    //設定上傳檔案的最大值。單個檔案。
		servletFileUpload.setHeaderEncoding("UTF-8");    //在記憶體中儲存的資料是以某種字符集編碼的位元組陣列?
		//設定檔案上傳的監聽器
		servletFileUpload.setProgressListener(new MyProgressListener(request));  //要在資料解析之前進行監聽?
		//進行資料的解析。將表單中每個輸入項(包括表單普通標籤和表單檔案上傳標籤)封裝成一個FileItem物件。
		try {
			List<FileItem> fileItemList=servletFileUpload.parseRequest(request);
			for(FileItem fileItem:fileItemList) {
				//判斷是表單普通標籤,還是表單檔案上傳標籤。
				if(fileItem.isFormField()) {    //表單普通標籤
					String fieldName=fileItem.getFieldName();   //得到標籤的name名。
					String fieldValue=fileItem.getString("utf-8");  //得到標籤的value值。getString()用於解決亂碼問題?
					System.out.println("標籤名為:"+fieldName+",value值為:"+fieldValue);	
				}else {   //為表單的檔案上傳標籤
					String fileName=fileItem.getName();  //得到上傳檔案的檔名。IE瀏覽器的得到的是全路徑 : C:\Users\xxx\Desktop\abc.txt  ; 其他瀏覽器得到的只是檔名 : abc.txt
					String fieldName=fileItem.getFieldName();   
					String fieldValue=fileItem.getString("utf-8");   //如果要列印檔案值,那麼就會在控制檯中列印亂碼(許多)。
					System.out.println("標籤名為:"+fieldName+",value值為:"+",檔名為:"+fileName);	
					//進行檔案的上傳(將檔案儲存到伺服器中【檔案的拷貝】)。
					InputStream inputStream=fileItem.getInputStream();  //這個輸入流是從哪裡輸入?
					String parentDir=this.getServletContext().getRealPath("/WEB-INF/upload/"+fileName);  //檔案要儲存的目的目錄。和getServletContextPath()有什麼區別?ServletContext()是什麼?
					String textDir=this.getServletContext().getContextPath();
					System.out.println("RealPath:"+parentDir+",ContextPath:"+textDir);
					//進行檔案的儲存(複製)
					File file=new File(parentDir);
					FileUtils.copyInputStreamToFile(inputStream, file);   //在儲存的時候,如果資料夾和檔案不存在,則建立資料夾和檔案。
					//刪除臨時檔案
					//fileItem.delete();
					//關閉流資源
					inputStream.close();
					
					/*圖片的大小和佔用空間的區別
					 *b,kb等的轉換。
					 *
					 * 
					 * */
				}
			}
		} catch (FileUploadException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		
	}
}

             這個class是一個servlet,那麼就能夠通過web.xml檔案中配置的對映路徑訪問。訪問的時候按照請求的方式,來決定用哪一個方法來處理請求 ,我們需要重寫其中的doGet()方法和doPost()方法。

              在doPost()方法中,我們首先檢查表單是否支援檔案上傳,檢查請求頭Content-type:multipart/form-data。如果是則支援上傳。

               然後我們建立核心工廠類DiskFileItemFactory,並給它設定一些基本屬性,比如記憶體緩衝大小和指定臨時檔案。

              通過核心工廠類我們能夠得到與之相關聯的核心類ServletFileUpload。當然了,我們也要給這個核心類設定一些屬性,例如上傳檔案的總共大小,單個檔案上傳的總共大小。如果所需上傳檔案資料大於最大值,那麼就會發生異常。

               設定完核心工廠類,我們就可以設定進度監聽。

               監聽完成後,說明檔案下載完成,我們就可以對錶單提交過來各個資料進行解析。我們解析傳遞過來的request請求,解析完成後會得到一個FileItem集合。在這裡,我們可以簡單的理解為這個FileItem集合就是表單中所有表單的集合。我們通過 遍歷這個集合就可以得到每個標籤的name值,value值等信心。

               我們只對檔案型別的資訊感興趣,所以我們只需要關注表單上傳檔案標籤type=file。所以呢,在遍歷的時候,我們通過fileItem.isFormField()方法來辨別出一個標籤是表單普通標籤,還是表單上傳檔案標籤。如果是上傳檔案標籤,那麼那麼我們可以的到這個標籤的name值,和上傳檔案的檔名。這裡需要注意,這個標籤的value代表的應該是檔案本身,因為我列印value的時候,在控制檯輸出了很多亂碼,亂碼代表的應該就是檔案。

                我們通過getRealPath()來得到專案相對於伺服器的實際路徑,而getContextPath()得到的是專案的相對路徑,也就是專案名本身。

                 最後我們通過FileUtils.copyInputStreamToFile(inputStream, file)方法來將檔案拷貝到指定地方。

四.index頁面

<%@ page contentType="text/html;charset=UTF-8" %>
<HTML>
<HEAD>
    <title>上傳檔案</title>
</HEAD>
   <body>
     <div>
         <!-- 假如我們要以表單的形式進行檔案上傳,那麼表單的提交方式必須為post,並且要設定表單的 enctype 屬性值為 multipart/form-data。
                                          上傳檔案時,表單提交要使用post方式,因為這種請求是放到httpbody中,傳輸的檔案無限制。
         
                                          只有使用enctype="multipart/form-data",表單才會把檔案的內容編碼到HTML請求中,否則只會把所要上傳的檔名放到httpbody,
                                          而不是把檔案內容編碼後放到httpbody中。
                                          
                                         檔案的上傳:我們把需要上傳的資源,傳送給伺服器,在伺服器上儲存下來。
                                         
         -->
         <form action="UploadServlet.do" method="post" enctype="multipart/form-data">
             <table>
                 <tr>
                     <td>姓名</td>
                     <td><input name="name" type="text"></td>
                     <td>別名</td>
                     <td><input name="namex" type="text"></td>
                     <td>年齡</td>
                     <td><select id="selectAge" name="selectAge">
                                <option value="0">----請選擇年齡----</option>	
                       			<option value="23">23</option>
                       			<option value="24">24</option>
                       			<option value="25">25</option>
                       		</select>
                      </td>
                     <td>上傳檔案</td>
                     <td><input name="fileUpLoad" type="file"></td>
                     <td><input type="submit" value="上傳"></td>
                 </tr>
             </table>
         </form>
     </div>
     
   </body>
</HTML>

                   假如我們要以表單的形式進行檔案上傳,那麼表單的提交方式必須為post,並且要設定表單的 enctype 屬性值為 multipart/form-data。                       上傳檔案時,表單提交要使用post方式,因為這種請求是放到httpbody中,傳輸的檔案無限制。                                 只有使用enctype="multipart/form-data",表單才會把檔案的內容編碼到HTML請求中,否則只會把所要上傳的檔名放到httpbody,而不是把檔案內容編碼後放到httpbody中。                       檔案的上傳:我們把需要上傳的資源,傳送給伺服器,在伺服器上儲存下來。

五.MyProgressListener

                      這個檔案就是從來監聽檔案上傳的進度的。

package com.java.controller;

import org.apache.commons.fileupload.ProgressListener;

import java.text.NumberFormat;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.ProgressListener;

public class MyProgressListener implements ProgressListener {
	
	//定義session,擴大request作用域為session。
	private HttpSession httpSession;
	public MyProgressListener(HttpServletRequest request) {
		httpSession=request.getSession();
	}
    /**
     * arg0表示已經讀到的內容,
     * arg1表示需要讀取的資料總數,
     * arg2表示讀取當前的列表項
     */
	@Override
	public void update(long arg0, long arg1, int arg2) {
		//進行資料的格式化,將位元組B轉換為MB。
		double read=arg0/1024.0/1024.0;
		double totle=arg1/1024.0/1024.0;
		double percent=read/totle;   //以讀取的%比。
		
        String read_format=dataFormat(read);
        String total_format=dataFormat(totle);
        NumberFormat numberFormat=NumberFormat.getPercentInstance();
        String percentReault=numberFormat.format(percent);
        
        //將資訊存入session
        httpSession.setAttribute("progress", percentReault);
        System.out.println("已讀:"+arg0+"----總數:"+arg1+"----當前Item:"+arg2);
        System.out.println("已讀:"+read+"MB"+"----總數:"+totle+"MB"+"----讀取進度:"+percent+"%");
        System.out.println("已讀:"+read_format+"----總數:"+total_format+"----讀取進度:"+percentReault);
        System.out.println("------------------------");
	}
	
    public String dataFormat(double data){
    	//進行資料的個格式化,
        String formdata="";
        if (data>=1024*1024) {    //大於等於1M
            formdata=Double.toString(data/1024/1024)+"M";
        }else if(data>=1024){//大於等於1KB
            formdata=Double.toString(data/1024)+"KB";
        }else{//小於1KB
            formdata=Double.toString(data)+"byte";
        }
        return formdata.substring(0, formdata.indexOf(".")+2);    //取小數點後兩位。
        
        /*double型別的資料有幾個小數點。
         *該方法中的其他方法的作用。
         * */
    }

}

                   通過列印到控制檯的資料,我們可以看到update()方法被指定了很多次。

列印到控制檯部分資料:

已讀:806162----總數:827192----當前Item:4
已讀:0.7688159942626953MB----總數:0.7888717651367188MB----讀取進度:0.9745766400061896%
已讀:0.7----總數:0.7----讀取進度:97%
------------------------
已讀:810216----總數:827192----當前Item:4
已讀:0.7726821899414062MB----總數:0.7888717651367188MB----讀取進度:0.9794775578100369%
已讀:0.7----總數:0.7----讀取進度:98%
------------------------
已讀:810300----總數:827192----當前Item:4
已讀:0.7727622985839844MB----總數:0.7888717651367188MB----讀取進度:0.9795791061809108%
已讀:0.7----總數:0.7----讀取進度:98%
------------------------
已讀:814354----總數:827192----當前Item:4
已讀:0.7766284942626953MB----總數:0.7888717651367188MB----讀取進度:0.9844800239847581%
已讀:0.7----總數:0.7----讀取進度:98%
------------------------
已讀:818408----總數:827192----當前Item:4
已讀:0.7804946899414062MB----總數:0.7888717651367188MB----讀取進度:0.9893809417886053%
已讀:0.7----總數:0.7----讀取進度:99%
------------------------
已讀:818492----總數:827192----當前Item:4
已讀:0.7805747985839844MB----總數:0.7888717651367188MB----讀取進度:0.9894824901594793%
已讀:0.7----總數:0.7----讀取進度:99%
------------------------
已讀:822546----總數:827192----當前Item:4
已讀:0.7844409942626953MB----總數:0.7888717651367188MB----讀取進度:0.9943834079633266%
已讀:0.7----總數:0.7----讀取進度:99%
------------------------
已讀:826600----總數:827192----當前Item:4
已讀:0.7883071899414062MB----總數:0.7888717651367188MB----讀取進度:0.9992843257671737%
已讀:0.7----總數:0.7----讀取進度:100%
------------------------
已讀:826684----總數:827192----當前Item:4
已讀:0.7883872985839844MB----總數:0.7888717651367188MB----讀取進度:0.9993858741380478%
已讀:0.7----總數:0.7----讀取進度:100%
------------------------
已讀:827192----總數:827192----當前Item:4
已讀:0.7888717651367188MB----總數:0.7888717651367188MB----讀取進度:1.0%
已讀:0.7----總數:0.7----讀取進度:100%
------------------------
標籤名為:name,value值為:qiao
標籤名為:namex,value值為:qiaox
標籤名為:selectAge,value值為:24
標籤名為:fileUpLoad,value值為:,檔名為:桌布.png
RealPath:F:\Development\DevelopmentTool\apache-tomcat-9.0.4-windows-x64\apache-tomcat-9.0.4\webapps\FileUploadAndProgressBar\WEB-INF\upload\桌布.png,ContextPath:/FileUploadAndProgressBar

六.不明白的問題

              在給工廠設定記憶體快取時,如果檔案大小大於記憶體快取,是否將檔案全部快取到臨時檔案中?還是將記憶體快取佔滿後,其它的資料在放到臨時檔案中?

              設定相應體的屬性 response.setContentType("text/html;charset=UTF-8");的意義和作用是什麼?

              給檔案解析器設定servletFileUpload.setHeaderEncoding("UTF-8");的意義和作用是什麼?

              進度監聽是否需要在解析request請求之前進行監聽?這個我猜是,因為列印的時候先把檔案下載完才進行解析的。

              InputStream inputStream=fileItem.getInputStream();這句程式碼是從哪裡獲得輸入的,檔案在複製的過程中,是怎樣進行流的轉換的,流是從哪裡去了哪裡?