`
netfork
  • 浏览: 488472 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

谈谈我对Struts2文件上传的理解(顺便讲了一下Struts2处理请求的大体过程)

阅读更多
在写上传文件的程序时,出现了异常,引发了我对Struts2上传文件的过程的Debug,结果有点心得,也想明白了我的一些疑惑,把整个过程与大家共享一下。

没有图只有字,大家如果看着不爽,可以直接看问题以及解决方法。


下文书中包的版本:commons-fileupload-1.2.1.jar、struts2-core-2.1.2.jar

孙鑫的书《Struts2 深入详解》509页是关于限制上传文件的最大长度的内容。
其中谈到fileUpload拦截器只是当文件上传到服务器上之后,才进行的文件类型和大小判断。
Struts2框架底层默认用的是apache的commons-fileupload组件对上传文件进行接受处理。
通过struts.multipart.maxSize属性来对文件大小进行限定时,将直接影响到commons-fileupload组件的文件大小设定,默认是2M。当上传文件超过了这个尺寸时,将从commons-fileupload组件中抛出SizeLimitExceededException异常。上传文件拦截器捕获到这个异常后,将直接把该异常信息设置为Action级别的错误信息。

经过我的测试和对源代码的Debug,发现确实如孙鑫书中所言,如果上传文件大于2M时,在页面上就出现了一堆英文的错误信息,大致是:the request was rejected because its size....exceeds the configured maximum...并且在fieUpload中将来自MultiPartRequestWrapper型request对象的错误信息给加到了Action的错误中。

这时候,你在ApplicationResources.properties中自定义的上传文件过大的错误信息根本不起作用。原因就如书上所言,在底层commons-fileupload组件中就把异常给抛出来了文件根本没被上传,所以到了fileUpload拦截器时,根据取不到文件,当然也就没法对文件的类型和大小进行判断了。

然而,这个异常直接带来两个问题:

1、在页面上显示了英文的错误信息。这样的信息显然不是我们想要的。
2、由于错误的产生,原来页面上输入的其他文本内容也都不见了,也就是说params注入失败。


带着这两个问题,我们来探寻一下Struts2对于请求的处理过程。
注:这并不是一篇关于Struts2请求过程的介绍,主要是为了解决以上两个问题,才引起的简单分析。

首先当然我们要拿FilterDispatcher开刀。

在doFilter方法中调用了prepareDispatcherAndWrapRequest方法,为了包装出Struts2自己的request对象,在prepareDispatcherAndWrapRequest方法中调用Dispatcher类的wrapRequest方法,在这个方法里,会根据请求内容的类型(提交的是文本的,还是multipart/form-data格式),决定是使用tomcat的HttpServletRequestWrapper类分离出请求中的数据,还是使用Struts2的MultiPartRequestWrapper来分离请求中的数据。
注:向服务器请求时,数据是以流的形式向服务器提交,内容是一些有规则东东,我们平时在jsp中用request内置对象取parameter时,实际上是由tomcat的HttpServletRequestWrapper类分解好了的,无需我们再分解这些东西了。

当然,在这里,我们研究的是上传文件的情况,所以,由于form中设定的提交内容是媒体格式的,所以,Dispatcher类的wrapRequest方法会将请求交由MultiPartRequestWrapper类来处理。

MultiPartRequestWrapper这个类是Struts2的类,并且继承了tomcat的HttpServletRequestWrapper类,也是我们将用来代替HttpServletRequest这个类的类,看名字也知道,是对多媒体请求的包装类。
Struts2本身当然不会再造个轮子,来解析请求,而是交由Apache的commons-fileupload组件来解析了。
在MultiPartRequestWrapper的构造方法中,会调用MultiPartRequest(默认为JakartaMultiPartRequest类)的parse方法来解析请求。

在Struts2的JakartaMultiPartRequest类的parse方法中才会真正来调用commons-fileupload组件的ServletFileUpload类对请求进行解析,至此,Struts2已经实现了将请求转交commons-fileupload组件对请求解析的全过程。剩下的就是等commons-fileupload组件对请求解析完毕后,拿到分解后的数据,根据field名,依次将分解后的field名和值放到params(HashMap类型)里,同时JakartaMultiPartRequest类重置了HttpServletRequest的好多方法,比如熟知的getParameter、getParameterNames、getParameterValues,实际上都是从解析后得到的那个params对象里拿数据,在这个过程,commons-fileupload组件也乖乖的把上传的文件分析好了,JakartaMultiPartRequest也毫不客气的把分解后的文件一个一个的放到了files(HashMap类型)中,实际上此时,commons-fileupload组件已经所有要上传的文件上传完了。至此,Struts2实现了对HttpServletRequest类的包装,当回到MultiPartRequestWrapper类后,再取一下上述解析过程中发生的错误,然后把错误加到了自己的errors列表中了。同样我们会发现在MultiPartRequestWrapper类中,也把HttpServletRequest类的好多方法重载了,毕竟是个包装类嘛,实际上对于上传文件的请求,在Struts2后期的处理中用到的request都是MultiPartRequestWrapper类对象,比如我们调用getParameter时,直接调用的是MultiPartRequestWrapper的getParameter方法,间接调的是JakartaMultiPartRequest类对象的getParameter方法。
注:从这里,我们就可以看出,JakartaMultiPartRequest是完全设计成可以替换的类了。

然后继续向回返,到了Dispatcher类的wrapRequest方法,直接把MultiPartRequestWrapper对象返回了,我们就终于回到了FilterDispatcher类的prepareDispatcherAndWrapRequest方法,此时,我们拿到了完全解析好了的request对象(MultiPartRequestWrapper类),该对象又进一步被返回到了FilterDispatcher类的doFilter方法,也就是回到了出发点,至此,doFilter中拿到的request对象就是一个将请求中的数据分解好的了HttpServletRequest对象,我们完全可以用getParameter方法取其中的数据了,同时,我们也可以用getFiles得到文件数组了。
doFilter方法中,会进一步调用actionMapper的getMapping方法对url进行解析,找出命名空间和action名等,以备后面根据配置文件调用相应的拦截器和action使用。

关于doFilter方法中下一步对Dispatcher类的serviceAction方法的调用,不再描述,总之在action被调用之前,会首先走到fileUpload拦截器(对应的是FileUploadInterceptor类),在这个拦截器中,会先看一下request是不是 MultiPartRequestWrapper,如果不是,就说明不是上传文件用的request,fildUpload拦截器会直接将控制权交给下一个拦截器;如果是,就会把request对象强转为MultiPartRequestWrapper对象,然后调用hasErrors方法,看看有没有上传时候产生的错误,有的话,就直接加到了Action的错误(Action级别的)中了。另外,在fileUpload拦截器中会将MultiPartRequestWrapper对象中放置的文件全取出来,把文件、文件名、文件类型取出来,放到request的parameters中,这样到了params拦截器时,就可以轻松的将这些内容注入到Action中了,这也就是为什么fileUpload拦截器需要放在params拦截器前面的理由。在文件都放到request的parameters对象里之后,fileUpload拦截器会继续调用其他拦截器直到Action等执行完毕,他还要做一个扫尾的工作:把临时文件夹中的文件删除(这些文件是由commons-fileupload组件上传的,供你在自己的Action中将文件copy到指定的目录下,当action执行完了后,这些临时文件当然就没用了)。

你好,你还在看吗?呵呵,是不是太多了,也太乱了,没办法,Struts2就是这样的调用的。也不知道Struts2有没有公开其Sequence图,我是想画一个,不过,太懒,还是看着代码说说吧。

如果上面看烦了,也完全可以不看了,直接看下面的。

在上面一番分析之后,文件上传的全过程就结束了。
我们回到我们的问题上来。

先看第一个:
1、在页面上显示了英文的错误信息。这显然不是我们想要的。

没办法了,commons-fileupload组件没想到国际化,在FileUploadInterceptor拦载器中,也没想着国际化,直接放到Action的错误中了,就没他事了,三种做法:
  (1)在错误显示之前,把这条错误给换掉,应该难度不大,我没做留给你做了。
  (2)或者重写一下JakartaMultiPartRequest这个类,把捕捉到的异常信息换成自己的,然后,通过Struts2的配置文件,把我们重写的这个parser换上去用。
  (3)直接改commons-fileupload组件的类,换成中文的。
我具体说一下第(3)种做法:找到FileUploadBase类,把902行~908行改一下。
FileUploadException ex =
    new SizeLimitExceededException(
        "the request was rejected because"
        + " its size (" + pCount
        + ") exceeds the configured maximum"
        + " (" + pSizeMax + ")",
        pCount, pSizeMax);
=>
FileUploadException ex = new SizeLimitExceededException(
"服务器拒绝了您的请求,原因可能是向服务器提交的数据发生了丢失。", pCount, pSizeMax);

把914行~918行改一下。
throw new SizeLimitExceededException(
        "the request was rejected because its size ("
        + requestSize
        + ") exceeds the configured maximum ("
        + sizeMax + ")",
=>
throw new SizeLimitExceededException("服务器拒绝了您的请求,原因是提交数据量过大(通常是由于上传文件过大),请返回上页重试。"
+ " (最大字节数:" + sizeMax / 1024
+ "K)", requestSize, sizeMax);


再看一下第二个问题。
2、由于错误的产生,原来页面上输入的内容也全部不见了,也就是说params注入失败。
关于这个问题我在javaeye上搜索到一篇文章(使用的commons-fileupload组件的jar包似乎比较老)。
http://www.iteye.com/topic/197345

虽然按照此文,当上传失败时,能够将其他输入内容显示出来,但是这样做的结果是全部的文件肯定会上传到服务器上,也就是说,虽然是页面上报了文件因为太大,请求被拒绝的错,但是文件依然会被上传到服务器上,commons-fileupload组件根本没会去拦文件的上传。
在这里要说明一下,如果你不抛出这个异常,请求的流会继续向服务器上传,只有当整个流上传完了之后,commons-fileupload组件才能正确的分析出文件部分、文本部分。所以,在这里抛出异常是不得已的作法,如果不抛异常,后果是虽然页面报错,但文件还是会被传到服务器的上,这一步根本没挡住输入流的上传,如果没挡住的话,大家想想会有什么后果?
所以,综上所述,对于第二个问题,如果出现了这个异常,我们根本无法让原来输入的内容还显示出来的,因为commons-fileupload组件并没有解析全部的输入内容,直接给出异常了,到了params拦截器中,request里就是空的,根本取不到parameter,所以也就无法注入到Action中了。这种情况下,只能显示一个告知用户由于提交数据量过大,服务器拒绝了请求的错误信息,比较好的方法是,直接跳到一个专门的页面,提示用户,然后让用户点返回来再次输入,否则用户会感觉上传文件大就大吧,怎么连我输入的其他一些内容也没给保存住。当然,如果能用Ajax来上传文件,对客户的操作体验可能是最好的,但是,这样可能会导致服务器上有些挂空的文件(上传后从来没被用过),需要想法清除的。

整个分析下来,我们说第二个问题基本上是无法避免的。
分享到:
评论
5 楼 ldbjakyo 2009-10-04  
kjj 写道
struts2 的核心在于action 简化web开发,其他的像文件上传这种鸡肋的功能,我认为没必要一定要跟随他的走,也没必要一定要用commons-upload这个,cos 就不存在这个问题,再说,修改源代码的方法还是算了吧,免得以后版本升级带来麻烦,说白了就是一个拦截器而已,是在不行自己实现一下拦截器,或者直接重写一个拦截器,把struts自带的那个废了算了

我很赞成·····cos那个真的很不错
4 楼 kjj 2008-12-31  
struts2 的核心在于action 简化web开发,其他的像文件上传这种鸡肋的功能,我认为没必要一定要跟随他的走,也没必要一定要用commons-upload这个,cos 就不存在这个问题,再说,修改源代码的方法还是算了吧,免得以后版本升级带来麻烦,说白了就是一个拦截器而已,是在不行自己实现一下拦截器,或者直接重写一个拦截器,把struts自带的那个废了算了
3 楼 ynw520 2008-12-31  
讲得透彻!
看来,我也应该去研究一下了!
2 楼 netfork 2008-12-11  
blurm 写道
lz问个小问题,为什么我在struts2当中设置文件类型拦截根本不起作用,但是同时设置的大小限制是好使的

看看是不是在struts.xml配置文件中,大小写有误,或单词拼错了?
如果还不行,可以把配置粘上来。
1 楼 blurm 2008-12-11  
lz问个小问题,为什么我在struts2当中设置文件类型拦截根本不起作用,但是同时设置的大小限制是好使的

相关推荐

    struts2文件上传下载源代码

    1. **配置Struts2 Action**:在`struts.xml`配置文件中,你需要定义一个Action,该Action负责处理文件上传请求。Action的类需要继承自`ActionSupport`,并覆盖`execute()`方法,以便处理上传的文件。 2. **设置...

    struts2文件上传jar

    这个压缩包包含了实现Struts2文件上传所需的全部jar包,这些库文件对于理解和实现文件上传功能至关重要。 首先,我们要了解Struts2文件上传的基本流程。当用户通过表单提交包含文件输入字段的请求时,Struts2框架会...

    Struts2多个文件上传

    这两个库提供了文件上传的基础功能,使得Struts2能够处理`multipart/form-data`类型的请求,这是文件上传的标准格式。 Struts2本身并不包含一个内置的请求解析器来处理文件上传。相反,它依赖于第三方库,如Jakarta...

    Struts2之struts2文件上传详解案例struts011

    接着,我们需要在Struts配置文件(struts.xml)中配置Action,确保它能够处理文件上传的请求。配置通常包括Action的名称、对应的类以及结果页面。例如: ```xml <result name="success">/success.jsp ...

    Struts2文件上传源码

    6. **源码分析**: 要深入理解Struts2的文件上传机制,你需要查看Struts2的源码,特别是`org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest`和`org.apache.struts2.components.Form`这两个类。...

    struts2文件上传例子.rar

    总的来说,这个例子展示了如何在Struts2框架下实现文件上传功能,包括Action的编写、配置文件的设定、表单的创建以及文件处理逻辑。通过学习这个例子,开发者可以了解到Struts2处理文件上传的基本流程,为自己的项目...

    JavaEE Struts文件上传

    1. **配置Struts2 Action**:在Struts2中,我们需要创建一个Action类来处理文件上传请求。这个类通常需要实现`ServletRequestAware`接口,以便可以直接访问HttpServletRequest对象,从中获取上传文件。 2. **添加...

    struts2 实现文件批量上传

    在`struts_upload`这个项目中,可能包含了Action类、配置文件、前端HTML页面等相关代码,通过阅读这些代码可以更深入地理解Struts2如何处理文件批量上传的全过程。记得在实际操作时,确保服务器环境已经配置好相应的...

    struts2+jquery+ajax文件异步上传

    Struts2、jQuery和Ajax是Web开发中的三个关键组件,它们共同构成了文件异步上传的基础框架。这个项目是在MyEclipse环境下实现的一个简单的文件上传功能,让我们深入了解一下这些技术及其在文件上传中的应用。 首先...

    Struts2文件上传下载和表单重复提交问题

    文件上传功能在Struts2中通过`Struts2`提供的插件来实现,主要依赖于`Apache Commons FileUpload`库。首先,要在`struts.xml`配置文件中启用文件上传支持,设置`struts.multipart.parser`为`jakarta`或`native2...

    struts2_uploadify带进度条的多文件上传下载

    Struts2 Uploadify是一个在Java Web开发中常用的插件,它结合了Struts2框架和Uploadify jQuery插件,能够实现文件的多选、上传进度显示以及后台处理等功能。这个项目示例提供了一个完整的解决方案,使得用户在上传多...

    Struts2文件批量上传

    Struts2文件批量上传是Java Web开发中常见的一种功能,主要应用于网站后台处理大量用户上传的文件,如图片、文档等。Struts2是一个强大的MVC框架,它提供了丰富的功能来支持文件上传操作,包括单个文件上传和批量...

    Struts2文件上传进度条

    Struts2文件上传进度条是Web开发中一个实用的功能,它允许用户在文件上传过程中查看当前的上传进度,提供更好的用户体验。在这个项目中,我们利用Struts2框架的拦截器机制来实现这一功能。 首先,我们需要理解...

    swfuplaod+struts2实现多文件上传

    3. **创建Struts2 Action**:在Struts2框架中,创建一个处理文件上传的Action类,该类通常会包含一个`List<HttpServletFileWrapper>`类型的属性,用于接收上传的文件。 4. **编写Struts2配置**:在struts.xml配置...

    struts2文件上传下载实例

    在“struts2文件上传下载实例”中,我们将探讨如何在Struts2框架下实现文件的上传和下载功能,这对于许多Web应用程序来说是必不可少的特性。 首先,`pom.xml`文件是Maven项目对象模型的配置文件,它定义了项目的...

    能运行的ExtJs+Struts2文件上传

    Struts2则在服务器端处理文件上传请求,将接收到的文件保存到指定位置,并可能进行一些验证和处理。 描述中提到"研究了半天的ExtJs+Struts2多文件上传",暗示这个项目可能包含了一种或多文件同时上传的解决方案。在...

    Struts2.0文件上传原理

    Struts2.0文件上传原理主要涉及Web应用中处理用户上传文件的功能,这一过程涉及到多个步骤和技术,包括HTTP协议、Multipart/form-data编码、临时文件存储以及后台处理。以下是对Struts2.0文件上传原理的详细解释: ...

    struts2文件上传例子(支持大文件)

    在这个“struts2文件上传例子(支持大文件)”中,我们将探讨如何利用Struts2框架实现文件上传功能,尤其是处理大文件的能力。 在Web应用中,文件上传是一项常见的需求,例如用户可能需要上传照片、文档或视频等。...

    struts2多文件的上传

    在Struts2中,文件上传是常见的功能之一,尤其在处理用户提交的表单数据时,如上传图片、文档等。在本项目中,"struts2多文件的上传"实现了用户一次性上传多个文件的能力。 要理解这个功能,首先我们需要了解Struts...

    ext struts2 swfupload 跨域文件上传

    总结起来,"ext struts2 swfupload 跨域文件上传"是关于如何使用EXTJS构建前端界面,结合Struts2处理服务器端逻辑,利用SwfUpload的跨域能力实现文件上传的一种解决方案。这个过程中涉及的技术包括JavaScript编程、...

Global site tag (gtag.js) - Google Analytics