基于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()方法被指定了很多次。并且上述中有java.lang.Double.toString() 方法,作用是返回此Double对象的字符串表示形式。而且返回的不是小数点后2位,而是小数点后一位(注释写错了)。

打印到控制台部分数据:

已读: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();这句代码是从哪里获得输入的,文件在复制的过程中,是怎样进行流的转换的,流是从哪里去了哪里?                          

 

 

 

              

              

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值