最近要做上傳,但是MultiPartRequestWrapper中的很多的方法都是過時的了,在晚上看看到這篇文章挺好的,放在這里,學(xué)習(xí)一下! 原文連接地址:
http://www.java3z.com/cwbwebhome/article/article2a/2136.jsp?id=463
Web瀏覽器為我們利用Web應(yīng)用程序發(fā)送文件提供了一條簡單的途徑,但是當(dāng)前版本的Java Web標(biāo)準(zhǔn)(servlets、JSP和JSF)卻無法為我們提供任何幫助。幸運的是,有一些第三方框架組件(例如Apache Commons File Upload、 Apache MyFaces和Oracle ADF Faces)實現(xiàn)了這種特性,它們都暴露了簡單的API和定制的標(biāo)記(tag)。
本文的前面一部分介紹文件上載操作是如何實現(xiàn)的,解釋了MyFaces和通用文件上載(前者在內(nèi)部使用了后者)。我們了解這些開放源代碼框架組件的內(nèi)部工作方式是非常重要的,因為只有這樣我們才能高效率地使用它們,才能根據(jù)自己的需要對它們進(jìn)行修改。本文的第二部分將介紹一個簡單的應(yīng)用程序,它讓用戶使用web瀏覽器上載文件。 |
|
基于Web的文件上載 我們把"上載"這個專業(yè)術(shù)語用得有點過濫了。Web管理員在自己的站點上發(fā)布文件的時候,它會說自己"上載"了一個文件。Web開發(fā)者在建立HTML窗體和腳本,讓普通用戶通過web瀏覽器發(fā)送一個文件的時候,他也會說自己實現(xiàn)了文件"上載"功能。
這兩種含義在某些方面是重疊的,因為web管理員可能使用基于web的界面來發(fā)布文件(頁面、圖像、腳本等等)。免費提供個人web站點的公司(例如Yahoo),實現(xiàn)基于web的文件上載功能的目的是為了讓人們上載自己的頁面。它允許任何一個擁有web瀏覽器和互聯(lián)網(wǎng)訪問能力的個人發(fā)布一個小型的web站點。但是,目前有一些更好的、可用于發(fā)布web內(nèi)容的方式,例如FTP或安全FTP。在這種情況下,你可以利用專用的應(yīng)用程序(例如FTP客戶端)代替web瀏覽器把自己的內(nèi)容上載到web服務(wù)器上。
本文從web開發(fā)者的角度來討論文件上載的問題。例如,一個基于web的郵件應(yīng)用程序(例如Yahoo郵件),就實現(xiàn)了文件上載,因為只有這樣,用戶才能夠發(fā)送那些帶有附件的消息。另一個比較好的例子是找工作的web站點,它允許用戶把自己的簡歷發(fā)送給招聘人員。本文的示例應(yīng)用程序計算上載文件的散列值。你可以在自己的應(yīng)用程序中對上載的文件進(jìn)行任意的處理,例如把它們的內(nèi)容存儲在數(shù)據(jù)庫中或把它們作為郵件附件發(fā)送。現(xiàn)在,讓我們看看如何在web應(yīng)用程序中實現(xiàn)文件上載吧。
一個HTML窗體中可以包含一個或多個<input type="file">元素,瀏覽器把它們作為輸入字段來顯示(呈現(xiàn)),在這些字段中允許用戶輸入文件路徑。在每個文件輸入字段的后面,web瀏覽器會添加一個按鈕,它會打開一個文件對話框,讓用戶選擇一個文件(代替輸入路徑):

