专题论坛
基于装饰器与模式对文件上传编程的改进设计
崔天鑫
(中国兵器工业信息中心,北京100089)
摘要:当前的Servlet规范对文件上传的支持非常有限。Servlet文件上传编程普遍面临以下问题:规范定义的束缚、处理普通表单数据与文件数据混杂带来的代码强耦合和灵活度缺失、缺乏上传进度实时显示功能等。针对这些问题,基于经典的Decorator与Observer设计模式,通过综合应用过滤器、包装器、,提出了一种处理普通表单数据代码与文件上传代码解耦、避免MultipartConfig注解、实现对文件上传进度实时监听的Servlet文件上传编程的改进设计,并给出了不依赖于任何开发框架的示范性的实现。关键词:Decorator模式;Observer模式;Servlet文件;文件上传;显示进度;改进设计
1概述
Servlet3.0规范发布之前,Servlet本身缺乏对文件上传的直接支持,文件上传编程让开发者倍感麻烦遥Servlet3.0规范起,提供了对文件上传的支持,使用起来也非常简便。HttpServletRequest定义了getPart()和getParts()两个方法来解析上传的文件。在编程时,需要为处理文件上传请求的Servlet配置M
ultipartConfig注解,以表示该Servlet处理的MIME类型为multipart/ form-data0在此前处理含文件上传的提交请求时,HttpServletRequest中用于获取普通表单数据的getParam-eter()和getParameterValues()方法都是取不到值的,现在可以直接获取。Servlet3.0规范提供对文件上传的直接支持,可以说是非常激动人心的。
针对Servlet文件上传编程目前存在的问题,给出一种改进设计。需要说明的是,在实际编程中,程序设计者往往依赖开发框架,在享受开发框架便利的同时,也不可避免地要受到框架设计的束缚。基于经典设计模式给出的改进设计,不依赖于任何开发框架,在一定程度上甚至有助于开发框架的设计和改进。因此在讨论相关问题以及应用详解给出的代码示例时,仅给出基于Servlet规范的基本实现0
2存在的问题
2.1规范定义的束缚
Servlet规范中要求处理文件上传请求的Servlet必须配置MultipartConfig注解,否则处理文件上传时就会出错。在实际的开发中,是否处理上传文件可能随实际业务不断变化,不可能对每个Servlet都配置注解;且如果Servlet配置了MultipartConfig注解,那么处理不含有文件数据的请求时,会影响解析效率。
2.2处理普通表单数据与文件数据混杂带来的代码强耦合和灵活度缺失
实际应用中,普通的表单数据和文件数据往往是混杂在一起的。无论使用第三方组件或是Servlet规范的原生支持,都要面对两者混杂带来的代码耦合问题。可以基于客户端技术通过两次提交(一次提交普通表单数 据,一次专门上传文件,可同时发送请求,也可基于XMLHttpRequest的回调函数实现一次点击自动完成两次提交)来实现服务端代码的解耦,但这种设计明显增加了客户端编程的复杂度。此外要保证提交事件的原子性,需要在服务端做额外的工作。
2.3缺乏上传进度实时显示功能
在客户端,使用Ajax2.0上传文件,可使用XML-HttpRequest.upload对象来监听上传进度。但在服务端,最新的Servlet4.0规范对文件上传的支持仍很有限,缺乏对文件上传进度监听的功能,这使得基于服务端的实时显示文件上传进度的编程非常复杂0
3设计目标
(1)实现普通表单数据和文件数据一次提交,无需发送多次请求。
(2)实现普通Servlet处理含有文件上传的普通表单数据,避免硬性注解。
(3)实现处理普通表单数据代码和文件上传代码的解耦0
作者简介:崔天鑫(1982-),男,硕士,高级工程师,研究方向:网络分布计算、软件开发技术
。
SreeaeBBHI*>SBSI3EIEIEIEIffiEBBI!IBBBeaeBI3BigBlslBBai3IIBBEISBHI3glBBIIIIIIBSSI3aBIIIglEISa3BEIQISB>E 实用第一f智慧密集
(4)实现对文件上传进度的实时监听。
4技术准备
4.1Decrorator模式
Decrorator模式,即装饰器模式,可以在不改变现有对象结构的同时,动态地给该对象添加一些额外的功能。就增加功能而言,装饰器模式比生成子类更灵活。Servlet规范中,javax.servlet.http.HttpServletRequestWrap-per类提供HttpServletRequest接口的便捷实现,实现了Decorator模式,可以通过编写子类实现对请求的包装遥在实际编程中,HttpServletRequestWrapper往往与Servlet 过滤器配合使用。
4.2Observer模式
Observer模式,即观察者模式,用于定义对象之间一对多的依赖关系,当一个对象的状态变化时,其他依赖它的对象都得到通知。观察者模式可以有效降低对象间的耦合度。作为Servlet规范中定义的特殊类,Servlet 是Observer模式的一个经典实现,用于监听Web应用程序中域对象的创建、销毁事件以及属性发生修改的事件0灵活运用Servlet,可以有效实现代码解耦o在commons-fileupload组件中,通过实现org.apachemons.fileupload.ProgressListener接口可以监听文件上传的实时进度0
4.3上传组件选择
对普遍使用的Servlet容器Tomcat的源代码进行分析,可以发现负责文件上传的at.util.http. fileupload包的代码是完全从commons-fileupload和com-mons-io复制而来,仅仅进行了重命名。由于Servlet规范中没有相应规定,因此基于Tomcat的Java Web应用也无法调用可以说唾手可得的文件上传进度监听功能。经慎重选择,为了能够实现对文件上传进度的监听,设计采用Commons-Fileupload组件遥
5设计详解
5.1设计思路
(1)使用Servlet过滤器,根据客户端提交的请求是否包含文件上传内容进行调度。
(2)通过HttpServletRequestWrapper对文件上传请求进行包装0
(3)实现ProgressListener 接口,上传文件时,设置进度并将实时进度回传给客户端。
(4)实现ServletRequestAttributeListener接口,监听普通表单数据保存完成事件并处理文件0
5.2客户端示例
客户端技术发展日新月异,基于传统fow提交的方式之外,还有Ajax、Fetch等多种表单数据提交和文件上传的方式0这里仅给出一个最简单的客户端示例,其他方式可参照实现0
<form method="post"enctype="multipart/form-data"action="/formDataSave.do">
<br>文字内容:<input type="text"name="text">
<br>文件内容:<input type="file"name="fileInput"/> <br><input type="submit"value="提交数据">
</form>
上述表单同时包含文本录入、文件上传框,方便代码来演示普通表单数据代码和文件上传代码的解耦。如果表单无需上传文件,应当将enctype设置为x-www-form-urlencoded0
5.3过滤器的设计与部署
通过Servlet过滤器实现对所有表单请求的过滤遥部署配置为:
<filter>
<filter-name>FileUploadFilter</filter-name>< filter-class>upload.FileUploadFilter</filter-class> </filter>
<filter-mapping>
<filter-name>FileUploadFilter</filter-name><url-pattern>*.do</url-pattern>
</filter-mapping>
具体代码实现为:
public class FileUploadFilter extends HttpServlet implements Filter{
public void doFilter(ServletRequest req,ServletRe-sponse res,FilterChain filterChain)throws Exception{ HttpServletRequest request=(HttpServletRequest)req;
ContentType().startsWith("multi-part/form-data")){
filterChain.doFilter(new FileRequestWrapper (request),res);
}else{
filterChain.doFilter(req,res);
}}}
这里要注意到过滤器根据请求的contentType决定是否对HttpServletRequest包装:只有
multipart/form-da-
ta才使用FileRequestWrapper进行包装;普通的表单提交无需做任何操作。
5.4Decorator模式的应用:HttpRequestWrapper
对于包含有文件上传内容的请求,建立HttpRequestWrapper的子类FileRequestWrapper袁代码如下:
public class FileRequestWrapper extends HttpServletRequestWrapper{
private Map params;
FileRequestWrapper(HttpServletRequest request){
super(request);
this.params=new HashMap();
try{
DiskFileltemFactory factory=new Disk-FileItemFactory();
〃设置上传文件的临时存放路径
factory.setRepository(new File("d: WRepositoryPath"));
ServletFileUpload upload=new Servlet-FileUpload(factory);
〃设置文件上传进度
upload.setProgressListener(new FileU-ploadProgressListener(request));
//itemListAll是所有提交的字段列表
List<FileItem>itemListAll=upload. parseRequest(request);
//存放上传文件的字段列表
ArrayList<FileItem>fileltemList=new ArrayList<FileItem>();
for(FileItem item:itemListAll){
if(item.isFormField()){
this.Field-Name(),StringO);
}else Size()>0){
fileItemList.add(item);
}
}
//将上传文件列表信息设置到request属性request.setAttribute("uploadFileList",fileltemList);
}catch(Exception e){
e.printStackTraceO;
}}
private void setParameter(String name,String value){
String[]mValue=(String[])(name);
if(mValue==null)mValue=new String[0]; String[]newValue=new String[mValue」ength+1];
System.arraycopy(mValue,0,newValue,0, mValue.length);
newValue[mValue」ength]=value;
this.params.put(name,newValue);
}
public Map getParameterMap(){return this. params;}
public Enumeration getParameterNames(){return new Vector(params.keySet()).elements();}
public String[]getParameterValues(String name){return new String[]{Parameter(name)};} public String getParameter(String name){ String[]mValue=(String[])(name);
return(mValue!=null&&mValue.length> 0)?mValue[0]:null;
}}
FileRequestWrapper的构造方法完成这些功能:(1)将普通表单数据和文件上传数据进行分拣并将文件上传信息设置到request的属性uploadFileList里曰(2)设置了文件上传进度。此外,实现了getPar
ame-ter、getParameterValues、getParameterNames、getParam-eterMap等方法,方便Servlet获取普通表单数据遥
5.5Observer模式的应用:ServletRequestAttributeListener
建立FileSaveAttributeListener类实现ServletReques-tAttributeListener接口,用于监听普通表单数据保存完毕的事件。具体代码如下:
public class FileSaveAttributeListener implements ServletRequestAttributeListener{
public void attributeAdded(ServletRequestAttributeEvent event){
if(!Name().equals("FileSaveCon-fig"))return;
HttpServletRequest request=(HttpServleServletRequest();
ArrayList<FileItem>fileltemList=(ArrayList< FileItem>)Attribute("uploadFileList");
if(fileItemList.isEmpty())return;
//FileItem.write()或delete()方法
}}
当普通表单数据保存完毕后,将上传文件保存路径 等信息设置到request的FileSaveConfig属性中,FileSaveAttributeListener即可监听到增加属性的事件,通过
servlet和tomcat的关系SreeaeBBHI*>SBSI3EIEIEIEIffiEBBI!IBBBeaeBI3BigBlslBBai3IIBBEISBHI3glBBIIIIIIBSSI3aBIIIglEISa3BEIQISB>E 实用第一f智慧密集
获取已经设置到request的属性uploadFileList值,可以将文件从临时目录保存到真正的路径(或上传到数据库)中或者删除(如果拒绝保存)。
处理普通表单的Servlet无需配置MultipartConfig注解,代码示例如下:
pu blic class FormDataSaveServlet extends HttpServlet{
protected void doPost(HttpServletRequest request,HttpServletResponse response){
〃获取普通表单数据
String Parameter("text");
〃…这里可以处理普通表单数据
request.setAttribute("FileSaveConfig",this. getFileSaveConfig(request));
}}
代码演示了通过getParameter方法获取普通表单数据(在实际开发中,当然要根据业务逻辑进行相关处理)。此外getFileSaveConfig用来根据普通表单数据的处理结果动态生成上传文件的保存路径等信息(或数据库记录信息),提供给FileSaveAttributeListener进行文件的处理(保存或删除)。
5.6Observer模式的应用:FileUploadProgressListener
建立FileUploadProgressListener类实现ProgressLis-tener接口进行上传进度监听,代码示例如下。
public clas s FileUploadProgressListener implements ProgressListener{
private DecimalFormat df=new DecimalFor-mat("#00");
private HttpServletRequest request;
private String currentProgress="";
FileUploadProgressListener(HttpServletRequest request){quest=request;}
public void update(long bytesRead,long bytesTotal,int items){
if(bytesTotal==0||bytesRead==0|| items<1)return;
String percent=items+":"+df.format ((double)bytesRead*100/(double)bytesTotal);
if(!percent.equals(this.currentProgress)){
this.currentProgress=percent;
}}}
构造方法传入的HttpServletRequest参数,用来获取session对象;update方法将实时上传进度百分比设置到session的FILE_UPLOAD_PERCENTAGE属性中。为避免进度被频繁调用,可以适当减少的活动频率,以上代码给了一个简易示例。
5.7实现文件上传进度实时显示
文件上传进度百分比已经设置在session中,可以通过Ajax轮询特定的Servlet来获取进度信息并实时改变客户端的显示;也可以通过事先设置在session中的WebSocket对象,使用基于服务端的“推”技术将进度信息“推”给客户端,客户端WebSocket的onmessage 函数用来显示实时进度。相关代码从略。
6结语
文件上传是各类基于Web的应用系统开发时无法绕行的问题。客户端相关技术飞速发展的同时,服务端的Servlet规范的支持力度仍然相当有限。受限于规范的约束,Servlet文件上传编程向来繁琐。给出一种改进设计,即基于经典设计模式的Decrorator与Observer模式,综合应用Servlet规范的过滤器、包装器和,完成一种代码解耦且不依赖于硬性注解的Servlet文件上传编程的新设计,并给出了示范性的实现。
参考文献
[1] E.Nebel,L.Masinter.Form-based File Upload in HTML
[S/OL].1995.
[2]Erich Gamma,Richard Helm,Ralph Johnson,John
Vlissides.Design Patterns:Elements of Reusable Ob ject-Oriented Software[M].Addison Wesley Longman, Inc.1995.
[3]William Crawford,Jonathan Kaplan.J2EE Design Pat
terns[M].O'Reilly&Associates,Inc.2003.
[4]Oracle America,Inc.Java Servlet Specification Version
4.0[S/OL].2017.
[5]The Apache Software Foundation.Apache Commons Fi
leUpload1.4API[EB/OL].2019.
[6]The Apache Software Foundation.Apache Tomcat9Doc
umentation[EB/OL].
2020.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论