JavaWeb文件上传与下载功能解析

时间:2022-02-05 10:04:44

在开发过程中文件的上传下载很常用。这里简单的总结一下:

1.文件上传必须满足的条件:
a、 页面表单的method必须是post 因为get传送的数据太小了
b、 页面表单的enctype必须是multipart/form-data类型的
c、 表单中提供上传输入域
代码细节: 客户端表单中:<form enctype="multipart/form-data"/>
(如果没有这个属性,则服务端读取的文件路径会因为浏览器的不同而不同)
服务端ServletInputStream is=request.getInputStream();用流的方式获取请求正文内容,进一步的解析。

JavaWeb文件上传与下载功能解析

2.上传文件的细节:
(1)为什么设置表单类型为:multipart/form-data.是设置这个表单传递的不是key=value值。传递的是字节码.
表单与请求的对应关系:

JavaWeb文件上传与下载功能解析

如上可以看出在设置表单类型为:multipart/form-data之后,在HTTP请求体中将你选择的文件初始化为二进制,如上图中的Cookie之下的一串的随机字符串下的内容。
但注意,在标识文件(即一串随机字符串)所分割出来的文件字节码中有两行特殊字符,即第一行内容文件头和一行空行。之后的第三行才是二进制的文件内容。
所以在服务端接受客户端上传的文件时,获取HTTP请求参数中的文件二进制时,要去掉前三行。

3.自己手工解析上传的txt文件:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 如果一个表单的类型是post且enctype为multipart/form-date
 * 则所有数据都是以二进制的方式向服务器上传递。
 * 所以req.getParameter("xxx")永远为null。
 * 只可以通过req.getInputStream()来获取数据,获取正文的数据
 *
 * @author wangxi
 *
 */
public class UpServlet extends HttpServlet {
 public void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  req.setCharacterEncoding("UTF-8");
  String txt = req.getParameter("txt");//返回的是null
  System.err.println("txt is :"+txt);
  System.err.println("=========================================");
  InputStream in = req.getInputStream();
//  byte[] b= new byte[1024];
//  int len = 0;
//  while((len=in.read(b))!=-1){
//   String s = new String(b,0,len);
//   System.err.print(s);
//  }
  BufferedReader br = new BufferedReader(new InputStreamReader(in));
  String firstLine = br.readLine();//读取第一行,且第一行是分隔符号,即随机字符串
  String fileName = br.readLine();//第二行文件信息,从中截取出文件名
  fileName = fileName.substring(fileName.lastIndexOf("\\")+1);// xxxx.txt"
  fileName = fileName.substring(0,fileName.length()-1);
 
  br.readLine();
  br.readLine();
  String data = null;
  //获取当前项目的运行路径
  String projectPath = getServletContext().getRealPath("/up");
  PrintWriter out = new PrintWriter(projectPath+"/"+fileName);
  while((data=br.readLine())!=null){
   if(data.equals(firstLine+"--")){
    break;
   }
   out.println(data);
  }
  out.close();
 }
}

4.使用apache-fileupload处理文件上传:
框架:是指将用户经常处理的业务进行一个代码封装。让用户可以方便的调用。
目前文件上传的(框架)组件:
Apache—-fileupload -
Orialiy – COS – 2008() -
Jsp-smart-upload – 200M。
用fileupload上传文件:

需要导入第三方包:

    Apache-fileupload.jar – 文件上传核心包。
    Apache-commons-io.jar – 这个包是fileupload的依赖包。同时又是一个工具包。

核心类:

    DiskFileItemFactory – 设置磁盘空间,保存临时文件。只是一个具类。
    ServletFileUpload  - 文件上传的核心类,此类接收request,并解析reqeust。
    ServletfileUpload.parseRequest(requdest)  - List<FileItem>
    注:一个FileItem就是一个标识的开始:---------243243242342 到 ------------------245243523452—就是一个FileItem

第一步:导入包:

JavaWeb文件上传与下载功能解析

第二步:书写一个servlet完成doPost方法

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * DiskFileItemFactory构造的两个参数
 * 第一个参数:sizeThreadHold - 设置缓存(内存)保存多少字节数据,默认为10K
 * 如果一个文件没有大于10K,则直接使用内存直接保存成文件就可以了。
 * 如果一个文件大于10K,就需要将文件先保存到临时目录中去。
 * 第二个参数 File 是指临时目录位置
 *
 */
