Struts2中,在Dispatcher.java里,把一个multipart/form-data类型的 HttpServletRequest对象包装成JakartaMultiPartRequest的过程中,已经读完了HttpServletRequest对象输入流数据,基于此对象输入流的特点--不可再读取第二次,因此Action中已经不可能使用fastupload API解析multipart/form-data请求了。
纵览struts2框架里Model Bean的自动装配过程,发现拦截器其实共享了一个ActionContext的“值栈”,“值栈”里存储了必要的数据,以便让拦截器、ActionProxy、ActionInvocation对象和HttpServletRequest隔离,最后在ParametersInterceptor.java中,从“值栈”取得相关的数据,装配到Model Bean中,因此,任何数据在装配之前,都需要在合适的时机放入“值栈”之中。Struts2虽然在包装HttpServletRequest阶段已经完成了文件解析的工作,但是直到FileUploadInterceptor被调用时,把解析的文件放入了“值栈”。
fastupload基于这个约定,编写了struts2文件上传的“插件”,之所以打上引号,其功能实质上算不了一个正真的插件,只是实现了MultiPartRequest这个接口过程中调用了fastupload相关的API。具体的用法请参考《Fastupload 0.4.7发布,支持struts2》一文。这种解决办法只能说是凑合,好处是---- 不需要修改struts2的源码。
但是struts2在使用apache commons fileupload的过程中,写的MultiPartRequestWrapper.java的构造函数是多了一个saveDir,用来指定apache commons fileupload解析时产生临时文件的存储目录,并且在ActionProxy对象执行结束后,要清除这些临时文件,不止于此,在Action中,用来操作上传的文件的类用了java.io.File,也就是一定要和文件系统相关联。不能不说, apache commons fileupload使用临时文件这种方式,拖累了struts2,spring mvc以及其他一些mvc框架。
假如解析multipart/form-data请求后,这些解析的数据都作为内存中的一个对象存在,在需要的时候才保存到文件系统中,mvc框架处理起来要简单的多,是不是这样?正因如此,要发挥fastupload的威力,一定要用内存的解析方式!
首先看一下使用内存解析方式,Action类有何变化!
public class StrutUploadAction1 extends ActionSupport { private MultiPartFile photo; private String description; @Override public String execute() throws Exception { System.out.println(photo); //此处省略业务逻辑代码 return super.execute(); } //省略getters & setters }
这个Actin中直接使用MultPartFile,解析后的上传文件的数据也保存在这个对象之中,也就是,上传文件的数据已经在内存之中, 用户很方便的把这些数据保存到文件系统还是拿到数据,处理完后直接丢弃。这种处理方式,给予用户极大的便利。
但是,struts2对于multipart/form-data请求的解析过程中,借用了临时文件。对用户而言, 不仅 处理上传文件不方便,而且还造成WEB APP性能低下,还要在Action被代理执行后清除这些临时文件。
struts2沿用这种处理方式已久, multipart/form-data请求的处理,处处体现了这个特征。fastupload针对这种现象,作出一个小规模的源码侵入。实现的步骤如下:
1, 修改struts配置,使用FastUploadInterceptor,配置其参数,注意这个fileUploadAction配置 。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="fastupload" class="org.apache.struts2.dispatcher.multipart.FastUploadMultiPartRequest" scope="default" /> <constant name="struts.multipart.handler" value="fastupload" /> <constant name="fastupload.parse.type" value="auto"/> <constant name="fastupload.allowed.types" value="image/jpeg, image/png, image/gif"/> <constant name="fastupload.allowed.extensions" value=".jpeg, .png, .gif"/> <package name="example" namespace="/example" extends="struts-default"> <interceptors> <interceptor name="fastuploadInterceptor" class="org.apache.struts2.dispatcher.multipart.FastUploadInterceptor" /> </interceptors> <action name="fileUploadAction" class="example.FileUpload"> <interceptor-ref name="fastuploadInterceptor"/> <interceptor-ref name="basicStack"/> <result>/example/upload.jsp</result> </action> </package> </struts>
2, 修改Dispatcher.warpRequest(HttpServletRequest, ServletContext)函数,用FastUploadMultiPartRequestWrapper替换struts2原有的warpper。
//TODO un-comment the next line for original code // request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext)); request = new FastUploadMultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));
3,既然使用了内存解析方式,不需要临时文件,也不用清理它们,注释掉Dispathcer.cleanUpRequest(HttpServletRequest)函数中所有的代码
/** * Removes all the files created by MultiPartRequestWrapper. * * @param request the HttpServletRequest object. * @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper * @throws java.io.IOException on any error. */ public void cleanUpRequest(HttpServletRequest request) throws IOException { // if (!(request instanceof MultiPartRequestWrapper)) { // return; // } // // MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request; // // Enumeration fileParameterNames = multiWrapper.getFileParameterNames(); // while (fileParameterNames != null && fileParameterNames.hasMoreElements()) { // String inputValue = (String) fileParameterNames.nextElement(); // File[] files = multiWrapper.getFiles(inputValue); // // for (File currentFile : files) { // if (LOG.isInfoEnabled()) { // String msg = LocalizedTextUtil.findText(this.getClass(), "struts.messages.removing.file", Locale.ENGLISH, "no.message.found", new Object[]{inputValue, currentFile}); // LOG.info(msg); // } // // if ((currentFile != null) && currentFile.isFile()) { // if (!currentFile.delete()) { // if (LOG.isWarnEnabled()) { // LOG.warn("Resource Leaking: Could not remove uploaded file '" + currentFile.getCanonicalPath() + "'."); // } // } // } // } // } }
4, 使用fastupload预先的过滤机制(可选),在FastUploadMultiPartRequest类中,找到下面的代码,把@Inject那两行反注释掉,
/** * not exposed yet * * @param allowedTypes * the allowedTypes to set */ // @Inject("fastupload.allowed.types") public void setAllowedTypes(String allowedTypes) { this.allowedTypes = allowedTypes; } /** * not exposed yet * * @param allowedExtensions * the allowedExtensions to set */ // @Inject("fastupload.allowed.extensions") public void setAllowedExtensions(String allowedExtensions) { this.allowedExtensions = allowedExtensions; }
向struts配置文件中增加两个全局配置常量,这用过滤设置目前还不支持通配符,详细的用法请参考fastupload项目中的 AcceptableFileFactory.java
<constant name="fastupload.allowed.types" value="image/jpeg, image/png, image/gif"/> <constant name="fastupload.allowed.extensions" value=".jpeg, .png, .gif"/>
5, 运行并测试那个Action -:)
采用侵入源码的方式,也是逼迫不已呀。就目前来说,也达到让fastupload支持struts2的目标,但是,如果能让struts2的发行包中,直接集成了fastupload组建高效解析API,才能让struts2框架使用者更容易的使用,需要与struts的开发小组讨论了... ... 这个阶段刚 结束,一个新的阶段接着开始。
fastupload支持struts2框架的开发过程比较仓促,加之本人水平有限,可能会有一些疏漏及不正确的地方,如果您发现了,请告诉我,我及时改正,同时也欢迎网友对fastupload提出建设性意见。
[原创文章,如转载,请注明出去!@仪山湖]