Java Web开发人员可使用Apache文件上传组件来接收浏览器上传的文件,该组件由多个类共同组成,可是,对于使用该组件来编写文件上传功能的Java Web开发人员来讲,只须要了解和使用其中的三个类:html
DiskFileUpload、FileItem和FileUploadException。
这三个类所有位于org.apache.commons.fileupload包中。java
在准备实验环境时得到的commons-fileupload-1.0.zip文件的解压缩目录中能够看到一个docs的子目录,其中包含了Apache文件上传组件中的各个API类的帮助文档,从这个文档中能够了解到各个API类的使用帮助信息。打开文件上传组件API帮助文档中的index.html页面,在左侧分栏窗口页面中列出了文件上传组件中的各个API类的名称。apache
图1.2数组
读者不须要逐个去阅读图1.2中列出的各个API类的帮助文档,而应该以图1.2中的示例代码为线索,以其中所使用到的类为入口点,按图索骥地进行阅读,对于示例代码中调用到的各个API类的方法则应重点掌握。浏览器
DiskFileUpload类是Apache文件上传组件的核心类,应用程序开发人员经过这个类来与Apache文件上传组件进行交互。但如今Apache建议使用ServletFileUpload类,两个类的方法相似。下面介绍DiskFileUpload类中的几个经常使用的重要方法。tomcat
setSizeMax方法用于设置请求消息实体内容的最大容许大小,以防止客户端故意经过上传特大的文件来塞满服务器端的存储空间,单位为字节。其完整语法定义以下:服务器
public void setSizeMax(long sizeMax)网络
若是请求消息中的实体内容的大小超过了setSizeMax方法的设置值,该方法将会抛出FileUploadException异常。app
Apache文件上传组件在解析和处理上传数据中的每一个字段内容时,须要临时保存解析出的数据。由于Java虚拟机默承认以使用的内存空间是有限的(笔者测试不大于100M),超出限制时将会发生“java.lang.OutOfMemoryError”错误,若是上传的文件很大,例如上传800M的文件,在内存中将没法保存该文件内容,Apache文件上传组件将用临时文件来保存这些数据;但若是上传的文件很小,例如上传600个字节的文件,显然将其直接保存在内存中更加有效。setSizeThreshold方法用于设置是否使用临时文件保存解析出的数据的那个临界值,该方法传入的参数的单位是字节。其完整语法定义以下:jsp
public void setSizeThreshold(int sizeThreshold)
setRepositoryPath方法用于设置setSizeThreshold方法中提到的临时文件的存放目录,这里要求使用绝对路径。其完整语法定义以下:
public void setRepositoryPath(String repositoryPath)
若是不设置存放路径,那么临时文件将被储存在"java.io.tmpdir"这个JVM环境属性所指定的目录中,tomcat 5.5.9将这个属性设置为了“<tomcat安装目录>/temp/”目录。
parseRequest 方法是DiskFileUpload类的重要方法,它是对HTTP请求消息进行解析的入口方法,若是请求消息中的实体内容的类型不是“multipart/form-data”,该方法将抛出FileUploadException异常。parseRequest 方法解析出FORM表单中的每一个字段的数据,并将它们分别包装成独立的FileItem对象,而后将这些FileItem对象加入进一个List类型的集合对象中返回。parseRequest 方法的完整语法定义以下:
public List parseRequest(HttpServletRequest req)
parseRequest 方法还有一个重载方法,该方法集中处理上述全部方法的功能,其完整语法定义以下:
parseRequest(HttpServletRequest req,int sizeThreshold,long sizeMax,
String path)
这两个parseRequest方法都会抛出FileUploadException异常。
isMultipartContent方法方法用于判断请求消息中的内容是不是“multipart/form-data”类型,是则返回true,不然返回false。isMultipartContent方法是一个静态方法,不用建立DiskFileUpload类的实例对象便可被调用,其完整语法定义以下:
public static final boolean isMultipartContent(HttpServletRequest req)
因为浏览器在提交FORM表单时,会将普通表单中填写的文本内容传递给服务器,对于文件上传字段,除了传递原始的文件内容外,还要传递其文件路径名等信息,如后面的图1.3所示。无论FORM表单采用的是“application/x-www-form-urlencoded”编码,仍是“multipart/form-data”编码,它们仅仅是将各个FORM表单字段元素内容组织到一块儿的一种格式,而这些内容又是由某种字符集编码来表示的。关于浏览器采用何种字符集来编码FORM表单字段中的内容,请参看笔者编著的《深刻体验java Web开发内幕——核心基础》一书中的第6.9.2的讲解,“multipart/form-data”类型的表单为表单字段内容选择字符集编码的原理和方式与“application/x-www-form-urlencoded”类型的表单是相同的。FORM表单中填写的文本内容和文件上传字段中的文件路径名在内存中就是它们的某种字符集编码的字节数组形式,Apache文件上传组件在读取这些内容时,必须知道它们所采用的字符集编码,才能将它们转换成正确的字符文本返回。
对于浏览器上传给WEB服务器的各个表单字段的描述头内容,Apache文件上传组件都须要将它们转换成字符串形式返回,setHeaderEncoding 方法用于设置转换时所使用的字符集编码,其原理与笔者编著的《深刻体验java Web开发内幕——核心基础》一书中的第6.9.4节讲解的ServletRequest.setCharacterEncoding方法相同。setHeaderEncoding 方法的完整语法定义以下:
public void setHeaderEncoding(String encoding)
其中,encoding参数用于指定将各个表单字段的描述头内容转换成字符串时所使用的字符集编码。
注意:若是读者在使用Apache文件上传组件时遇到了中文字符的乱码问题,通常都是没有正确调用setHeaderEncoding方法的缘由。
FileItem类用来封装单个表单字段元素的数据,一个表单字段元素对应一个FileItem对象,经过调用FileItem对象的方法能够得到相关表单字段元素的数据。
FileItem是一个接口,在应用程序中使用的其实是该接口一个实现类,该实现类的名称并不重要,程序能够采用FileItem接口类型来对它进行引用和访问,为了便于讲解,这里将FileItem实现类称之为FileItem类。FileItem类还实现了Serializable接口,以支持序列化操做。
对于“multipart/form-data”类型的FORM表单,浏览器上传的实体内容中的每一个表单字段元素的数据之间用字段分隔界线进行分割,两个分隔界线间的内容称为一个分区,每一个分区中的内容能够被看做两部分,一部分是对表单字段元素进行描述的描述头,另一部是表单字段元素的主体内容。
主体部分有两种可能性,要么是用户填写的表单内容,要么是文件内容。FileItem类对象实际上就是对图1.3中的一个分区的数据进行封装的对象,它内部用了两个成员变量来分别存储描述头和主体内容,其中保存主体内容的变量是一个输出流类型的对象。当主体内容的大小小于DiskFileUpload.setSizeThreshold方法设置的临界值大小时,这个流对象关联到一片内存,主体内容将会被保存在内存中。当主体内容的数据超过DiskFileUpload.setSizeThreshold方法设置的临界值大小时,这个流对象关联到硬盘上的一个临时文件,主体内容将被保存到该临时文件中。临时文件的存储目录由DiskFileUpload.setRepositoryPath方法设置,临时文件名的格式为“upload_00000005(八位或八位以上的数字).tmp”这种形式,FileItem类内部提供了维护临时文件名中的数值不重复的机制,以保证了临时文件名的惟一性。当应用程序将主体内容保存到一个指定的文件中时,或者在FileItem对象被垃圾回收器回收时,或者Java虚拟机结束时,Apache文件上传组件都会尝试删除临时文件,以尽可能保证临时文件能被及时清除。
下面介绍FileItem类中的几个经常使用的方法:
isFormField方法用于判断FileItem类对象封装的数据是否属于一个普通表单字段,仍是属于一个文件表单字段,若是是普通表单字段则返回true,不然返回false。该方法的完整语法定义以下:
public boolean isFormField()
getName方法用于得到文件上传字段中的文件名,对于图1.3中的第三个分区所示的描述头,getName方法返回的结果为字符串“C:\bg.gif”。若是FileItem类对象对应的是普通表单字段,getName方法将返回null。即便用户没有经过网页表单中的文件字段传递任何文件,但只要设置了文件表单字段的name属性,浏览器也会将文件字段的信息传递给服务器,只是文件名和文件内容部分都为空,但这个表单字段仍然对应一个FileItem对象,此时,getName方法返回结果为空字符串"",读者在调用Apache文件上传组件时要注意考虑这个状况。getName方法的完整语法定义以下:
public String getName()
注意:若是用户使用Windows系统上传文件,浏览器将传递该文件的完整路径,若是用户使用Linux或者Unix系统上传文件,浏览器将只传递该文件的名称部分。
getFieldName方法用于返回表单字段元素的name属性值,也就是返回图1.3中的各个描述头部分中的name属性值,例如“name=p1”中的“p1”。getFieldName方法的完整语法定义以下:
public String getFieldName()
write方法用于将FileItem对象中保存的主体内容保存到某个指定的文件中。若是FileItem对象中的主体内容是保存在某个临时文件中,该方法顺利完成后,临时文件有可能会被清除。该方法也可将普通表单字段内容写入到一个文件中,但它主要用途是将上传的文件内容保存在本地文件系统中。其完整语法定义以下:
public void write(File file)
getString方法用于将FileItem对象中保存的主体内容做为一个字符串返回,它有两个重载的定义形式:
public java.lang.String getString() public java.lang.String getString(java.lang.String encoding) throws java.io.UnsupportedEncodingException
前者使用缺省的字符集编码将主体内容转换成字符串,后者使用参数指定的字符集编码将主体内容转换成字符串。若是在读取普通表单字段元素的内容时出现了中文乱码现象,请调用第二个getString方法,并为之传递正确的字符集编码名称。
getContentType 方法用于得到上传文件的类型,对于图1.3中的第三个分区所示的描述头,getContentType方法返回的结果为字符串“image/gif”,即“Content-Type”字段的值部分。若是FileItem类对象对应的是普通表单字段,该方法将返回null。getContentType 方法的完整语法定义以下:
public String getContentType()
isInMemory方法用来判断FileItem类对象封装的主体内容是存储在内存中,仍是存储在临时文件中,若是存储在内存中则返回true,不然返回false。其完整语法定义以下:
public boolean isInMemory()
delete方法用来清空FileItem类对象中存放的主体内容,若是主体内容被保存在临时文件中,delete方法将删除该临时文件。尽管Apache组件使用了多种方式来尽可能及时清理临时文件,但系统出现异常时,仍有可能形成有的临时文件被永久保存在了硬盘中。在有些状况下,能够调用这个方法来及时删除临时文件。其完整语法定义以下:
public void delete()
在文件上传过程当中,可能发生各类各样的异常,例如网络中断、数据丢失等等。为了对不一样异常进行合适的处理,Apache文件上传组件还开发了四个异常类,其中FileUploadException是其余异常类的父类,其余几个类只是被间接调用的底层类,对于Apache组件调用人员来讲,只需对FileUploadException异常类进行捕获和处理便可。
ServletRequestContext类提供访问request的方法。实现RequestContext接口。
1 package biz; 2 3 import java.io.File; 4 import java.io.UnsupportedEncodingException; 5 import java.util.HashMap; 6 import java.util.Iterator; 7 import java.util.List; 8 import java.util.Map; 9 10 import javax.servlet.ServletException; 11 import javax.servlet.http.HttpServlet; 12 import javax.servlet.http.HttpServletRequest; 13 14 import org.apache.commons.fileupload.FileItem; 15 import org.apache.commons.fileupload.FileUploadException; 16 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 17 import org.apache.commons.fileupload.servlet.ServletFileUpload; 18 19 /** 20 * commons fileupload 包装类 21 * @author kiant 22 * @version Sep 9, 2008 23 */ 24 public class MutiFileUpload extends HttpServlet { 25 private Map<String, String> parameters; //保存普通 form 表单域 26 private Map<String,FileItem> files; //保存上传文件 27 28 private String encoding; //设置编码格式,推荐 jsp 和 处理类 均为 UTF-8 29 private Long sizeMax; //设置上传数据的最大数据 30 private int sizeThreshold; //设置内存缓冲区的阀值 31 private String repositoryPath; //文件超出缓冲区大小时的临时存放目录 32 33 private String uploadDir; //上传文件保存文件夹 34 35 36 /* 37 * methods 38 */ 39 /** 40 * 对 request 进行处理 41 * @throws UnsupportedEncodingException 42 */ 43 public void handleRequest(HttpServletRequest request) throws ServletException, UnsupportedEncodingException { 44 parameters = new HashMap<String, String>(); 45 files = new HashMap<String, FileItem>(); 46 47 //DiskFileItem工厂,主要用来设定上传文件的参数 48 DiskFileItemFactory factory = new DiskFileItemFactory(); 49 factory.setSizeThreshold(getSizeThreshold()); //设置内存缓冲区的阀值 50 if (getRepositoryPath() != null && getRepositoryPath() != "") { 51 factory.setRepository(new File(getRepositoryPath())); //临时目录,默认 52 } 53 54 // 使用fileItemFactory为参数实例化一个ServletFileUpload对象 55 ServletFileUpload upload = new ServletFileUpload(factory); 56 upload.setHeaderEncoding(getEncoding()); //设置编码格式,推荐 jsp 和 处理类 均为 UTF-8 57 upload.setSizeMax(getSizeMax()); //设置上传数据的最大数据 58 59 // 开始处理表单请求 60 try { 61 List items = upload.parseRequest(request); 62 Iterator it = items.iterator(); 63 while (it.hasNext()) { 64 FileItem item = (FileItem) it.next(); 65 if (item.isFormField()) { //若是是表单字段 66 String name = item.getFieldName(); 67 String value = item.getString(getEncoding()); 68 parameters.put(name, value); 69 } else { //若是是文件字段 70 String name = item.getFieldName(); 71 files.put(name, item); 72 } 73 } 74 75 } catch (FileUploadException e) { 76 e.printStackTrace(); 77 } 78 79 } 80 81 82 /* 83 * property accessors 84 */ 85 public Map<String, String> getParameters() { 86 return parameters; 87 } 88 public void setParameters(Map<String, String> parameters) { 89 this.parameters = parameters; 90 } 91 public Map<String, FileItem> getFiles() { 92 return files; 93 } 94 public void setFiles(Map<String, FileItem> files) { 95 this.files = files; 96 } 97 public String getEncoding() { 98 return encoding; 99 } 100 public void setEncoding(String encoding) { 101 this.encoding = encoding; 102 } 103 public Long getSizeMax() { 104 return sizeMax; 105 } 106 public void setSizeMax(Long sizeMax) { 107 this.sizeMax = sizeMax; 108 } 109 public int getSizeThreshold() { 110 return sizeThreshold; 111 } 112 public void setSizeThreshold(int sizeThreshold) { 113 this.sizeThreshold = sizeThreshold; 114 } 115 public String getRepositoryPath() { 116 return repositoryPath; 117 } 118 public void setRepositoryPath(String repositoryPath) { 119 this.repositoryPath = repositoryPath; 120 } 121 122 public String getUploadDir() { 123 return uploadDir; 124 } 125 126 public void setUploadDir(String uploadDir) { 127 this.uploadDir = uploadDir; 128 } 129 130 }
1 ** 2 * Method execute 3 * 4 * @param mapping 5 * @param form 6 * @param request 7 * @param response 8 * @return ActionForward 9 * @throws IOException 10 */ 11 public ActionForward execute(ActionMapping mapping, ActionForm form, 12 HttpServletRequest request, HttpServletResponse response) throws IOException { 13 14 try { 15 //利用组件读取 "multipart/form-data" 传输流文件的具体参数 16 getMutiFileUpload().handleRequest(request); 17 18 String opusName = getMutiFileUpload().getParameters().get("opusName"); 19 String comments = getMutiFileUpload().getParameters().get("comments"); 20 Integer cid = Integer.parseInt(getMutiFileUpload().getParameters().get("categorie0")); 21 int fullWidht = Integer.parseInt(getMutiFileUpload().getParameters().get("fullView")); 22 23 FileItem fileItem = getMutiFileUpload().getFiles().get("picFile"); 24 EcAccount account = (EcAccount)request.getSession().getAttribute("account"); 25 String fileDir = getMutiFileUpload().getUploadDir() + account.getLoginId(); // 保存目录 26 SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssS"); 27 String fileName = format.format(new Date()); // 保存文件名 28 fileName = fileDir + "/" + fileName; 29 String[] strType = fileItem.getContentType().split("/"); 30 String extName = strType[1]; //文件扩展名 31 32 //这里我省略了有效性的验证 33 34 //填充 opus实体 35 EcOpus opus = new EcOpus(); 36 opus.setEcAccount(account); 37 opus.setEcOpusCategorie(getEcOpusBiz().findCategorieID(cid)); 38 opus.setOpusName(opusName); 39 opus.setOpusComments(comments); 40 opus.setOriginalView(fileName + "o." + extName); 41 opus.setLinkView(fileName + "l." + extName); 42 opus.setSmallView(fileName + "s." + extName); 43 opus.setFullView(fileName + "f." + extName); 44 opus.setSubmitted(new Date()); 45 opus.setImageSize(0); 46 opus.setWidth(0); 47 opus.setHeight(0); 48 opus.setComment(0); 49 opus.setFavourite(0); 50 opus.setTodayView(0); 51 opus.setTotalView(0); 52 opus.setDeleteFlag(Byte.parseByte("0")); 53 54 // 得到写入路径 55 String absoluteDir = servlet.getServletContext().getRealPath("/").replaceAll("\\\\", "/"); 56 57 // 开始写入 58 getEcOpusBiz().addOpus(opus, absoluteDir, fileItem, fullWidht, extName); 59 60 return mapping.findForward("success"); 61 } catch (Exception e) { 62 e.printStackTrace(); 63 return mapping.findForward("fail"); 64 } 65 }
1 // 原文件保存 2 File file = new File(absoluteDir + opus.getOriginalView()); 3 fileItem.write(file);