public class Up2Servlet extends HttpServlet {
 public void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  req.setCharacterEncoding("UTf-8");
  //获取项目的路径
  String path = getServletContext().getRealPath("/up");
  //第一步声明diskfileitemfactory工厂类,用于在指的磁盘上设置一个临时目录
  DiskFileItemFactory disk =
    new DiskFileItemFactory(1024*10,new File("/home/wang/"));
  //第二步:声明ServletFileUpoload,接收上面的临时目录
  ServletFileUpload up = new ServletFileUpload(disk);
  //第三步:解析request
  try {
   List<FileItem> list = up.parseRequest(req);
   //如果就一个文件
   FileItem file = list.get(0);
   //获取文件名,带路径
   String fileName = file.getName();
   fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
   //获取文件的类型
   String fileType = file.getContentType();
   //获取文件的字节码
   InputStream in = file.getInputStream();
   //声明输出字节流
   OutputStream out = new FileOutputStream(path+"/"+fileName);
   //文件copy
   byte[] b = new byte[1024];
   int len = 0;
   while((len=in.read(b))!=-1){
    out.write(b,0,len);
   }
   out.close();
 
   long size = file.getInputStream().available();
 
   //删除上传的临时文件
   file.delete();
   //显示数据
   resp.setContentType("text/html;charset=UTf-8");
   PrintWriter op = resp.getWriter();
   op.print("文件上传成功<br/>文件名:"+fileName);
   op.print("<br/>文件类型:"+fileType);
   op.print("<br/>文件大小(bytes)"+size);
 
 
 
  } catch (Exception e) {
   e.printStackTrace();
  }
 
 }
 
}

5.使用该框架上传多个文件:

第一步:修改页面的表单为多个input type=”file”

?
1
2
3
4
5
<form action="<c:url value='/Up3Servlet'/>" method="post" enctype="multipart/form-data">
  File1:<input type="file" name="txt"><br/>
  File2:<input type="file" name="txt"><br/>
  <input type="submit"/>
 </form>

第二步:遍历list

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Up3Servlet extends HttpServlet {
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  request.setCharacterEncoding("UTF-8");
  String path = getServletContext().getRealPath("/up");
  //声明disk
  DiskFileItemFactory disk = new DiskFileItemFactory();
  disk.setSizeThreshold(1024*1024);
  disk.setRepository(new File("d:/a"));
  //声明解析requst的servlet
  ServletFileUpload up = new ServletFileUpload(disk);
  try{
   //解析requst
   List<FileItem> list = up.parseRequest(request);
   //声明一个list<map>封装上传的文件的数据
   List<Map<String,String>> ups = new ArrayList<Map<String,String>>();
   for(FileItem file:list){
    Map<String,String> mm = new HashMap<String, String>();
    //获取文件名
    String fileName = file.getName();
    fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
    String fileType = file.getContentType();
    InputStream in = file.getInputStream();
    int size = in.available();
    //使用工具类
    FileUtils.copyInputStreamToFile(in,new File(path+"/"+fileName));
    mm.put("fileName",fileName);
    mm.put("fileType",fileType);
    mm.put("size",""+size);
 
    ups.add(mm);
    file.delete();
   }
   request.setAttribute("ups",ups);
   //转发
   request.getRequestDispatcher("/jsps/show.jsp").forward(request, response);
 
  }catch(Exception e){
   e.printStackTrace();
  }
 }
}

如上就是上传文件的常用做法。现在我们在来看看fileupload的其他查用API.

JavaWeb文件上传与下载功能解析

JavaWeb文件上传与下载功能解析

判断一个fileItem是否是file(type=file)对象或是text(type=text|checkbox|radio)对象:
boolean isFormField() 如果是text|checkbox|radio|select这个值就是true.

6.处理带说明信息的图片

JavaWeb文件上传与下载功能解析

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class UpDescServlet extends HttpServlet {
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  request.setCharacterEncoding("UTF-8");//可以获取中文的文件名
  String path = getServletContext().getRealPath("/up");
  DiskFileItemFactory disk =
    new DiskFileItemFactory();
  disk.setRepository(new File("d:/a"));
  try{
   ServletFileUpload up =
     new ServletFileUpload(disk);
   List<FileItem> list = up.parseRequest(request);
   for(FileItem file:list){
    //第一步:判断是否是普通的表单项
    if(file.isFormField()){
     String fileName = file.getFieldName();//<input type="text" name="desc">=desc
     String value = file.getString("UTF-8");//默认以ISO方式读取数据
     System.err.println(fileName+"="+value);
    }else{//说明是一个文件
     String fileName = file.getName();
     fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
     file.write(new File(path+"/"+fileName));
     System.err.println("文件名是:"+fileName);
     System.err.println("文件大小是:"+file.getSize());
     file.delete();
    }
   }
  }catch(Exception e){
   e.printStackTrace();
  }
 }
 
}

