Struts的FormFile与Commons-FileUpload控件使用心得
转自:
http://www.iteye.com/topic/212566
前一段时间刚来公司,看到一个项目中以前有人写的struts代码。是使用了FormFile来处理关于文件上传的模块。但是用力一段时间后,发现出问题了。写完的这个模块,上传文件是没有问题的,但是当服务器的空间较小的时候,穿一个比较大的文件就出问题了,文件还没有上传完,就抛出一个错误的页面,报告上传模块出了问题,而且是Tomcat默认的出错页面。
于是想办法,修改,查看源代码,发现原来写这段代码的人是默认等文件上传完以后进入Action了才判断文件大小是否超出了限制。
但是,默认配置下使用struts的FormFile比较特殊,FormFile是struts包对外的一个接口,而且org.apache.struts.upload包是使用的commons-fileupload-1.0进行的封装。如果使用了它来实现文件上传的功能,则必须是FormFile对象在被初始化以后才能使用,那什么时候它才是被初始化的呢?
答案是:在进入Action之前就已经初始化好了!
因此,原先的设计:在Action中判断文件大小是根本不能在上传过程中起到提示作用的,因为这时候文件已经上传完了。而且这个设计还有一个确定就是不能捕获上传过程中出现的任何问题。也就是说:在Action里我们得到的FormFile对象是上传的一个结果,而不是一个未上传好就可以使用的对象!
那如何控制FormFile上传的过程呢?显然,在Action里处理已经不能奏效了,想想别的办法,让我们翻看一下Struts的源代码找找灵感吧。
这是struts1.1的org.apache.struts.upload包的描述:
(见附件)
从上图我们可以看出有有CommonsMultipartRequestHandler和DiskMultipartRequestHandler两个类实现了MultipartRequestHandler接口。
大家都知道,Commons-fileupload控件在上传的时候,使用的enctype为:enctype="multipart/form-data",因此不难看出MultipartRequestHandler的实现就是来处理enctype="multipart/form-data"这样的post请求的。
但是这里有两个类,CommonsMultipartRequestHandler和DiskMultipartRequestHandler。到底哪个是处理FormFile的上传的呢?这个问题应该从org.apache.struts.config包里来找。
org.apache.struts.config包是用来处理struts配置文件的数据的包。找到org.apache.struts.config. ControllerConfig。
看这几行:
- /**
- * The fully qualified Java class name of the MultipartRequestHandler
- * class to be used.
- */
- protected String multipartClass =
- "org.apache.struts.upload.CommonsMultipartRequestHandler";
- public String getMultipartClass()
- {
- return (this.multipartClass);
- }
- public void setMultipartClass(String multipartClass)
- {
- if (configured)
- {
- throw new IllegalStateException("Configuration is frozen");
- }
- this.multipartClass = multipartClass;
- }
这几行的意思很明白,如果没有在配置文件中配置MultipartRequestHandler实现类的绝对路径,那就使用org.apache.struts.upload.CommonsMultipartRequestHandler类默认处理。
^_^,这就是关键了:struts是默认使用org.apache.struts.upload.CommonsMultipartRequestHandler类来处理FormFile指定的上传文件的。
马上转到org.apache.struts.upload.CommonsMultipartRequestHandler来看看:
- /**
- *默认文件上传的大小是250M
- */
- public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;
- /**
- *上传文件在内存中使用的缓冲区大小,超过次数值的数据写入硬盘。
- */
- public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;
还有,最最重要的实现方法:
- /**
- * Parses the input stream and partitions the parsed items into a set of
- * form fields and a set of file items. In the process, the parsed items
- * are translated from Commons FileUpload <code>FileItem</code> instances
- * to Struts <code>FormFile</code> instances.
- * @param request The multipart request to be processed.
- * @throws ServletException if an unrecoverable error occurs.
- 就是这个函数处理上传文件的request,把request交给Commons FileUpload 控件处理,并解析FileItem转换成Struts的FormFile。
- */
- public void handleRequest(HttpServletRequest request) throws ServletException
再看看这个函数内部是怎么实现的吧?
- // 使用了DiskFileUpload。
- // (Commons-FileUpload很老版本的一个上传实现类了,还在用?我的显示是Deprecated)
- DiskFileUpload upload = new DiskFileUpload();
- // 上传最大值
- upload.setSizeMax((int) getSizeMax(ac));
- // 上传文件在内存中使用的缓冲区大小
- upload.setSizeThreshold((int) getSizeThreshold(ac));
- // 存在硬盘的什么地方,一般是默认
- pload.setRepositoryPath(getRepositoryPath(ac));
接着看handleRequest如何处理request的:
- // Parse the request into file items.
- List items = null;
- try
- {
- items = upload.parseRequest(request);
- }
- //这里是关键:上传过程中出了超出最大值的异常了,如何处理?
- catch (DiskFileUpload.SizeLimitExceededException e)
- {
- // Special handling for uploads that are too big
- request.setAttribute(
- MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,
- Boolean.TRUE);
- return;
- }
- //出了其他异常,如enctype不对,磁盘空间不足怎么办?
- catch (FileUploadException e)
- {
- log.error("Failed to parse multipart request", e);
- throw new ServletException(e);
- }
这次一目了然了:
Struts根本没有把上传过程中出的超出最大值的异常带到Action,而是把它放到了rquest的Attribute里。
而出了其他异常如enctype不对,磁盘空间不足怎么办?很遗憾,Struts没有去处理它,而是log了一下,抛给了上一层了。
那我一定要获得这些全部异常咋办呢?没办法,自己定制一个MultipartRequestHandler吧,那样就能彻底解决上传过程中的控制问题了!
在此之前,我们得先去最新版的commons-fileupload控件看看上传过程中可能抛出多少异常?
- //所有上传异常的父类
- org.apache.commons.fileupload.FileUploadException
- //注意:这个类的类名是FileUploadBase.SizeLimitExceededException是个public内
- //部类。上传的formdata总的数据超出了规定大小
- org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException
- //注意:也是个内部类。这个才是上传的文件超出了规定大小
- org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException
- //其它的,也看看吧:
- org.apache.commons.fileupload.FileUploadBase.FileUploadIOException
- org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException
- org.apache.commons.fileupload.FileUploadBase.IOFileUploadException
- org.apache.commons.fileupload.FileUploadBase.UnknownSizeException
要想获得尽可能仔细的数据就在处理的try/catch块里把上面的异常都catch一下,放到request的attribute里去就OK了。
另外还有要说的是,最好用commons-fileupload控件的最新版本,因为DiskFileUpload这个类,commons-fileupload已经弃用了,取而代之的是ServletFileUpload类了,所以一定要注意!切记,切记…..
这是我写的CommonsMultipartRequestHandler替代类的public void handleRequest(HttpServletRequest request) throws ServletException函数:
- public void handleRequest(HttpServletRequest request) throws ServletException
- {
- // Get the app config for the current request.
- ModuleConfig ac = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
- // DiskFileItem工厂,主要用来设定上传文件的参数
- DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
- // 上传文件所用到的缓冲区大小,超过此缓冲区的部分将被写入到磁盘
- fileItemFactory.setSizeThreshold((int) this.getSizeThreshold(ac));
- // 上传文件用到的临时文件存放位置
- fileItemFactory.setRepository(this.getRepository(ac));
- // 使用fileItemFactory为参数实例化一个ServletFileUpload对象
- // 注意:该对象为commons-fileupload-1.2新增的类.
- // 对于1.2以下的commons-fileupload版本并不存在此类.
- ServletFileUpload upload = new ServletFileUpload(fileItemFactory);
- // 从session中读取对本次上传文件的最大值的限制
- String maxUploadSize = (String)request.getSession().
- getAttribute(BasicConstants.maxUploadSize);
- // 获取struts-config文件中controller标签的maxFileSize属性来确定默认上传的限制
- // 如果struts-config文件中controller标签的maxFileSize属性没设置则使用默认的上传限制250M.
- long defaultOrConfigedMaxUploadSize = this.getSizeMax(ac);
- if (maxUploadSize != null && maxUploadSize != "")
- {
- // 如果maxUploadSize设定不正确则上传限制为defaultOrConfigedMaxUploadSize的值
- // 正确则为maxUploadSize转换成的字节数
- upload.setSizeMax((long) this.convertSizeToBytes(
- maxUploadSize, defaultOrConfigedMaxUploadSize));
- }
- else
- {
- // 如果maxUploadSize没设置则使用默认的上传限制
- upload.setSizeMax(defaultOrConfigedMaxUploadSize);
- }
- // 从session中清空maxUploadSize
- request.getSession().removeAttribute("maxUploadSize");
- // Create the hash tables to be populated.
- elementsText = new Hashtable();
- elementsFile = new Hashtable();
- elementsAll = new Hashtable();
- // Parse the request into file items.
- List items = null;
- // ServletFileUpload类来处理表单请求
- // 抛出的异常为FileUploadException的子异常
- // 如果捕获这些异常就将捕获的异常放到session中返回.
- try
- {
- items = upload.parseRequest(request);
- }
- catch (FileUploadBase.SizeLimitExceededException e)
- {
- // 请求数据的size超出了规定的大小.
- request.getSession().setAttribute(
- BasicConstants.baseSizeLimitExceededException, e);
- return;
- }
- catch (FileUploadBase.FileSizeLimitExceededException e)
- {
- // 请求文件的size超出了规定的大小.
- request.getSession().setAttribute(
- BasicConstants.baseFileSizeLimitExceededException, e);
- return;
- }
- catch (FileUploadBase.IOFileUploadException e)
- {
- // 文件传输出现错误,例如磁盘空间不足等.
- request.getSession().setAttribute(
- BasicConstants.baseIOFileUploadException, e);
- return;
- }
- catch (FileUploadBase.InvalidContentTypeException e)
- {
- // 无效的请求类型,即请求类型enctype != "multipart/form-data"
- request.getSession().setAttribute(
- BasicConstants.baseInvalidContentTypeException, e);
- return;
- }
- catch (FileUploadException e)
- {
- // 如果都不是以上子异常,则抛出此总的异常,出现此异常原因无法说明.
- request.getSession().setAttribute(
- BasicConstants.FileUploadException, e);
- return;
- }
- // Partition the items into form fields and files.
- Iterator iter = items.iterator();
- while (iter.hasNext())
- {
- FileItem item = (FileItem) iter.next();
- if (item.isFormField())
- {
- addTextParameter(request, item);
- }
- else
- {
- addFileParameter(item);
- }
- }
- }
其它部分均未做什么大改变。
好了,替代类写好了,我们怎么去用呢?
这样:在struts-config文件中写配置:
- <!-- ========== Controller Configuration ================================ -->
- <controller>
- <!-- The "input" parameter on "action" elements is the name of a
- local or global "forward" rather than a module-relative path -->
- <set-property value="true" property="inputForward" />
- <set-property value="text/html; charset=UTF-8"
- property="contentType" />
- <!-- 通过写类的全名来替代struts默认的MultipartRequestHandler -->
- <set-property property="multipartClass"
- value="com.amplesky.commonmodule.struts.AmpleskyMultipartRequestHandler" />
- <!-- 规定的上传文件的最大值 -->
- <set-property property="maxFileSize" value="15M" />
- <!-- 缓冲区大小 -->
- <set-property property="memFileSize" value="5M" />
- </controller>
好了!现在我们再用FormFile上传文件,可以在上传之前动态设置或者从配置文件设置上传文件的大小,一旦上传过程中出现了异常,就会被写入request的attributs里。当进入action的时候,通过在Action里获取异常就可以判断上传过程中出了什么问题了,而且在上传过程中文件一旦超出了规定大小,或者磁盘大小不足的情况会立即中断上传的。这样我们的功能就实现了。
最后,感慨一下,
1.感觉commons-fileupload还是挺好用的,但是FormFile的使用不大好,基本上误导能力很强,网上关于FormFile的资料说明很少,以上这些都是我自己摸索出来的。
2.Struts
1都到了1.3版本了,但是对于FormFile的实现依然使用commons-fileupload-1.0版本的DiskFileUpload类,可见更新也不怎么样。其实我觉得这是apache大部分开源工具的一个通病:更新确实不怎么快,commons框架里边的源代码和jar包不一致,还有很多是2004或者2003年的…要命的是居然不支持javase5的新特性????
3.
认为把异常放到request的attributs里不好,曾经尝试过抛出异常然后通过配置exception-controller来抓去异常处理,但是抓不到,怎么都抓不到,后来翻了大半天源码才发现:struts在处理异常请求的时候将出现的ServletException和IOExcepton都交给了上层去处理了,根本不会抛出来。所以这两种异常是抓不到的。
Struts的FormFile与Commons-FileUpload控件使用心得的更多相关文章
- C# 自定义FileUpload控件
摘要:ASP.NET自带的FileUpload控件会随着浏览器的不同,显示的样式也会发生改变,很不美观,为了提高用户体验度,所以我们会去自定义FileUpload控件 实现思路:用两个Button和T ...
- FileUpload控件使用初步
FileUpload控件使用初步 FileUpload控件使用初步: 1.实现文件上传 protected void btnSubmit_click(object sender, EventArg ...
- webform FileUpload控件实例应用 上传图片
首先在根目录下建一个"images"文件: HTML: <form id="form1" runat="server"> < ...
- WebForm之FileUpload控件(文件上传)
FileUpload控件要与Button.LinkButton.ImageButton配合使用 FileUpload控件的方法及属性: 1.SaveAs("要上传到服务器的绝对路径" ...
- ASP.NET让FileUpload控件支持浏览自动上传功能的解决方法
ASP.NET的FileUpload控件默认是不支持服务端的onchange事件的,此时可以用一种变通的方法来实现这一功能. 这就需要借用客户端的onchange事件,调用__doPostBack方法 ...
- FileUpload控件「批次上传 / 多档案同时上传」的范例--以「流水号」产生「变量名称」
原文出處 http://www.dotblogs.com.tw/mis2000lab/archive/2013/08/19/multiple_fileupload_asp_net_20130819. ...
- Fileupload控件导致500错误
问题: 今天遇到一个问题,用Fileupload控件上传Excel文件,用一个button控件调用“FileUpload1.SaveAs”方法,点击按钮后出现服务器500错误.如下图: 解决方法: 在 ...
- FileUpload控件
FileUpload控件 属性:FileName: 获取上传的文件名 HasFile: 是否选择(存在)上传的文件 ContentLength: 获得上窜文件的大小,单位是字节(byte) 方法:Se ...
- UpdatePanel1里面使用FileUpload控件
最近做项目过程中,遇到了UpdatePanel1里面放了一个FileUpload控件,结果从后台就获取不到上传的文件了,找了好久才找到原因.原因: 加了红色部分后立马获取到了.
随机推荐
- strcpy函数用法
字符串是数组类型,不能通过赋值运算进行,要通过strcpy进行拷贝,其中目的字符串必须是字符串变量,源字符串可以是常量,复制后源字符串保持不变. strcpy()是C中的一个复制字符串的库函数,在C+ ...
- Android工具
2018-09-27 安卓签名工具 AndroidKiller 比如包打好了,想替换一个图片,就用zip打开,替换图片,重新签名.
- Android单片机与蓝牙模块通信实例代码
Android单片机与蓝牙模块通信实例代码 参考路径:http://www.jb51.net/article/83349.htm 啦啦毕业了,毕业前要写毕业设计,需要写一个简单的蓝牙APP进行交互,通 ...
- 《mysql必知必会》学习_第20章_20180809_欢
第20章:更新和删除数据 P140 update customers set_emails='elmer@fudd.com' where cust_id=10005; 更新多个列,用逗号隔开.注意被指 ...
- vi显示中文乱码
问题:vi/vim 编辑ANSI文本时,中文会显示乱码! 解决方法:修改vi/vim配置文件,添加如下红色并加粗的部分! vi 配置文件路径:/etc/vircvim 配置文件路径:/etc/v ...
- Android开发 - ImageView加载Base64编码的图片
在我们开发应用的过程中,并不是所有情况下都请求图片的URL或者加载本地图片,有时我们需要加载Base64编码的图片.这种情况出现在服务端需要动态生成的图片,比如: 二维码 图形验证码 ... 这些应用 ...
- 微信小程序scroll-view 横向和纵向scroll-view组件
scroll-view为滚动视图,分为水平滚动和垂直滚动.注意滚动视图垂直滚动时一定要设置高度否则的话scroll-view不会生效.滚动视图常用的地方一般都是Item项比较多的界面,比如我的模块 主 ...
- 什么是RDD?
顾名思义,从字面理解RDD就是 Resillient Distributed Dataset,即弹性分布式数据集. 它是Spark提供的核心抽象. RDD在抽象上来讲是一种抽象的分布式的数据集.它是被 ...
- HTML中META标签的使用
一.META标签简介 <meta> 元素可提供有关页面的元信息,元数据总是以名称/值的形式被成对传递的. <meta> 标签位于文档的头部,不包含任何内容. <meta& ...
- kubernetes集群搭建(3):master节点安装
1.master节点上执行: yum -y install kubernetes flannel etcd 2.修改etcd配置为: [root@k8s-master ~]# vi /etc/etcd ...