圖1:包含文件輸入字段的web窗體
當(dāng)用戶點擊窗體的"提交(Submit)"按鈕的時候,web瀏覽器對窗體的數(shù)據(jù)進(jìn)行編碼,其中包括被選中的文件、文件名(或路徑)和窗體的其它一些參數(shù)。接著,瀏覽器把編碼后的數(shù)據(jù)發(fā)送給web服務(wù)器,web服務(wù)器把這些數(shù)據(jù)傳遞給<form>標(biāo)記的action屬性所指定的腳本來處理。如果你開發(fā)一個Java web應(yīng)用程序,那么操作腳本可能是一個servlet(小服務(wù)程序)或JSP頁面。
由于窗體數(shù)據(jù)默認(rèn)的編碼方式和默認(rèn)的GET方法都不適合文件上載,所以包含文件輸入字段的窗體必須在<form>標(biāo)記中指定multipart/form-data編碼方式和POST方法:
<form enctype="multipart/form-data" method="POST" action="...">
...
<input type="file" name="...">
...
</form>
但是事情并非這么簡單,因為實現(xiàn)servlet和JSP規(guī)范的應(yīng)用程序服務(wù)器不一定能夠處理multipart/form-data編碼方式。因此,你必須分析請求的輸入流,例如Apache通用文件上載就是一個小型的Java程序包,它讓你能夠從編碼的數(shù)據(jù)中獲取被上載文件的內(nèi)容。這個程序包的API是很靈活的,它允許你把小文件存儲在內(nèi)存中,把大文件存儲在磁盤的臨時目錄中。你可以指定一個文件大小的閥值,大于這個值的文件都會被寫到磁盤上,而不會保留在內(nèi)存中,而且你還可以規(guī)定允許的被上載文件的最大大小。
前面提到的org.apache.commons.fileupload程序包包含一個叫作DiskFileUpload的類,它的parseRequest()方法獲取HttpServletRequest參數(shù),并返回org.apache.commons.fileupload.FileItem實例列表。編碼后的窗體數(shù)據(jù)從servlet請求的getInputStream()方法所返回的數(shù)據(jù)流流中讀取。FileItem這個名字容易使人誤解,因為這個接口的實例同時表現(xiàn)了被上載的文件和正常的請求參數(shù)。
通用文件上載程序包所提供的API賦予了你訪問分解后的窗體數(shù)據(jù)的權(quán)利,但是servlet請求的getParameter()和getParameterValues()方法卻無法工作。這是一個難題,因為在輸入字段、檢查框、單選框和列表框后臺運行的JSF組件需要調(diào)用這兩個方法。我們可以利用Servlets API提供的兩個特性(過濾器和請求包裝器)來解決這個問題。下一部分描述了Apache MyFaces如何實現(xiàn)過濾器,添加了大量必要的對文件上載的支持,而不會中斷已有的JSF組件。此外,MyFaces為JavaBeans提供了API,同時還提供了一個定制的JSF組件,它表現(xiàn)為<input type="file">元素。
配置JSF和MyFaces擴(kuò)展
目前,JSF規(guī)范的主要實現(xiàn)是JSF參考實現(xiàn)(RI),與此同時,Apache提供了另外一個實現(xiàn),就是MyFaces。當(dāng)然可能存在其它一些JSF實現(xiàn),但是JSF RI和MyFaces是其中最流行的兩個。很多開發(fā)者更喜歡前者,因為Sun把它稱為"官方的"實現(xiàn),但是,MyFaces擁有一些有趣的擴(kuò)展(例如對上載文件操作的支持)。如果你愿意,可以把MyFaces擴(kuò)展與Sun的JSF RI一起使用。你只需要把myfaces-extensions.jar文件、JSF RI的JAR文件和commons-fileupload-1.0.jar文件一起放到自己的web應(yīng)用程序的WEB-INF/lib目錄中就可以了。下面是需要的JAR文件:
JSF 1.1 RI jsf-api.jarjsf-impl.jar
JSTL 1.1 RI jstl.jarstandard.jar
MyFaces擴(kuò)展 myfaces-extensions.jar
Apache Commons(供JSF和MyFaces 擴(kuò)展使用) commons-collections.jarcommons-digester.jarcommons-beanutils.jarcommons-logging.jarcommons-fileupload-1.0.jar
org.apache.myfaces.component.html.util程序包中的MultipartRequestWrapper類充當(dāng)MyFaces和通用文件上載之間的橋梁。這個類擴(kuò)展了HttpServletRequestWrapper,重載了getParameterMap()、getParameterNames()、getParameter()和getParameterValues()方法,這樣它們才能適應(yīng)multipart/form-data編碼方式。此外,MultipartRequestWrapper提供了兩個新的方法,分別是getFileItem()和getFileItems(),它允許你通過org.apache.commons.fileupload.FileItem接口來訪問被上載的文件。
當(dāng)MyFaces檢測到編碼方式的時候,org.apache.myfaces.component.html.util程序包中的ExtensionsFilter就建立MultipartRequestWrapper實例。因此,你不需要關(guān)心窗體數(shù)據(jù)的分解,但是如果你希望改變被上載文件的處理方式,那么了解它是如何實現(xiàn)的就很有用處了。在典型的應(yīng)用程序中,你必須在自己的web應(yīng)用程序的web.xml描述信息中配置ExtensionsFilter,這樣它才能在JSF的FacesServlet前面截取HTTP請求:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<servlet>
<servlet-name>FacesServlet</servlet-name>
<servlet-class>
javax.faces.webapp.FacesServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<filter>
<filter-name>ExtensionsFilter</filter-name>
<filter-class>
org.apache.myfaces.component.html.util.ExtensionsFilter
</filter-class>
<init-param>
<param-name>uploadMaxFileSize</param-name>
<param-value>10m</param-value>
</init-param>
<init-param>
<param-name>uploadThresholdSize</param-name>
<param-value>100k</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ExtensionsFilter</filter-name>
<servlet-name>FacesServlet</servlet-name>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
前面示例中的兩個過濾器(filter)參數(shù)告訴MyFaces把小于100K的文件放入內(nèi)存中,并且忽略(即不處理)占用10MB以上磁盤空間的文件。大小介于uploadThresholdSize和uploadMaxFileSize之間的文件將作為臨時文件存儲在磁盤上。如果你試圖載入一個太大的文件,MyFaces當(dāng)前版本將忽略所有的窗體數(shù)據(jù),就像用戶提交了一個空表單一樣。如果你希望給上載文件失敗的用戶一些提示信息,就需要更改MyFaces的MultipartRequestWrapper類,找到捕捉SizeLimitExceededException異常的位置,并使用FacesContext.getCurrentInstance().addMessage()來警告用戶。
前面提到,MyFaces擴(kuò)展包含了文件上載組件,我們可以在JSF頁面中使用它。下一部分將介紹如何實現(xiàn)這樣的操作。 使用MyFaces的文件上載組件。
為了在web頁面中使用JSF和MyFaces,你必須在<%@taglib%>指令中聲明它們的標(biāo)記庫:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://myfaces.apache.org/extensions" prefix="x"%>
JSF的<h:form>標(biāo)記沒有method屬性,這是因為它只支持POST方法,但它擁有enctype屬性,如果你希望上載文件,就必須使用該屬性,把窗體數(shù)據(jù)設(shè)置為多部分(multipart)編碼類型:
<f:view>
<h:form id="MyForm" enctype="multipart/form-data" >
...
<x:inputFileUpload id="myFileId"
value="#{myBean.myFile}"
storage="file"
required="true"/>
...
</h:form>
</f:view>
MyFaces的<x:inputFileUpload>標(biāo)記使你能夠定義UI組件的屬性,它的呈現(xiàn)部件(renderer)生成<input type="file">元素。org.apache.myfaces.custom.fileupload程序包包含了UI組件類(HtmlInputFileUpload)、呈現(xiàn)部件(HtmlFileUploadRenderer)、定制的標(biāo)記處理程序(HtmlInputFileUploadTag)、UploadedFile接口和其它一些輔助類。HtmlInputFileUpload類擴(kuò)展了JSF標(biāo)準(zhǔn)的HtmlInputText組件,重載了它的一些方法。HtmlFileUploadRenderer負(fù)責(zé)生成HTML標(biāo)記,從MultipartRequestWrapper中獲取FileItem。
MyFaces并沒有讓你直接訪問通用文件上載所建立的FileItem實例,它提供了自己的UploadedFile接口來獲取被上載的文件的內(nèi)容、內(nèi)容類型、文件名和文件大小。JSF窗體的后臺bean必須擁有一個UploadedFile類型的屬性。前面的例子用JSF表達(dá)式(#{myBean.myFile})把UI組件的值綁定到了這樣一個bean上。JSF框架組件將獲取HtmlInputFileUpload組件的值,它是一個UploadedFile實例,并且會設(shè)置后臺bean的myFile屬性:
import org.apache.myfaces.custom.fileupload.UploadedFile;
...
public class MyBean {
private UploadedFile myFile;
public UploadedFile getMyFile() {
return myFile;
}
public void setMyFile(UploadedFile myFile) {
this.myFile = myFile;
}
...
}
MyFaces擁有UploadedFile接口的兩種實現(xiàn)方式:UploadedFileDefaultMemoryImpl和UploadedFileDefaultFileImpl。當(dāng)<x:inputFileUpload>標(biāo)記沒有帶storage屬性,或者這個屬性的值是memory的時候,就使用前者。當(dāng)storage屬性的值是file的時候使用后者。
UploadedFileDefaultMemoryImpl類從FileItem實例中獲取被上載文件的內(nèi)容、文件名、文件大小和內(nèi)容類型,并把所有這些信息存儲在私有字段中。因此,即使通用文件上載把這個文件保存在磁盤上,UploadedFile的這種實現(xiàn)也會把被上載文件的內(nèi)容保存在內(nèi)存中,浪費了資源。
UploadedFileDefaultFileImpl類使用一個過渡字段來保存FileItem實例的指針,僅僅在調(diào)用getInputStream()方法的時候,才使用它來獲取被上載文件的內(nèi)容。這種實現(xiàn)節(jié)約了內(nèi)存空間,但是,如果它是序列化的(serialize),那么在還原序列化(deserialization)之后,你就無法獲取文件內(nèi)容了。因此,文件上載窗體的后臺bean不能保存在session作用域(scope)中,因為當(dāng)應(yīng)用程序重新啟動或服務(wù)器關(guān)閉的時候,應(yīng)用程序服務(wù)器會序列化對話(session)bean。
如果你希望得到了一個工作正常的、效率很高的解決方案,那么就把后臺bean保存在request作用域中,并在<x:inputFileUpload>中指定storage="file"以節(jié)約內(nèi)存資源。你也可以通過添加writeObject()方法(它用于序列化被上載文件的內(nèi)容)來解決UploadedFileDefaultFileImpl類的序列化問題。為了保證UploadedFile的這種實現(xiàn)的高效性,相應(yīng)的readObject()方法應(yīng)該重新建立一個臨時文件,而不是從內(nèi)存中讀取內(nèi)容。
使用MyFaces的時候還需要注意一個問題:UploadedFile接口并沒有定義一個用于刪除通用文件上載在磁盤上所建立的臨時文件的方法。當(dāng)FileItem實例作為垃圾被清理的時候,就應(yīng)該刪掉這些文件。通用文件上載的DefaultFileItem類擁有一個finalize()方法,當(dāng)管理臨時文件的對象被從內(nèi)存中刪除的時候,它可以刪除它們所管理的臨時文件。如果你的應(yīng)用程序正在上載大型文件的時候,你可能希望在它們被處理后立即刪除,而不用等待垃圾清除過程。為了實現(xiàn)這樣的功能,你必須添加一個getFileItem()方法(在UploadedFileDefaultFileImpl中),它應(yīng)該返回FileItem實例,而該實例擁有delete()方法。
示例應(yīng)用程序
前面的部分介紹了是MyFaces如何在通用文件上載的幫助下支持文件上載的。現(xiàn)在我們來看一個實際的利用了這種特性的應(yīng)用程序。JSF窗體(MyForm.jsp)讓用戶選擇一個文件和一個報文摘要算法,后臺bean( MyBean.java)使用這個算法計算出一個散列值,顯示在另外一個web頁面(MyResult.jsp)中。這些頁面和后臺bean通過一個JSF配置文件(faces-config.xml)結(jié)合在一起。
MyForm.jsp頁面
這個JSF窗體使用了MyFaces的<x:inputFileUpload>標(biāo)記,同時還使用了其它一些標(biāo)準(zhǔn)的JSF標(biāo)記來呈現(xiàn)標(biāo)簽、消息、包含報文摘要算法的下拉列表、使用JSF表達(dá)式(#{myBean.processMyFile})指定了處理被上載文件的操作方法的命令按鈕:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://myfaces.apache.org/extensions" prefix="x"%>
<f:view>
<h:form id="MyForm" enctype="multipart/form-data" >
<h:messages globalOnly="true" styleClass="message"/>
<h:panelGrid columns="3" border="0" cellspacing="5">
<h:outputLabel for="myFileId" value="File: "/>
<x:inputFileUpload id="myFileId" value="#{myBean.myFile}" storage="file" required="true"/>
<h:message for="myFileId"/>
<h:outputLabel for="myParamId" value="Param: "/>
<h:selectOneMenu id="myParamId" value="#{myBean.myParam}" required="true">
<f:selectItem itemLabel="" itemValue=""/>
<f:selectItem itemLabel="MD5" itemValue="MD5"/>
<f:selectItem itemLabel="SHA-1" itemValue="SHA-1"/>
<f:selectItem itemLabel="SHA-256" itemValue="SHA-256"/>
<f:selectItem itemLabel="SHA-384" itemValue="SHA-384"/>
<f:selectItem itemLabel="SHA-512" itemValue="SHA-512"/>
</h:selectOneMenu>
<h:message for="myParamId"/>
<h:outputText value=" "/>
<h:commandButton value="Submit"
action="#{myBean.processMyFile}"/>
<h:outputText value=" "/>
</h:panelGrid>
</h:form>
</f:view>
MyBean類
后臺bean擁有三個屬性:myFile、myParam和myResult。前面已經(jīng)解釋了myFile屬性的角色。它讓你獲取被上載文件的內(nèi)容和文件名、文件大小、內(nèi)容類型。myParam屬性的值是報文摘要算法。myResult屬性將保存processMyFile()方法執(zhí)行之后的散列值。MyBean類為自己的所有屬性提供了get和set方法:
package com.devsphere.articles.jsfupload;
import org.apache.myfaces.custom.fileupload.UploadedFile;
...
public class MyBean {
private UploadedFile myFile;
private String myParam;
private String myResult;
public UploadedFile getMyFile() {
return myFile;
}
public void setMyFile(UploadedFile myFile) {
this.myFile = myFile;
}
public String getMyParam() {
return myParam;
}
public void setMyParam(String myParam) {
this.myParam = myParam;
}
public String getMyResult() {
return myResult;
}
public void setMyResult(String myResult) {
this.myResult = myResult;
}
...
}
processMyFile()通過一個輸入流獲取被上載文件的內(nèi)容,這個輸入流是通過myFile.getInputStream()獲得的。散列值是在java.security.MessageDigest的幫助下計算出來的,然后把它轉(zhuǎn)換為字符串,這樣就可以被myResult屬性訪問了:
package com.devsphere.articles.jsfupload;
...
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.*;
public class MyBean {
...
public String processMyFile() {
try {
MessageDigest md= MessageDigest.getInstance(myParam);
InputStream in = new BufferedInputStream(
myFile.getInputStream());
try {
byte[] buffer = new byte[64 * 1024];
int count;
while ((count = in.read(buffer)) > 0)
md.update(buffer, 0, count);
} finally {
in.close();
}
byte hash[] = md.digest();
StringBuffer buf = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
int b = hash[i] & 0xFF;
int c = (b >> 4) & 0xF;
c = c < 10 ? '0' + c : 'A' + c - 10;
buf.append((char) c);
c = b & 0xF;
c = c < 10 ? '0' + c : 'A' + c - 10;
buf.append((char) c);
}
myResult = buf.toString();
return "OK";
} catch (Exception x) {
FacesMessage message = new FacesMessage(
FacesMessage.SEVERITY_FATAL,
x.getClass().getName(), x.getMessage());
FacesContext.getCurrentInstance().addMessage(null, message);
return null;
}
}
}
faces-config.xml文件
JSF配置文件把后臺bean定義在request作用域中,并且指定了一個導(dǎo)航規(guī)則:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
"http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>
<managed-bean>
<managed-bean-name>myBean</managed-bean-name>
<managed-bean-class>
com.devsphere.articles.jsfupload.MyBean
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<navigation-rule>
<from-view-id>/MyForm.jsp</from-view-id>
<navigation-case>
<from-outcome>OK</from-outcome>
<to-view-id>/MyResult.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
MyResult.jsp頁面
這個web頁面顯示了被上載文件的一些信息:
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<f:view>
<h:panelGrid columns="2" border="0" cellspacing="5">
<h:outputText value="FileName:"/>
<h:outputText value="#{myBean.myFile.name}"/>
<h:outputText value="FileSize:"/>
<h:outputText value="#{myBean.myFile.size}"/>
<h:outputText value="Param:"/>
<h:outputText value="#{myBean.myParam}"/>
<h:outputText value="Result:"/>
<h:outputText value="#{myBean.myResult}"/>
</h:panelGrid>
</f:view>
被顯示的文件名可能帶有客戶端文件系統(tǒng)的完整路徑,如下所示:

圖2:結(jié)果頁面產(chǎn)生的輸出信息
總結(jié) 在很多情況下,用戶需要通過瀏覽器上載文件,但是在服務(wù)器端卻沒有處理這些文件的理想方法。把文件的內(nèi)容保存在內(nèi)存中對于小型文件來說是可以接受的,但是把被上載的文件存儲在臨時文件中使這種情況變復(fù)雜了。MyFaces讓你能夠選擇一個適合應(yīng)用程序需求的解決方案,但是這個框架組件也有一些小問題。當(dāng)你不再需要臨時文件的時候,它無法讓你刪除它們;文件名有時候帶有文件路徑;當(dāng)用戶上載過大的文件的時候也沒有警告信息。這些缺陷是可以修補的,因為我們可以使用源代碼,而且本文就介紹了MyFaces代碼可以改進(jìn)的一些的地方。無論如何,對于多數(shù)應(yīng)用程序來說,MyFaces不做修改就合乎需求。本文的例子是使用JSF 1.1.01、MyFaces 1.0.9和通用文件上載1.0環(huán)境測試的。