7.文件上传的性能提升
在解析request获取FileItem的集合的时候,使用:

FileItemIterator it= up.getItemIterator(request);

比使用

List<FileItem> list = up.parseRequest(request);

性能上要好的多。
示例代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class FastServlet extends HttpServlet {
 
 
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {
  request.setCharacterEncoding("UTF-8");
  String path = getServletContext().getRealPath("/up");
  DiskFileItemFactory disk =
    new DiskFileItemFactory();
  disk.setRepository(new File("d:/a"));
  try{
   ServletFileUpload up = new ServletFileUpload(disk);
   //以下是迭代器模式
   FileItemIterator it= up.getItemIterator(request);
   while(it.hasNext()){
    FileItemStream item = it.next();
    String fileName = item.getName();
    fileName=fileName.substring(fileName.lastIndexOf("\\")+1);
    InputStream in = item.openStream();
    FileUtils.copyInputStreamToFile(in,new File(path+"/"+fileName));
   }
  }catch(Exception e){
   e.printStackTrace();
  }
 
 }
 
}

8.文件的下载
既可以是get也可以是post。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  req.setCharacterEncoding("UTF-8");
  String name = req.getParameter("name");
  //第一步:设置响应的类型
  resp.setContentType("application/force-download");
  //第二读取文件
  String path = getServletContext().getRealPath("/up/"+name);
  InputStream in = new FileInputStream(path);
  //设置响应头
  //对文件名进行url编码
  name = URLEncoder.encode(name, "UTF-8");
  resp.setHeader("Content-Disposition","attachment;filename="+name);
  resp.setContentLength(in.available());
 
  //第三步:开始文件copy
  OutputStream out = resp.getOutputStream();
  byte[] b = new byte[1024];
  int len = 0;
  while((len=in.read(b))!=-1){
   out.write(b,0,len);
  }
  out.close();
  in.close();
 }
 
在使用J2EE流行框架时

使用框架内部封装好的来完成上传下载更为简单:

Struts2完成上传.

在使用Struts2进行开发时,导入的jar包不难发现存在 commons-fileupload-1.3.1.jar 包。通过上面的学习我们已经可以使用它进行文件的上传下载了。但Struts2在进行了进一步的封装。

view

?
1
2
3
4
5
6
7
<form action="fileUpload.action" method="post" enctype="multipart/form-data">
   
  username: <input type="text" name="username"><br>
  file: <input type="file" name="file"><br>
 
  <input type="submit" value="submit">
 </form>

Controller

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class FileUploadAction extends ActionSupport
{
 private String username;
 
   //注意,file并不是指前端jsp上传过来的文件本身,而是文件上传过来存放在临时文件夹下面的文件
 private File file;
 
 //提交过来的file的名字
 //struts会自动截取上次文件的名字注入给该属性
 private String fileFileName;
 //getter和setter此时为了节约篇幅省掉
  @Override
 public String execute() throws Exception
 {
 //保存上传文件的路径
  String root = ServletActionContext.getServletContext().getRealPath("/upload");
  //获取临时文件输入流
  InputStream is = new FileInputStream(file);
  //输出文件
  OutputStream os = new FileOutputStream(new File(root, fileFileName));
  //打印出上传的文件的文件名
  System.out.println("fileFileName: " + fileFileName);
    // 因为file是存放在临时文件夹的文件,我们可以将其文件名和文件路径打印出来,看和之前的fileFileName是否相同
  System.out.println("file: " + file.getName());
  System.out.println("file: " + file.getPath());
 
  byte[] buffer = new byte[1024];
  int length = 0;
 
  while(-1 != (length = is.read(buffer, 0, buffer.length)))
  {
   os.write(buffer);
  }
 
  os.close();
  is.close();
 
  return SUCCESS;
 }
}

首先我们要清楚一点,这里的file并不是真正指代jsp上传过来的文件,当文件上传过来时,struts2首先会寻找struts.multipart.saveDir(这个是在default.properties里面有)这个name所指定的存放位置(默认是空),我们可以在我们项目的struts2中来指定这个临时文件存放位置。

 <constant name="struts.multipart.saveDir" value="/repository"/> 

 如果没有设置struts.multipart.saveDir,那么将默认使用javax.servlet.context.tempdir指定的地址,javax.servlet.context.tempdir的值是由服务器来确定的,例如:假如我的web工程的context是abc,服务器使用Tomcat,那么savePath就应该是%TOMCAT_HOME%/work/Catalina/localhost/abc_,临时文件的名称类似于upload__1a156008_1373a8615dd__8000_00000001.tmp,每次上传的临时文件名可能不同,但是大致是这种样式。而且如果是使用Eclipse中的Servers里面配置Tomcat并启动的话,那么上面地址中的%TOMCAT_HOME%将不会是系统中的实际Tomcat根目录,而会是Eclipse给它指定的地址,例如我本地的地址是这样的:/home/wang/EclipseJavaCode/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/abc/upload__1a156008_1373a8615dd__8000_00000001.tmp。

Struts2完成下载.

struts2的文件下载更简单,就是定义一个输入流,然后将文件写到输入流里面就行,关键配置还是在struts.xml这个配置文件里配置:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FileDownloadAction extends ActionSupport
{
 //要下载文件在服务器上的路径
 private String path;
 //要下载文件的文件名
 private String downloadFileName;
 //写入getter和setter
 public InputStream getDownloadFile()
 {
  return ServletActionContext.getServletContext().getResourceAsStream(path);
 }
 
 @Override
 public String execute() throws Exception
 {
 //当前action默认在valuestack的栈顶
  setDownloadFileName(xxx);
  return SUCCESS;
 }
}

action只是定义了一个输入流downloadFile,然后为其提供getter方法就行,接下来我们看看struts.xml的配置文件:

?
1
2
3
4
5
6
<action name="fileDownload" class="com.struts2.FileDownloadAction">
   <result name="download" type="stream">
 <param name="contentDisposition">attachment;fileName="${downloadFileName}"</param>
 <param name="inputName">downloadFile</param>
  </result>
</action>

struts.xml配置文件有几个地方我们要注意,首先是result的类型,type一定要定义成stream类型_,告诉action这是文件下载的result,result元素里面一般还有param子元素,这个是用来设定文件下载时的参数,inputName这个属性就是得到action中的文件输入流,名字一定要和action中的输入流属性名字相同,然后就是contentDisposition属性,这个属性一般用来指定我们希望通过怎么样的方式来处理下载的文件,如果值是attachment,则会弹出一个下载框,让用户选择是否下载,如果不设定这个值,那么浏览器会首先查看自己能否打开下载的文件,如果能,就会直接打开所下载的文件,(这当然不是我们所需要的),另外一个值就是filename这个就是文件在下载时所提示的文件下载名字。在配置完这些信息后,我们就能过实现文件的下载功能了。

SpringMVC完成上传:

view于struts2示例中的完全一样。此出不在写出。

Controller:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
@RequestMapping(value="fileOperate")
public class FileOperateAction {
 @RequestMapping(value="upload")
 public String upload(HttpServletRequest request,@RequestParam("file") MultipartFile photoFile){
 //上传文件保存的路径
  String dir = request.getSession().getServletContext().getRealPath("/")+"upload";
  //原始的文件名
  String fileName = photoFile.getOriginalFilename();      //获取文件扩展名
  String extName = fileName.substring(fileName.lastIndexOf("."));
 //防止文件名冲突,把名字小小修改一下
 fileName = fileName.substring(0,fileName.lastIndexOf(".")) + System.nanoTime() + extName;
  FileUtils.writeByteArrayToFile(new File(dir,fileName),photoFile.getBytes());
  return "success";
 }
 }

SpringMVC完成下载:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RequestMapping("/download")
public String download(String fileName, HttpServletRequest request,
  HttpServletResponse response) {
 response.setCharacterEncoding("utf-8");
 response.setContentType("multipart/form-data");
 response.setHeader("Content-Disposition", "attachment;fileName="
   + fileName);
 try {
  InputStream inputStream = new FileInputStream(new File(文件的路径);
 
  OutputStream os = response.getOutputStream();
  byte[] b = new byte[2048];
  int length;
  while ((length = inputStream.read(b)) > 0) {
   os.write(b, 0, length);
  }
 
   // 这里主要关闭。
  os.close();
 
  inputStream.close();
 } catch (FileNotFoundException e) {
  e.printStackTrace();
 } catch (IOException e) {
  e.printStackTrace();
 }
  // 返回值要注意,要不然就出现下面这句错误!
  //java+getOutputStream() has already been called for this response
 return null;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。