功能介绍:
由两个页面组成,页面的生成是根据请求参数action的不同,doGet()将生成逻辑委托给相应的方法来完成:
页面1:显示整个已经上传文件的列表,提供下载支持
页面2:显示上传文件的页面
配置servlet支持文件上传:
这里有两种方法:一是使用注解;二是在web.xml中配置
两种方法的配置如下:
@WebServlet( name = "FileUploadServlet", urlPatterns = {"/upload"}, loadOnStartup = 1 ) //告诉web容器为该servlet提供文件上传支持 @MultipartConfig( // 告诉web容器文件必须达到5MB时才写入临时目录 fileSizeThreshold = 5_242_800, // 5MB // 上传的文件不能超过20MB maxFileSize = 20_971_520L, // 20MB // 不能接收超过40MB的请求 maxRequestSize = 41_942_040L // 40MB )
<servlet> <servlet-name>FileUploadServlet</servlet-name> <servlet-class>cn.example.FileUploadServlet</servlet-class> <multipart-config> <location>/tmp</location><!-- 告诉浏览器在哪里存储临时文件 --> <max-file-size>20971520</max-file-size><!--20MB--> <max-request-size>41943040</max-request-size><!--40MB--> <file-size-threshold>5242880</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>FileUploadServlet</servlet-name> <url-pattern>/upload</url-pattern> </servlet-mapping>
使用什么来存储上传的文件:
因为没有学过servlet连接数据库,所以为了方便,直接将上传上来的文件以二进制数组的方式保存在一个Attachment对象中,在servlet中使用一个哈希map作为文件数据库,以文件id(每个文件都有一个唯一的id)为键,以Attachment对象为值。当需要下载某个文件时,需要在请求URL中提供该文件的唯一id,然后在map中返回该文件。因为文件是保存在内存中的,所以这种方法不适宜上传大文件。
如何显示页面:
因为没有学习过jsp,为了使页面具有动态性,所以选择一种比较麻烦的方式,在servlet中直接构建页面。使用HttpServletResponse对象获得一个PrintWriter对象,将html页面代码(包含一些生成的数据)输入到输出流中,就形成了简单的具有动态效果的页面。这项工作主要由doGet()来完成,根据请求参数action的不同,生成不同的页面。
如何实现文件的上传:
1.html表单设置支持文件上传
2.配置servlet支持文件上传
3.在servlet中处理上传逻辑:
3.1获得文件的Part对象,该对象可以表示上传的文件或者表单数据
3.2从这个Part对象中获得一个输入流,新建一个输出流(ByteArrayOutputStream,它的数据是字节数组的形式写入,因为我需要把上传的文件以字节数组的方法保存在Attachment对象中),把输入流中的数据复制到输出流中,完成之后再把输出流中的内容转换成字节数组保存在Attachment对象中
3.3生成一个唯一id(使用同步方式,当一个线程正在处理该共享资源时,其他线程需要等待),把它与Attachment对象组成键值对添加到map中
如何实现文件的下载:
1.在请求URL中获得文件id,从map中获取包含该文件的Attachment对象
2.设置相应头属性Content-Disposition,例如
// 强制浏览器询问用户是保存还是下载文件,而不是在浏览器打开该文件
resp.setHeader("Content-Disposition", "attachment; filename = " + attachment.getName());
// 设置内容类型是通用的、二进制内容类型,这样容器就不会使用字符编码对该数据进行处理
// 更加准确的应该使用附件的MIME内容类型 resp.setContentType("application/octet-stream");
3.将Attachment对象保存的文件数据输出到输出流中
详细代码:
package cn.example; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.Writer; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; @WebServlet( name = "FileUploadServlet", urlPatterns = {"/upload"}, loadOnStartup = 1 ) //告诉web容器为该servlet提供文件上传支持 @MultipartConfig( // 告诉web容器文件必须达到5MB时才写入临时目录 fileSizeThreshold = 5_242_800, // 5MB // 上传的文件不能超过20MB maxFileSize = 20_971_520L, // 20MB // 不能接收超过40MB的请求 maxRequestSize = 41_942_040L // 40MB ) public class FileUploadServlet extends HttpServlet{ private Map<String, Attachment> attachmentDB = new HashMap<String, Attachment>(); private volatile int TICKET_ID_SEQUENCE = 1; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if(action == null) action = "list"; switch (action) { case "create": this.showUploadForm(resp); break; case "download": this.downloadAttachment(req, resp); break; case "list": default: this.listAttachment(resp); break; } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if(action == null) action = "list"; switch (action) { case "create": this.createAttachment(req, resp); break; default: this.listAttachment(resp); break; } } private void createAttachment(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { Part filePart = req.getPart("file1"); Attachment attachment = null; String idString = null; if(filePart != null && filePart.getSize() > 0){ attachment = this.processAttachment(filePart); int id; synchronized (this) { id = this.TICKET_ID_SEQUENCE++; idString = Integer.toString(id); this.attachmentDB.put(idString, attachment); } } resp.sendRedirect("upload?action=list&id=" + idString); } private void showUploadForm(HttpServletResponse resp) throws IOException { PrintWriter writer = this.writeHeader(resp); writer.append("<h2>上传一个文件</h2> "); writer.append("<form method="POST" action="upload" ") .append("enctype="multipart/form-data"> "); // 以multipart/form-data的编码格式对表单进行编码 // 隐藏域在页面中对于用户是不可见的,在表单中插入隐藏域的目的在于收集或发送信息,以利于被处理表单的程序所使用 writer.append("<input type="hidden" name="action" ") .append("value="create"/> "); writer.append("<b>文件</b><br/> "); writer.append("<input type="file" name="file1"/><br/><br/> "); writer.append("<input type="submit" value="提交"/> "); writer.append("</form> "); this.writeFooter(writer); } private PrintWriter writeHeader(HttpServletResponse resp) throws IOException { resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); PrintWriter writer = resp.getWriter(); writer.append("<!DOCTYPE html> ") .append("<html> ") .append(" <head> ") .append(" <title>文件上传</title> ") .append(" </head> ") .append(" <body> "); return writer; } private void writeFooter(PrintWriter writer) { writer.append(" </body> ").append("</html> "); } private Attachment processAttachment(Part filePart) throws IOException{ InputStream inputStream = filePart.getInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); int read; final byte[] bytes = new byte[1024]; while((read = inputStream.read(bytes)) != -1){ outputStream.write(bytes, 0, read); } Attachment attachment = new Attachment(); attachment.setName(filePart.getSubmittedFileName()); attachment.setContents(outputStream.toByteArray()); return attachment; } private void listAttachment(HttpServletResponse resp) throws IOException { PrintWriter writer = this.writeHeader(resp); writer.append("<h2>文件列表</h2> "); writer.append("<a href="upload?action=create">上传一个文件") .append("</a><br/><br/> "); if(attachmentDB.size() == 0){ writer.append("没有文件可供下载"); }else{ for(String id : this.attachmentDB.keySet()) { Attachment attachment = this.attachmentDB.get(id); writer.append("attachment #").append(id) .append("文件:").append("<a href="upload?action=download&id=" + id).append("">") .append(attachment.getName()).append("</a> ").append("<br/><br/> "); } } this.writeFooter(writer); } private void downloadAttachment(HttpServletRequest req, HttpServletResponse resp) throws IOException { String id = req.getParameter("id"); if(id == null){ resp.sendRedirect("upload?action=create"); return; } Attachment attachment = attachmentDB.get(id); if(attachment == null){ resp.sendRedirect("upload?action=create"); return; } // 强制浏览器询问用户是保存还是下载文件,而不是在浏览器打开该文件 resp.setHeader("Content-Disposition", "attachment; filename = " + attachment.getName()); // 设置内容类型是通用的、二进制内容类型,这样容器就不会使用字符编码对该数据进行处理 // 更加准确的应该使用附件的MIME内容类型 resp.setContentType("application/octet-stream"); // 使用ServletOutputStream将附件内容输出到响应中 ServletOutputStream stream = resp.getOutputStream(); stream.write(attachment.getContents()); } }
结果如下:
上传文件:
文件列表:
文件下载: