加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

JSP 实用程序之简易文件上传组件

发布时间:2020-12-13 21:08:21 所属栏目:PHP教程 来源:网络整理
导读:文件上传,包括但不限于图片上传,是 Web 开发中习以为常的场景,相信各位或多或少都曾写过这方面相干的代码。Java 界若说文件上传,则言必称 Apache Commons FileUpload,论必及 SmartUpload。愈甚者,Servlet 3.0 将文件上传列为 JSR 标准,使得通过几个注

文件上传,包括但不限于图片上传,是 Web 开发中习以为常的场景,相信各位或多或少都曾写过这方面相干的代码。Java 界若说文件上传,则言必称 Apache Commons FileUpload,论必及  SmartUpload。愈甚者,Servlet 3.0 将文件上传列为 JSR 标准,使得通过几个注解就能够在 Servlet 中配置上传,不必依赖任何组件。使用第3方组件或 Servlet 自带组件固然强大,但只靠 JSP 亦能完成任务,且短小而精悍,岂不美哉?本文实现的方法纯洁基于 JSP 代码,没有弄成 Servlet 和专门的 Class(.java),实现方法纯洁是基于 JSP,没有太高的技术难度。实际使用进程中直接部署便可。

操作组件的代码行数不超过 10 行,只需几个步骤:

  1. 生成组件实例
  2. 设置实例属性
  3. 调用上传/下载方法
  4. 处理调用结果

首先是上传页面,本例是1张静态的 HTML。

上传成功以下图所示。


使用 POST 的表单,设置 ContentType 为 multipart/form-data 多段数据,还要记得 input 的 name 属性。

<html> <body> <form action="action.jsp" enctype="multipart/form-data" method="POST"> selectimage: <input type="file" name="myfile" /><br> <input type="submit" value="upload" /> </form> </body> </html>

action 中接受客户端要求的服务端代码在 action.jsp 中。action.jsp 通过 <%@include file="Upload.jsp"%>包括了核心 Java 代码,而 Upload.jsp 里面又包括了另外1个 UploadRequest.jsp 文件。总之,我们这个小小的 Java 程序,1共包括了 UploadRequest 要求信息类、UploadException 自定义异常类和最重要的 Upload 类这3个类。

<%@page pageEncoding="UTF⑻"%> <%@include file="Upload.jsp"%> <% UploadRequest ur = new UploadRequest();// 创建要求信息,所有参数都在这儿设置 ur.setRequest(request); //1定要传入 request ur.setFileOverwrite(true);// 相同文件名是不是覆盖?true=允许覆盖 Upload upload = new Upload();// 上传器 try { upload.upload(ur); } catch (UploadException e) { response.getWriter().println(e.toString()); } if (ur.isOk()) // 上传成功 response.getWriter().println("上传成功:" + ur.getUploaded_save_fileName()); else response.getWriter().println("上传失败!"); %>

这里创建了 UploadRequest 实例。文件上传操作通常会附加1些限制,如:文件类型、上传文件总大小、每一个文件的最大大小等。除此之外,作为1个通用组件还需要斟酌更多的问题, 如:支持自定义文件保存目录、支持相对路径和绝对路径、支持自定义保存的文件的文件名称等。这些配置统统在 UploadRequest 里设置。

至于 JSP 里面的类,我愿意多说说。 JSP 里面允许我们定义 Java 的类,类本是可以是 static,但不能有 static 成员。实际上 JSP 类都是内部类,定义 static 与否关系不大。如果不能定义 static 方法,就把 static 方法移出类体外,书写成,

<%! /** * 获得开头数据头占用的长度 * * @param dateBytes * 文件2进制数据 * @return */ private static int getStartPos(byte[] dateBytes) { .... } %>

 <%! ... %> 和 <% ... %> 不同,前者是定义类成员的。

好~我们在看看 UploadRequest.jsp,就知道具体配置些甚么。

<%@page pageEncoding="UTF⑻"%> <%!/** * 上传要求的 bean,包括所有有关要求的信息 * @author frank * */ public static class UploadRequest { /** * 上传最大文件大小,默许 1 MB */ private int MaxFileSize = 1024 * 1000; /** * 保存文件的目录 */ private String upload_save_folder = "E:temp"; /** * 上传是不是成功 */ private boolean isOk; /** * 是不是更名 */ private boolean isNewName; /** * 成功上传以后的文件名。如果 isNewName = false,则是原上传的名字 */ private String uploaded_save_fileName; /** * 相同文件名是不是覆盖?true=允许覆盖 */ private boolean isFileOverwrite = true; private HttpServletRequest request; /** * @return the maxFileSize */ public int getMaxFileSize() { return MaxFileSize; } /** * @param maxFileSize the maxFileSize to set */ public void setMaxFileSize(int maxFileSize) { MaxFileSize = maxFileSize; } /** * @return the upload_save_folder */ public String getUpload_save_folder() { return upload_save_folder; } /** * @param upload_save_folder the upload_save_folder to set */ public void setUpload_save_folder(String upload_save_folder) { this.upload_save_folder = upload_save_folder; } /** * @return the isOk */ public boolean isOk() { return isOk; } /** * @param isOk the isOk to set */ public void setOk(boolean isOk) { this.isOk = isOk; } /** * @return the isNewName */ public boolean isNewName() { return isNewName; } /** * @param isNewName the isNewName to set */ public void setNewName(boolean isNewName) { this.isNewName = isNewName; } /** * @return the uploaded_save_fileName */ public String getUploaded_save_fileName() { return uploaded_save_fileName; } /** * @param uploaded_save_fileName the uploaded_save_fileName to set */ public void setUploaded_save_fileName(String uploaded_save_fileName) { this.uploaded_save_fileName = uploaded_save_fileName; } /** * @return the isFileOverwrite */ public boolean isFileOverwrite() { return isFileOverwrite; } /** * 相同文件名是不是覆盖?true=允许覆盖 * @param isFileOverwrite the isFileOverwrite to set */ public void setFileOverwrite(boolean isFileOverwrite) { this.isFileOverwrite = isFileOverwrite; } /** * @return the request */ public HttpServletRequest getRequest() { return request; } /** * @param request the request to set */ public void setRequest(HttpServletRequest request) { this.request = request; } } %>

这是1个普通的 Java bean。完成上传逻辑的是 Upload 类。 其原理是,1、由客户端把要上传的文件生成 request 数据流,与服务器端建立连接;2、在服务器端接收 request 流,将流缓存到内存中;3、由服务器真个内存把文件输出到指定的目录。Upload.jsp 完全代码以下所示。

<%@page pageEncoding="UTF⑻" import="java.io.*"%> <%@include file="UploadRequest.jsp"%> <%! public static class UploadException extends Exception { private static final long serialVersionUID = 579958777177500819L; public UploadException(String msg) { super(msg); } } public static class Upload { /** * 接受上传 * * @param uRequest * 上传 POJO * @return * @throws UploadException */ public UploadRequest upload(UploadRequest uRequest) throws UploadException { HttpServletRequest req = uRequest.getRequest(); // 获得客户端上传的数据类型 String contentType = req.getContentType(); if(!req.getMethod().equals("POST")){ throw new UploadException("必须 POST 要求"); } if (contentType.indexOf("multipart/form-data") == ⑴) { throw new UploadException("未设置表单 multipart/form-data"); } int formDataLength = req.getContentLength(); if (formDataLength > uRequest.getMaxFileSize()) { // 是不是超大 throw new UploadException("文件大小超过系统限制!"); } // 保存上传的文件数据 byte dateBytes[] = new byte[formDataLength]; int byteRead = 0,totalRead = 0; try(DataInputStream in = new DataInputStream(req.getInputStream());){ while (totalRead < formDataLength) { byteRead = in.read(dateBytes,totalRead,formDataLength); totalRead += byteRead; } } catch (IOException e) { e.printStackTrace(); throw new UploadException(e.toString()); } // 获得数据分割字符串 int lastIndex = contentType.lastIndexOf("="); // 数据分割线开始位置boundary=--------------------------- String boundary = contentType.substring(lastIndex + 1,contentType.length());// --------------------------⑵57261863525035 // 计算开头数据头占用的长度 int startPos = getStartPos(dateBytes); // 边界位置 int endPos = byteIndexOf(dateBytes,boundary.getBytes(),(dateBytes.length - startPos)) - 4; // 创建文件 String fileName = uRequest.getUpload_save_folder() + getFileName(dateBytes,uRequest.isNewName()); uRequest.setUploaded_save_fileName(fileName); File checkedFile = initFile(uRequest); // 写入文件 try(FileOutputStream fileOut = new FileOutputStream(checkedFile);){ fileOut.write(dateBytes,startPos,endPos - startPos); fileOut.flush(); uRequest.setOk(true); } catch (FileNotFoundException e) { e.printStackTrace(); throw new UploadException(e.toString()); } catch (IOException e) { e.printStackTrace(); throw new UploadException(e.toString()); } return uRequest; } } /** * 获得开头数据头占用的长度 * * @param dateBytes * 文件2进制数据 * @return */ private static int getStartPos(byte[] dateBytes) { int startPos; startPos = byteIndexOf(dateBytes,"filename=&;".getBytes(),0); startPos = byteIndexOf(dateBytes,"n".getBytes(),startPos) + 1; // 遍历掉3个换行符到数据块 startPos = byteIndexOf(dateBytes,startPos) + 1; startPos = byteIndexOf(dateBytes,startPos) + 1; return startPos; } /** * 在字节数组里查找某个字节数组,找到返回>=0,未找到返回⑴ * @param data * @param search * @param start * @return */ private static int byteIndexOf(byte[] data,byte[] search,int start) { int index = ⑴; int len = search.length; for (int i = start,j = 0; i < data.length; i++) { int temp = i; j = 0; while (data[temp] == search[j]) { // System.out.println((j+1)+",值:"+data[temp]+","+search[j]); // 计数 j++; temp++; if (j == len) { index = i; return index; } } } return index; } /** * 如果没有指定目录则创建;检测是不是可以覆盖文件 * * @param uRequest * 上传 POJO * @return * @throws UploadException */ private static File initFile(UploadRequest uRequest) throws UploadException { File dir = new File(uRequest.getUpload_save_folder()); if (!dir.exists()) dir.mkdirs(); File checkFile = new File(uRequest.getUploaded_save_fileName()); if (!uRequest.isFileOverwrite() && checkFile.exists()) { throw new UploadException("文件已存在,制止覆盖!"); } return checkFile; } /** * 获得 POST Body 中的文件名 * * @param dateBytes * 文件2进制数据 * @param isAutoName * 是不是自定命名,true = 时间戳文件名 * @return */ private static String getFileName(byte[] dateBytes,boolean isAutoName) { String saveFile = null; if(isAutoName){ saveFile = "2016" + System.currentTimeMillis(); } else { String data = null; try { data = new String(dateBytes,"UTF⑻"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); data = "errFileName"; } // 获得上传的文件名 saveFile = data.substring(data.indexOf("filename=&;") + 10); saveFile = saveFile.substring(0,saveFile.indexOf("n")); saveFile = saveFile.substring(saveFile.lastIndexOf("") + 1,saveFile.indexOf("&;")); } return saveFile; } %>

通过 DataInputStream 读取流数据到 dataBytes 中然后写入 FileOutputStream。另外还有些围绕配置的逻辑。

值得1提的是,Tomcat 7 下 JSP 默许的 Java 语法仍旧是 1.6 的。在 JSP 里面嵌入 Java 1.7 特性的代码会抛出“Resource specification not allowed here for source level below 1.7”的异常。因而需要修改 Tomcat/conf/web.xml 里面的配置文件,找到 <servlet> 节点,加入下面粗体部份才可以。注意是 jsp 节点,不是 default 节点(很类似)。

<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <strong> <init-param> <param-name>compilerSourceVM</param-name> <param-value>1.7</param-value> </init-param> <init-param> <param-name>compilerTargetVM</param-name> <param-value>1.7</param-value> </init-param></strong> <load-on-startup>3</load-on-startup> </servlet>

至此,1个简单的文件上传器就完成了。但是本组件的缺点还是很明显的,试罗列两项:1、上传流占用内存而非磁盘,所以上传大文件时内存会吃紧;2、尚不支持多段文件上传,也就是1次只能上传1个文件。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读