JavaWeb 之文件的上传下载
又到了每周更新博客的时候了,每看到自己发布的随笔阅读量上涨的时候就特别开心,我也会尽自己的努力提高自己的水平,总结出通俗易读的学习笔记,还望大家能多多支持!!!
--------------------------------------------------------------------------------------------------------------
文件的上传
- 设置表单请求方式为 post
- 设置表单类型为 file
- 设置编码方式 enctype=”multipart/form-data”
- 使用 fileUpload 和 io 组件完成文件的上传操作
Jquery 实现添加多文件上传组件
- 功能演示
- 每次点击 createNew 添加一个新的上传组件,并为之生成最新的索引
- 点击删除按钮删删除当前组件并将其余的组件按照从小到大的顺序排列
- 实现代码
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <html> 3 <head> 4 <title>FileUpload</title> 5 <script type="text/javascript" src="jquery-1.7.2.js"></script> 6 <script type="text/javascript"> 7 $(function () { 8 // 定义变量 i,使其每次点击事件自增,达到动态增加目的 9 var i = 2; 10 // 给 createNew 添加点击事件 11 $("#createNew").click(function () { 12 // this 表示 button,第一层 parent 表示 td 第二层 parent 表示 tr,然后在其前面加入两个 tr,并为所有 tr 添加 class 属性 13 $(this).parent().parent().before("<tr class='file'><td>File" + i + "</td><td><input type='file'name='file " + i + " '></td></tr>" + 14 "<tr class='desc'><td>Desc" + i + "</td><td><input type='text' name='desc" + i + "'><button id='delete" + i + "'>删除</button></td></tr>"); 15 // 每次添加之后是 i 加 1 16 i++; 17 // 点击删除的事件 18 $("#delete" + (i - 1)).click(function () { 19 // 将 tr 保存到变量中,方便对前一个 tr(文件表单所对应的 tr) 的删除 20 var $tr = $(this).parent().parent(); 21 // 删除前一个 tr 22 $tr.prev().remove(); 23 $tr.remove(); 24 // 找到所有 class 为 file 的第一个 td,然后对其遍历,并将参数传入遍历的函数中 25 $(".file").find("td:first").each(function (index) { 26 var n = index + 1; 27 // 将 td 的文本改变,达到对 td 的重新排序 28 $(this).text("File" + n); 29 // 将 input 的 name 属性改变 30 $(this).parent().find("td:last").find("input").attr("name", "file" + n); 31 }); 32 $(".desc").find("td:first").each(function (index) { 33 var n = index + 1; 34 $(this).text("Desc" + n); 35 $(this).parent().find("td:last").find("input").attr("name", "desc" + n); 36 }); 37 }); 38 return false; 39 }); 40 }); 41 </script> 42 </head> 43 <body> 44 <h3>${requestScope.message}</h3> 45 <form action="${pageContext.request.contextPath}/realUploadServlet" method="post" enctype="multipart/form-data"> 46 <table cellspacing="10" cellpadding="0"> 47 48 <tr class="file"> 49 <td>File1</td> 50 <td><input type="file" name="file1"></td> 51 </tr> 52 <tr class="desc"> 53 <td>Desc1</td> 54 <td><input type="text" name="desc1"></td> 55 </tr> 56 <tr> 57 <td> 58 <button type="submit">Submit</button> 59 </td> 60 <td> 61 <button type="submit" id="createNew">CreateNew</button> 62 </td> 63 </tr> 64 </table> 65 </body> 66 </html>
数据库设计 -- files 表
- 表结构
DAO、doMain 类设计
1 package com.javaweb.file.servlet.database.dao; 2 3 import com.mchange.v2.c3p0.ComboPooledDataSource; 4 5 import javax.sql.DataSource; 6 import java.sql.Connection; 7 import java.sql.SQLException; 8 9 /** 10 * Created by shkstart on 2017/12/02. 11 */ 12 public class JDBCTools { 13 14 private static DataSource dataSource; 15 16 static { 17 dataSource = new ComboPooledDataSource("uploadFile"); 18 } 19 20 public static Connection getConnection() { 21 Connection connection = null; 22 23 try { 24 connection = dataSource.getConnection(); 25 } catch (SQLException e) { 26 e.printStackTrace(); 27 } 28 29 return connection; 30 } 31 32 public static void releaseConnection(Connection connection) { 33 try { 34 connection.close(); 35 } catch (SQLException e) { 36 e.printStackTrace(); 37 } 38 } 39 }
1 package com.javaweb.file.servlet.database.dao; 2 3 import org.apache.commons.dbutils.QueryRunner; 4 import org.apache.commons.dbutils.handlers.BeanListHandler; 5 6 import java.lang.reflect.ParameterizedType; 7 import java.lang.reflect.Type; 8 import java.sql.Connection; 9 import java.sql.SQLException; 10 import java.util.List; 11 12 /** 13 * Created by shkstart on 2017/12/02. 14 */ 15 public class DAO<T> { 16 17 private static QueryRunner queryRunner; 18 private Class<T> type; 19 20 21 public DAO() { 22 queryRunner = new QueryRunner(); 23 24 Type superclass = getClass().getGenericSuperclass(); 25 if (superclass instanceof ParameterizedType) { 26 ParameterizedType parameterizedType = (ParameterizedType) superclass; 27 28 Type[] args = parameterizedType.getActualTypeArguments(); 29 if (args != null && args.length > 0) { 30 if (args[0] instanceof Class) { 31 type = (Class<T>) args[0]; 32 } 33 } 34 } 35 } 36 37 public void update(String sql, Object...args) { 38 Connection connection = JDBCTools.getConnection(); 39 System.out.println(queryRunner); 40 try { 41 queryRunner.update(connection, sql, args); 42 } catch (SQLException e) { 43 e.printStackTrace(); 44 } finally { 45 JDBCTools.releaseConnection(connection); 46 } 47 } 48 49 public List<T> getAll(String sql) { 50 Connection connection = JDBCTools.getConnection(); 51 List<T> fileList = null; 52 try { 53 fileList = queryRunner.query(connection, sql, new BeanListHandler<T>(type)); 54 } catch (SQLException e) { 55 e.printStackTrace(); 56 } 57 return fileList; 58 }
- c3p0 配置文件
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <c3p0-config> 3 <named-config name="uploadFile"> 4 <property name="user">root</property> 5 <property name="password">zy961029</property> 6 <property name="driverClass">com.mysql.jdbc.Driver</property> 7 <property name="jdbcUrl">jdbc:mysql:///sh_db</property> 8 9 <property name="maxStatements">1</property> 10 <property name="maxStatementsPerConnection">5</property> 11 </named-config> 12 </c3p0-config>
- 依据数据表新建 Files 类
1 package com.javaweb.file.servlet.file.bean; 2 3 /** 4 * Created by shkstart on 2017/12/02. 5 */ 6 public class Files { 7 private Integer id; 8 private String fileName; 9 private String filePath; 10 private String fileDesc; 11 12 public Files() { 13 14 } 15 16 @Override 17 public String toString() { 18 return "Files{" + 19 ", fileName='" + fileName + '\'' + 20 ", filePath='" + filePath + '\'' + 21 ", fileDesc='" + fileDesc + '\'' + 22 '}'; 23 } 24 25 public Files(String fileName, String filePath, String fileDesc) { 26 this.fileName = fileName; 27 this.filePath = filePath; 28 this.fileDesc = fileDesc; 29 } 30 31 public Integer getId() { 32 return id; 33 } 34 35 public void setId(Integer id) { 36 this.id = id; 37 } 38 39 public String getFileName() { 40 return fileName; 41 } 42 43 public void setFileName(String fileName) { 44 this.fileName = fileName; 45 } 46 47 public String getFilePath() { 48 return filePath; 49 } 50 51 public void setFilePath(String filePath) { 52 this.filePath = filePath; 53 } 54 55 public String getFileDesc() { 56 return fileDesc; 57 } 58 59 public void setFileDesc(String fileDesc) { 60 this.fileDesc = fileDesc; 61 } 62 }
- 依据文件上传功能的要求新建 files 表对应的接口(实现文件上传需要插入数据表即 update,实现文件下载需要获取数据表数据即 getAll())
1 package com.javaweb.file.servlet.database.dao; 2 3 import com.javaweb.file.servlet.file.bean.Files; 4 5 import java.util.List; 6 7 /** 8 * Created by shkstart on 2017/12/02. 9 */ 10 public interface FilesDao { 11 void update(Files files); 12 List<Files> getAll(); 13 }
- 使用 DAO 类实现 FilesDao 接口
1 package com.javaweb.file.servlet.daoImpl; 2 3 import com.javaweb.file.servlet.database.dao.DAO; 4 import com.javaweb.file.servlet.database.dao.FilesDao; 5 import com.javaweb.file.servlet.file.bean.Files; 6 7 import java.util.List; 8 9 /** 10 * Created by shkstart on 2017/12/02. 11 */ 12 public class FilesDaoImpl extends DAO<Files> implements FilesDao { 13 @Override 14 public void update(Files files) { 15 String sql = "INSERT INTO files (file_name, file_path, file_desc) VALUES(?, ?, ?)"; 16 update(sql, files.getFileName(), files.getFilePath(), files.getFileDesc()); 17 } 18 19 @Override 20 public List<Files> getAll() { 21 String sql = "SELECT file_name fileName, file_path filePath, file_desc fileDesc FROM files"; 22 List<Files> filesList = getAll(sql); 23 return filesList; 24 } 25 }
配置上传文件的需求,并在 ContextListener 监听器中初始化(文件大小、文件类型等限制)
- 新建一个单例的属性控制器,在监听器中调用该类的方法添加属性,以及在 Servlet 中调用该类方法获取属性
1 package com.javaweb.file.servlet.property.data; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 /** 7 * 该类为单例设计模式 8 */ 9 public class PropertiesControl { 10 // 存储所配置的属性 11 private Map<String, String> properties = new HashMap<String, String>(); 12 // 将该类编写为单例的 13 private PropertiesControl() {} 14 15 private static PropertiesControl instance = new PropertiesControl(); 16 17 public static PropertiesControl getInstance() { 18 return instance; 19 } 20 // 添加属性到 map 的方法 21 public void addPorperty(String propertyName, String propertyValue) { 22 properties.put(propertyName, propertyValue); 23 } 24 // 获取属性的方法 25 public String getProperty(String propertyName) { 26 return properties.get(propertyName); 27 } 28 }
- 监听器类(初始化所有属性)
1 package com.javaweb.file.servlet.properties.listener; /** 2 * Created by shkstart on 2017/11/30. 3 */ 4 5 import com.javaweb.file.servlet.property.data.PropertiesControl; 6 7 import javax.servlet.ServletContextEvent; 8 import javax.servlet.ServletContextListener; 9 import java.io.IOException; 10 import java.io.InputStream; 11 import java.util.Map; 12 import java.util.Properties; 13 14 public class PropertiesListener implements ServletContextListener{ 15 16 // 利用 ContextListener 读取属性并初始化 17 // 我没想到用监听器,想到的是 web 应用的初始化参数,和 servlet 的 init 方法 18 @Override 19 public void contextInitialized(ServletContextEvent servletContextEvent) { 20 // 读取配置文件 21 InputStream inputStream = getClass().getClassLoader().getResourceAsStream("/upload.properties"); 22 Properties properties = new Properties(); 23 try { 24 // 加载输入流(文件) 25 properties.load(inputStream); 26 // 遍历属性集合 27 for (Map.Entry<Object, Object> entry : properties.entrySet()) { 28 // 获取属性名和属性值 29 String propertyName = (String) entry.getKey(); 30 String propertyValue = (String) entry.getValue(); 31 // 添加属性到 PropertyControl 类 32 PropertiesControl.getInstance().addPorperty(propertyName, propertyValue); 33 } 34 } catch (IOException e) { 35 e.printStackTrace(); 36 } 37 } 38 39 @Override 40 public void contextDestroyed(ServletContextEvent servletContextEvent) { 41 42 } 43 }
- 配置文件
1 #文件总大小为 200M 2 sizeMax=209715200 3 #单个文件大小为 10M 4 sizeSingle=10485760 5 #允许的文件后缀 6 nameOfEnd=.md,.png,.jpg,.zip
文件上传实现代码(核心Servlet)
- 使用 fileUpload 组件上传文件其具体细节可查看其自带的官方文档,这里直接贴出代码(含有详细注释)
1 package com.javaweb.file.servlet.test.servlet; 2 3 import com.javaweb.file.servlet.daoImpl.FilesDaoImpl; 4 import com.javaweb.file.servlet.database.dao.FilesDao; 5 import com.javaweb.file.servlet.file.bean.Files; 6 import com.javaweb.file.servlet.helloServlet.DeleteFile; 7 import com.javaweb.file.servlet.project.exname.exception.ExNameException; 8 import com.javaweb.file.servlet.property.data.PropertiesControl; 9 import org.apache.commons.fileupload.FileItem; 10 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 11 import org.apache.commons.fileupload.servlet.ServletFileUpload; 12 13 import javax.servlet.ServletException; 14 import javax.servlet.http.HttpServlet; 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 import java.io.*; 18 import java.util.*; 19 20 /** 21 * Created by shkstart on 2017/11/30. 22 */ 23 public class UploadServlet extends HttpServlet { 24 // 设置要把文件保存到的虚拟路径,可获得其真实路径 25 private static final String REAL_PATH = "/WEB-INF"; 26 private HttpServletRequest request; 27 // 设置的临时文件夹 28 private static final String TEMP_DIR = "D:/io"; 29 30 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 31 this.request = request; 32 33 // 获取 ServletFileUpload 34 ServletFileUpload upload = getServletFileUpload(); 35 // 36 String path = request.getContextPath() + "/uploadFileTest/success.jsp"; 37 try { 38 // 此 Map 的键为需要上传文件的路径加文件名,值为可获取对应的输入流 39 Map<String, FileItem> uploadFile = new HashMap<String, FileItem>(); 40 // 获取所有提交的表单域和文件域 41 List<FileItem> items = upload.parseRequest(request); 42 // 构建 FileUploadBean,和需要上传文件的 Map 集合 43 List<Files> beans = buildFileUploadBeans(items, uploadFile); 44 // 校验扩展名 45 validateExName(beans); 46 // 上传文件操作 47 upload(uploadFile); 48 // 保存到数据库 49 save(beans); 50 // 清空临时文件夹 51 DeleteFile.deleteFiles(TEMP_DIR); 52 } catch (Exception e) { 53 e.printStackTrace(); 54 // 若发生异常转发回原页面,并提示错误消息(在这可以抓取后缀名不合法的异常,并返回页面报错) 55 path = "/upload.jsp"; 56 request.setAttribute("message", e.getMessage()); 57 request.getRequestDispatcher(path).forward(request, response); 58 } 59 // 若没有异常则重定向到文件上传成功目录 60 response.sendRedirect(path); 61 } 62 // 执行数据库保存操作 63 private void save(List<Files> beans) { 64 for (Files file : beans) { 65 FilesDao fileDao = new FilesDaoImpl(); 66 // 执行插入操作 67 fileDao.update(file); 68 } 69 } 70 // 遍历所传入的 Map 集合获得文件路径和可获得输入流的 item 对象 71 private void upload(Map<String, FileItem> uploadFile) { 72 for (Map.Entry<String, FileItem> entry : uploadFile.entrySet()) { 73 String filePath = entry.getKey(); 74 FileItem item = entry.getValue(); 75 uploadFile(filePath, item); 76 } 77 } 78 // 进行文件上传操作(IO 操作) 79 private void uploadFile(String filePath, FileItem item) { 80 try { 81 int len; 82 byte[] buffer = new byte[1024]; 83 InputStream inputStream = item.getInputStream(); 84 BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); 85 OutputStream outputStream = new FileOutputStream(filePath); 86 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); 87 88 while ((len = bufferedInputStream.read(buffer)) != -1) { 89 bufferedOutputStream.write(buffer, 0, len); 90 } 91 bufferedOutputStream.close(); 92 bufferedInputStream.close(); 93 } catch (IOException e) { 94 e.printStackTrace(); 95 } 96 } 97 // 校验文件后缀名 98 private void validateExName(List<Files> beans) { 99 // 允许的后缀名 100 String nameOfEnd = PropertiesControl.getInstance().getProperty("nameOfEnd"); 101 // 将配置的文件名分开,并存为一个 list 102 List<String> legalExName = Arrays.asList(nameOfEnd.split(",")); 103 // 遍历将要上传的文件的后缀名 104 for (Files bean : beans) { 105 // 获得当前文件的后缀名 106 String exName = getExName(bean.getFileName()); 107 // 如果后缀名不合法那么就抛出自定义异常,并转发到页面打印错误消息 108 if (!legalExName.contains(exName)) { 109 // 若不是允许的后缀名则抛出自建异常,并提示错误消息 110 throw new ExNameException("不允许上传后缀名为" + exName + "的文件"); 111 } 112 } 113 } 114 // 根据传入的文件名获得文件的后缀名 115 private String getExName(String fileName) { 116 // 获得 . 对应的下标 117 int numOfPoint = fileName.lastIndexOf("."); 118 // 返回文件后缀名 119 return fileName.substring(numOfPoint, fileName.length()); 120 } 121 122 // 构建 FileUploadBean 集合 123 private List<Files> buildFileUploadBeans(List<FileItem> items, Map<String, FileItem> uploadFile) { 124 // 需要返回的 FileUploadBean 集合 125 List<Files> beans = new ArrayList<Files>(); 126 // 初始化 filePath 127 String filePath = null; 128 // 将表单域和文件域分开存储,便于比较并对 FileUpload 赋值 129 List<FileItem> itemDesc = new ArrayList<FileItem>(); 130 List<FileItem> itemFile = new ArrayList<FileItem>(); 131 132 // 分别为表单集合和文件集合赋值 133 for (FileItem item : items) { 134 if (item.isFormField()) { 135 // 为表单集合赋值 136 itemDesc.add(item); 137 } 138 // 判断 item 对象是否为文件域 139 if (!item.isFormField()) { 140 // 为文件集合赋值 141 itemFile.add(item); 142 } 143 } 144 for (FileItem item1 : itemDesc) { 145 // 获得表单前面显示文本,如 desc1 146 String fieldName1 = item1.getFieldName(); 147 // 获得表单前面显示文本得最后一位数字,如 1 148 String descNum = fieldName1.substring(fieldName1.length() - 1, fieldName1.length()); 149 // 新建数据表对应的 Files 对象,在后面经过比较后为其初始化 150 Files bean = null; 151 // 遍历两个集合找出相匹配的文本域 152 for (FileItem item2 : itemFile) { 153 // 获得表单前面显示文本,如 file1 154 String fieldName2 = item2.getFieldName(); 155 // 获得表单前面显示文本的最后一位,如 1 156 String fileNum = fieldName2.substring(fieldName2.length() - 1, fieldName2.length()); 157 // 如果获得的数字一样,那么就表示他们两个是一组上传组件 158 if (descNum.equals(fileNum)) { 159 // 利用文件 item 对象构建完整的存放路径,获取后缀名 160 String exName = getExName(item2.getName()); 161 // 利用随机数加系统时间构建文件名,以及真实路径 162 filePath = gteFilePath(exName); 163 // 初始化 bean 164 String desc = null; 165 try { 166 // 使用过滤器不能彻底解决乱码问题,在过滤器的基础上获取参数值的过程中传入编码参数 167 desc = item1.getString("UTF-8"); 168 } catch (UnsupportedEncodingException e) { 169 e.printStackTrace(); 170 } 171 // 经过比较初始化 bean 对象 172 bean = new Files(item2.getName(), filePath, desc); 173 } 174 // 初始化 Map 对象 175 uploadFile.put(filePath, item2); 176 } 177 // 将所有的 files 对象加入 list 集合中 178 beans.add(bean); 179 } 180 return beans; 181 } 182 183 // 构建 filePath 184 private String gteFilePath(String exName) { 185 // 生成随机数 186 Random random = new Random(); 187 // 获得所提供虚拟路径的真实路径,并构建上传文件所需的地址 + 文件名(更改原文件名为随机的,即为随机数加上系统时间) 188 String filePath = getServletContext().getRealPath(REAL_PATH) + "\\" + System.currentTimeMillis() + random.nextInt(100000) + exName; 189 return filePath; 190 } 191 192 private ServletFileUpload getServletFileUpload() { 193 // 文件总大小的限制(获取监听器初始化的属性配置) 194 String sizeMax = PropertiesControl.getInstance().getProperty("sizeMax"); 195 // 单个文件大小限制 196 String sizeSingle = PropertiesControl.getInstance().getProperty("sizeSingle"); 197 // 获取 FileItem 集合对象 198 DiskFileItemFactory factory = new DiskFileItemFactory(); 199 // 设置临时文件夹(当文件大小超过设置的大小就先将文件存储在临时文件下,以提高效率) 200 factory.setRepository(new File(TEMP_DIR)); 201 // 设置使用临时文件夹的阀值 5M 202 factory.setSizeThreshold(1024 * 1024 * 5); 203 // 获得 ServletFileUpload 对象 204 ServletFileUpload upload = new ServletFileUpload(factory); 205 // 设置总文件大小限制 206 upload.setSizeMax(Long.parseLong(sizeMax)); 207 // 设置单个文件大小限制 208 upload.setFileSizeMax(Long.parseLong(sizeSingle)); 209 return upload; 210 } 211 212 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 213 doPost(request, response); 214 } 215 }
- 清空临时文件夹的操作类
1 package com.javaweb.file.servlet.helloServlet; 2 3 import java.io.File; 4 5 /** 6 * Created by shkstart on 2017/12/01. 7 */ 8 public class DeleteFile { 9 10 public static void deleteFiles(String filePath) { 11 // 根据所传路径新建 File 对象 12 File file = new File(filePath); 13 // 获得所有的文件集合 14 File[] files = file.listFiles(); 15 // 遍历文件和目录集合 16 for (File file1 : files) { 17 // 如果碰到文件直接删除 18 if (file1.isFile()) { 19 file1.delete(); 20 } 21 // 如果碰到目录,先删除所有文件 22 if (file1.isDirectory()) { 23 // 递归调用,删除目录中所有的文件 24 deleteFiles(filePath + "/" + file1.getName()); 25 // 调用方法删除空目录 26 deleteDir(filePath + "/" + file1.getName()); 27 } 28 } 29 } 30 31 private static void deleteDir(String filePath) { 32 // 因为目录为空,所以新建 File 对象为空目录,直接删除 33 File file = new File(filePath); 34 file.delete(); 35 } 36 }
- 过滤器操作
1 package com.javaweb.file.servlet.encode.filter; 2 3 import javax.servlet.*; 4 import javax.servlet.http.HttpServletRequest; 5 import java.io.IOException; 6 7 /** 8 * Created by shkstart on 2017/12/01. 9 */ 10 public class EncodeFilter implements Filter { 11 public void destroy() { 12 } 13 14 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { 15 req.setCharacterEncoding("UTF-8"); 16 chain.doFilter(req, resp); 17 } 18 19 public void init(FilterConfig config) throws ServletException { 20 21 } 22 }
- 上传成功页面
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <html> 3 <head> 4 <title>SuccessPage</title> 5 </head> 6 <body> 7 <h3>上传成功!<a href="${pageContext.request.contextPath}/index.jsp">返回</a></h3> 8 </body> 9 </html>
文件下载
- 点击文件下载超链接到 Servlet 中查询数据库获取所有已上传的文件,封装到 request 中转发回显示页面,提供下载操作
- DownloadServlet
1 package com.javaweb.file.servlet.test.servlet; 2 3 import com.javaweb.file.servlet.daoImpl.FilesDaoImpl; 4 import com.javaweb.file.servlet.database.dao.FilesDao; 5 import com.javaweb.file.servlet.file.bean.Files; 6 import com.sun.corba.se.spi.orbutil.fsm.Input; 7 8 import javax.servlet.ServletException; 9 import javax.servlet.http.HttpServlet; 10 import javax.servlet.http.HttpServletRequest; 11 import javax.servlet.http.HttpServletResponse; 12 import java.io.*; 13 import java.lang.reflect.InvocationTargetException; 14 import java.lang.reflect.Method; 15 import java.net.URLEncoder; 16 import java.util.List; 17 18 /** 19 * Created by shkstart on 2017/12/02. 20 */ 21 public class DownloadServlet extends HttpServlet { 22 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 23 String servletPath = request.getServletPath(); 24 String methodName = servletPath.substring(1, servletPath.length() - 3); 25 try { 26 Method method = getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); 27 method.invoke(this, request, response); 28 } catch (NoSuchMethodException e) { 29 e.printStackTrace(); 30 } catch (IllegalAccessException e) { 31 e.printStackTrace(); 32 } catch (InvocationTargetException e) { 33 e.printStackTrace(); 34 } 35 } 36 37 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 38 doPost(request, response); 39 } 40 41 protected void show(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 42 FilesDao filesDao = new FilesDaoImpl(); 43 // 查询数据库,将所有数据封装到 list 中 44 List<Files> filesList = filesDao.getAll(); 45 // 将list 封装在 request 域中传回页面 46 request.setAttribute("filesList", filesList); 47 request.getRequestDispatcher("/download.jsp").forward(request, response); 48 } 49 50 // 纯 IO 操作 51 protected void download(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 52 // 根据请求参数获取文件名 53 String fileName = request.getParameter("fileName"); 54 // 根据请求参数获取文件路径 55 String filePath = request.getParameter("filePath"); 56 // 获取文件响应类型,通知浏览器这是一个需要下载的文件,以及应该将该处理交由用户处理 57 response.setContentType("multipart/form-data"); 58 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); 59 // 获取输出流 60 OutputStream outputStream = response.getOutputStream(); 61 // 获取输入流 62 InputStream inputStream = new FileInputStream(filePath); 63 64 // BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); 65 // BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); 66 67 int len; 68 byte[] buffer = new byte[1024]; 69 70 while ((len = inputStream.read()) != -1) { 71 outputStream.write(buffer, 0, len); 72 } 73 74 // bufferedOutputStream.close(); 75 // bufferedInputStream.close(); 76 inputStream.close(); 77 } 78 }
- 在首页点击下载后,请求到 Servlet 中执行 show 方法,并将请求转发到显示页面(download.jsp),如下
1 <%-- 2 Created by IntelliJ IDEA. 3 User: yin‘zhao 4 Date: 2017/12/02 5 Time: 13:28 6 To change this template use File | Settings | File Templates. 7 --%> 8 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 9 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 10 <html> 11 <head> 12 <title>DownloadPage</title> 13 </head> 14 <body> 15 <h3> 16 <c:forEach items="${requestScope.filesList}" var="file"> 17 FileName: ${file.fileName}<br><br> 18 FileDesc: ${file.fileDesc}<br><br> 19 <a href="download.do?fileName=${file.fileName}&filePath=${file.filePath}">Download</a><br><br> 20 <hr><br> 21 </c:forEach> 22 </h3> 23 </body> 24 </html>
- 该案例的 web.xml 文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" 5 version="3.1"> 6 <filter> 7 <filter-name>EncodeFilter</filter-name> 8 <filter-class>com.javaweb.file.servlet.encode.filter.EncodeFilter</filter-class> 9 </filter> 10 <filter-mapping> 11 <filter-name>EncodeFilter</filter-name> 12 <url-pattern>/*</url-pattern> 13 </filter-mapping> 14 <listener> 15 <listener-class>com.javaweb.file.servlet.properties.listener.PropertiesListener</listener-class> 16 </listener> 17 <servlet> 18 <servlet-name>FileUploadServlet</servlet-name> 19 <servlet-class>com.javaweb.file.servlet.helloServlet.FileUploadServlet</servlet-class> 20 </servlet> 21 <servlet> 22 <servlet-name>UploadServlet</servlet-name> 23 <servlet-class>com.javaweb.file.servlet.test.servlet.UploadServlet</servlet-class> 24 </servlet> 25 <servlet> 26 <servlet-name>DownloadServlet</servlet-name> 27 <servlet-class>com.javaweb.file.servlet.test.servlet.DownloadServlet</servlet-class> 28 </servlet> 29 <servlet-mapping> 30 <servlet-name>DownloadServlet</servlet-name> 31 <url-pattern>*.do</url-pattern> 32 </servlet-mapping> 33 <servlet-mapping> 34 <servlet-name>UploadServlet</servlet-name> 35 <url-pattern>/realUploadServlet</url-pattern> 36 </servlet-mapping> 37 <servlet-mapping> 38 <servlet-name>FileUploadServlet</servlet-name> 39 <url-pattern>/uploadServlet</url-pattern> 40 </servlet-mapping> 41 </web-app>
- 点击页面的下载后,执行下载操作(Servlet 中的 download 方法)
写这个小案例时遇到了以下几个问题,折腾了好一会儿,虽然找到了解决方法但是对有些问题并不是很清楚,其解决方法我觉得还有更好,还望大神指教,先谢谢了!
- 使用了 c3p0.0.92.jar 需要 额外的依赖包,使用 0.91.jar 不需要额外的依赖包
- 在下载文件的时候我们利用到了反射,由于没有在 files 类中创建无参构造器,而报错
- 在上一步报错后,自己在 deBug 找错完之后,直接运行时出现1099 端口被占用问题(在 cmd 命令行中杀死 1099 进程)
- 有时候启动 IDEA 的时候会报错(Cannot start internal HTTP server. Git integration, JavaScript debugger and LiveEdit may operate with errors. Please check your firewall settings and restart IntelliJ IDEA),我每次都是关掉防火墙不知道有没有彻底点的解决方案!!!
建议在开发的过程中使用绝对路径,可以避免找不到页面的问题,应该先了解 / 所代表的含义
- 在请求转发的时候、web.xml 文件映射 Servlet 访问路径的时候,/ 代表当前 WEB 应用的根目录
- 在超链接中、form 表单的 action 中、请求重定向的时候,/ 代表当前站点的根目录
- 若 / 交由 Servlet 容器来处理则代表当前 WEB 应用的根目录,若 / 交由浏览器处理则代表当前站点的根目录
至此文件的上传和下载小案例就结束了,欢迎大家提出多的、好的意见,一块进步!