下載本文源代碼 引言 文件的上傳和下載在
J2EE編程已經(jīng)是一個(gè)非常古老的話(huà)題了,也許您馬上就能掰著(zhù)指頭數出好幾個(gè)著(zhù)名的大件:如SmartUpload、
Apache的FileUpload。但如果您的項目是構建在Struts+Spring+
Hibernate(以下稱(chēng)SSH)框架上的,這些大件就顯得笨重而滄桑了,SSH提供了一個(gè)簡(jiǎn)捷方便的文件上傳下載的方案,我們只需要通過(guò)一些配置并輔以少量的代碼就可以完好解決這個(gè)問(wèn)題了。
本文將圍繞SSH文件上傳下載的主題,向您詳細講述如何開(kāi)發(fā)基于SSH的Web程序。SSH各框架的均為當前最新版本:
·Struts 1.2
·Spring 1.2.5
·Hibernate 3.0
本文選用的數據庫為
Oracle 9i,當然你可以在不改動(dòng)代碼的情況下,通過(guò)配置文件的調整將其移植到任何具有Blob字段類(lèi)型的數據庫上,如
MySQL,SQLServer等。
總體實(shí)現 上傳文件保存到T_FILE表中,T_FILE表結構如下:
其中:
·FILE_ID:文件ID,32個(gè)字符,用Hibernate的uuid.hex算法生成。
·FILE_NAME:文件名。
·FILE_CONTENT:文件內容,對應Oracle的Blob類(lèi)型。
·REMARK:文件備注。
文件數據存儲在Blob類(lèi)型的FILE_CONTENT表字段上,在Spring中采用OracleLobHandler來(lái)處理Lob字段(包括Clob和Blob),由于在程序中不需要引用到oracle數據驅動(dòng)程序的具體類(lèi)且屏蔽了不同數據庫處理Lob字段方法上的差別,從而撤除程序在多數據庫移植上的樊籬。
1.首先數據表中的Blob字段在Java領(lǐng)域對象中聲明為byte[]類(lèi)型,而非java.sql.Blob類(lèi)型。
2.數據表Blob字段在Hibernate持久化映射文件中的type為org.springframework.orm.hibernate3.support.BlobByteArrayType,即Spring所提供的用戶(hù)自定義的類(lèi)型,而非java.sql.Blob。
3.在Spring中使用org.springframework.jdbc.support.lob.OracleLobHandler處理Oracle數據庫的Blob類(lèi)型字段。
通過(guò)這樣的設置和配置,我們就可以象持久化表的一般字段類(lèi)型一樣處理Blob字段了。
以上是Spring+Hibernate將文件二進(jìn)制數據持久化到數據庫的解決方案,而Struts通過(guò)將表單中file類(lèi)型的組件映射為ActionForm中類(lèi)型為org.apache.struts.upload. FormFile的屬性來(lái)獲取表單提交的文件數據。
綜上所述,我們可以通過(guò)圖 2,描繪出SSH處理文件上傳的方案:
文件上傳的頁(yè)面如圖 3所示:
文件下載的頁(yè)面如圖 4所示:
該工程的資源結構如圖 5所示:
工程的類(lèi)按SSH的層次結構劃分為數據持久層、業(yè)務(wù)層和Web層;WEB-INF下的applicationContext.xml為Spring的配置文件,struts-config.xml為Struts的配置文件,file-upload.jsp為文件上傳頁(yè)面,file-list.jsp為文件列表頁(yè)面。
本文后面的章節將從數據持久層->業(yè)務(wù)層->W(wǎng)eb層的開(kāi)發(fā)順序,逐層講解文件上傳下載的開(kāi)發(fā)過(guò)程。
數據持久層
1、領(lǐng)域對象及映射文件
您可以使用Hibernate Middlegen、HIbernate Tools、Hibernate Syhchronizer等工具或手工的方式,編寫(xiě)Hibernate的領(lǐng)域對象和映射文件。其中對應T_FILE表的領(lǐng)域對象Tfile.java為:
代碼 1 領(lǐng)域對象Tfile
1. package sshfile.model; 2. public class Tfile 3.{ 4. private String fileId; 5. private String fileName; 6. private byte[] fileContent; 7. private String remark; 8. …//getter and setter 9. } |
特別需要注意的是:數據庫表為Blob類(lèi)型的字段在Tfile中的fileContent類(lèi)型為byte[]。Tfile的Hibernate映射文件Tfile.hbm.xml放在Tfile .java類(lèi)文件的相同目錄下:
代碼 2 領(lǐng)域對象映射文件
1. <?xml version="1.0"?> 2. <!DOCTYPE hibernate-mapping PUBLIC 3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > 5. <hibernate-mapping> 6. <class name="sshfile.model.Tfile" table="T_FILE"> 7. <id name="fileId" type="java.lang.String" column="FILE_ID"> 8. <generator class="uuid.hex"/> 9. </id> 10. <property name="fileContent" 11. type="org.springframework.orm.hibernate3.support.BlobByteArrayType" 12. column="FILE_CONTENT" lazy="true"/> 13. …//其它一般字段的映射 14. </class> 15. </hibernate-mapping> |
fileContent字段映射為Spring所提供的BlobByteArrayType類(lèi)型,BlobByteArrayType是用戶(hù)自定義的數據類(lèi)型,它實(shí)現了Hibernate 的org.hibernate.usertype.UserType接口。BlobByteArrayType使用從sessionFactory獲取的Lob操作句柄lobHandler將byte[]的數據保存到Blob數據庫字段中。這樣,我們就再沒(méi)有必要通過(guò)硬編碼的方式,先insert然后再u(mài)pdate來(lái)完成Blob類(lèi)型數據的持久化,這個(gè)原來(lái)難伺候的老爺終于被平民化了。關(guān)于lobHandler的配置請見(jiàn)本文后面的內容。
此外lazy="true"說(shuō)明地返回整個(gè)Tfile對象時(shí),并不返回fileContent這個(gè)字段的數據,只有在顯式調用tfile.getFileContent()方法時(shí)才真正從數據庫中獲取fileContent的數據。這是Hibernate3引入的新特性,對于包含重量級大數據的表字段,這種抽取方式提高了對大字段操作的靈活性,否則加載Tfile對象的結果集時(shí)如果總是返回fileContent,這種批量的數據抽取將可以引起數據庫的"洪泛效應"。
2、DAO編寫(xiě)和配置
Spring強調面向接口編程,所以我們將所有對Tfile的數據操作的方法定義在TfileDAO接口中,這些接口方法分別是:
·findByFildId(String fileId)
·save(Tfile tfile)
·List findAll()
TfileDAOHibernate提供了對TfileDAO接口基于Hibernate的實(shí)現,如代碼 3所示:
代碼 3 基于Hibernate 的fileDAO實(shí)現類(lèi)
1. package sshfile.dao; 2. 3. import sshfile.model.*; 4. import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 5. import java.util.List; 6. 7. public class TfileDAOHibernate 8. extends HibernateDaoSupport implements TfileDAO 9. { 10. public Tfile findByFildId(String fileId) 11. { 12. return (Tfile) getHibernateTemplate().get(Tfile.class, fileId); 13. } 14. public void save(Tfile tfile) 15. { 16. getHibernateTemplate().save(tfile); 17. getHibernateTemplate().flush(); 18. } 19. public List findAll() 20. { 21. return getHibernateTemplate().loadAll(Tfile.class); 22. } 23. } |
TfileDAOHibernate通過(guò)擴展Spring提供的Hibernate支持類(lèi)HibernateDaoSupport而建立,HibernateDaoSupport封裝了HibernateTemplate,而HibernateTemplate封裝了Hibernate所提供幾乎所有的的數據操作方法,如execute(HibernateCallback action),load(Class entityClass, Serializable id),save(final Object entity)等等。
所以我們的DAO只需要簡(jiǎn)單地調用父類(lèi)的HibernateTemplate就可以完成幾乎所有的數據庫操作了。
由于Spring通過(guò)代理Hibernate完成數據層的操作,所以原Hibernate的配置文件hibernate.cfg.xml的信息也轉移到Spring的配置文件中:
代碼 4 Spring中有關(guān)Hibernate的配置信息
1. <beans> 2. <!-- 數據源的配置 //--> 3. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 4. destroy-method="close"> 5. <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> 6. <property name="url" value="jdbc:oracle:thin:@localhost:1521:ora9i"/> 7. <property name="username" value="test"/> 8. <property name="password" value="test"/> 9. </bean> 10. <!-- Hibernate會(huì )話(huà)工廠(chǎng)配置 //--> 11. <bean id="sessionFactory" 12. class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 13. <property name="dataSource" ref="dataSource"/> 14. <property name="mappingDirectoryLocations"> 15. <list> 16. <value>classpath:/sshfile/model</value> 17. </list> 18. </property> 19. <property name="hibernateProperties"> 20. <props> 21. <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop> 22. <prop key="hibernate.cglib.use_reflection_optimizer">true</prop> 23. </props> 24. </property> 25. </bean> 26. <!-- Hibernate 模板//--> 27. <bean id="hibernateTemplate" 28. class="org.springframework.orm.hibernate3.HibernateTemplate"> 29. <property name="sessionFactory" ref="sessionFactory"/> 30. </bean> 31. <!--DAO配置 //--> 32. <bean id="tfileDAO" class="sshfile.dao.TfileDAOHibernate"> 33. <property name="hibernateTemplate" ref="hibernateTemplate" /> 34. </bean> 35. … 36. </beans> |
第3~9行定義了一個(gè)數據源,其實(shí)現類(lèi)是apache的BasicDataSource,第11~25行定義了Hibernate的會(huì )話(huà)工廠(chǎng),會(huì )話(huà)工廠(chǎng)類(lèi)用Spring提供的LocalSessionFactoryBean維護,它注入了數據源和資源映射文件,此外還通過(guò)一些鍵值對設置了Hibernate所需的屬性。
其中第16行通過(guò)類(lèi)路徑的映射方式,將sshfile.model類(lèi)包目錄下的所有領(lǐng)域對象的映射文件裝載進(jìn)來(lái),在本文的例子里,它將裝載進(jìn)Tfile.hbm.xml映射文件。如果有多個(gè)映射文件需要聲明,使用類(lèi)路徑映射方式顯然比直接單獨指定映射文件名的方式要簡(jiǎn)便。
第27~30行定義了Spring代理Hibernate數據操作的HibernateTemplate模板,而第32~34行將該模板注入到tfileDAO中。
需要指定的是Spring 1.2.5提供了兩套Hibernate的支持包,其中Hibernate 2相關(guān)的封裝類(lèi)位于org.springframework.orm.hibernate2.*包中,而Hibernate 3.0的封裝類(lèi)位于org.springframework.orm.hibernate3.*包中,需要根據您所選用Hibernate版本進(jìn)行正確選擇。
3、Lob字段處理的配置
我們前面已經(jīng)指出Oracle的Lob字段和一般類(lèi)型的字段在操作上有一個(gè)明顯的區別--那就是你必須首先通過(guò)Oracle的empty_blob()/empty_clob()初始化Lob字段,然后獲取該字段的引用,通過(guò)這個(gè)引用更改其值。所以要完成對Lob字段的操作,Hibernate必須執行兩步數據庫訪(fǎng)問(wèn)操作,先Insert再Update。
使用BlobByteArrayType字段類(lèi)型后,為什么我們就可以象一般的字段類(lèi)型一樣操作Blob字段呢?可以確定的一點(diǎn)是:BlobByteArrayType不可能逾越Blob天生的操作方式,原來(lái)是BlobByteArrayType數據類(lèi)型本身具體數據訪(fǎng)問(wèn)的功能,它通過(guò)LobHandler將兩次數據訪(fǎng)問(wèn)的動(dòng)作隱藏起來(lái),使Blob字段的操作在表現上和其他一般字段業(yè)類(lèi)型無(wú)異,所以L(fǎng)obHandler即是那個(gè)"苦了我一個(gè),幸福十億人"的那位幕后英雄。
LobHandler必須注入到Hibernate會(huì )話(huà)工廠(chǎng)sessionFactory中,因為sessionFactory負責產(chǎn)生與數據庫交互的Session。LobHandler的配置如代碼 5所示:
代碼 5 Lob字段的處理句柄配置
1. <beans> 2. … 3. <bean id="nativeJdbcExtractor" 4. class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor" 5. lazy-init="true"/> 6. <bean id="lobHandler" 7. class="org.springframework.jdbc.support.lob.OracleLobHandler" lazy-init="true"> 8. <property name="nativeJdbcExtractor"> 9. <ref local="nativeJdbcExtractor"/> 10. </property> 11. </bean> 12. … 13. </beans> |
首先,必須定義一個(gè)能夠從連接池中抽取出本地數據庫JDBC對象(如OracleConnection,OracleResultSet等)的抽取器:nativeJdbcExtractor,這樣才可以執行一些特定數據庫的操作。對于那些僅封裝了Connection而未包括Statement的簡(jiǎn)單數據連接池,SimpleNativeJdbcExtractor是效率最高的抽取器實(shí)現類(lèi),但具體到apache的BasicDataSource連接池,它封裝了所有JDBC的對象,這時(shí)就需要使用CommonsDbcpNativeJdbcExtractor了。Spring針對幾個(gè)著(zhù)名的Web服務(wù)器的數據源提供了相應的JDBC抽取器:
·WebLogic:WebLogicNativeJdbcExtractor
·WebSphere:WebSphereNativeJdbcExtractor
·JBoss:JBossNativeJdbcExtractor
在定義了JDBC抽取器后,再定義lobHandler。Spring 1.2.5提供了兩個(gè)lobHandler:
·DefaultLobHandler:適用于大部分的數據庫,如SqlServer,MySQL,對Oracle 10g也適用,但不適用于Oracle 9i(看來(lái)Oracle 9i確實(shí)是個(gè)怪胎,誰(shuí)叫Oracle 公司自己都說(shuō)Oracle 9i是一個(gè)過(guò)渡性的產(chǎn)品呢)。
·OracleLobHandler:適用于Oracle 9i和Oracle 10g。
由于我們的數據庫是Oracle9i,所以使用OracleLobHandler。
在配置完LobHandler后, 還需要將其注入到sessionFactory的Bean中,下面是調用后的sessionFactory Bean的配置:
代碼 6 將lobHandler注入到sessionFactory中的配置
1. <beans> 2. … 3. <bean id="sessionFactory" 4. class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 5. <property name="dataSource" ref="dataSource"/> 6. <!-- 為處理Blob類(lèi)型字段的句柄聲明 //--> 7. <property name="lobHandler" ref="lobHandler"/> 8. … 9. </bean> 10. … 11. </beans> |
如第7所示,通過(guò)sessionFactory的lobHandler屬性進(jìn)行注入。
業(yè)務(wù)層
1、業(yè)務(wù)層接口
"面向接口而非面向類(lèi)編程"是Spring不遺余力所推薦的編程原則,這條原則也已經(jīng)為大部開(kāi)發(fā)者所接受;此外,JDK的動(dòng)態(tài)代理只對接口有效,否則必須使用CGLIB生成目標類(lèi)的子類(lèi)。我們依從于Spring的倡導為業(yè)務(wù)類(lèi)定義一個(gè)接口:
代碼 7 業(yè)務(wù)層操作接口
1. public interface FileService 2. { 3. void save(FileActionForm fileForm);//將提交的上傳文件保存到數據表中 4. List getAllFile();//得到T_FILE所示記錄 5. void write(OutputStream os,String fileId);//將某個(gè)文件的文件數據寫(xiě)出到輸出流中 6. String getFileName(String fileId);//獲取文件名 7. } |
其中save(FileActionForm fileForm)方法,將封裝在fileForm中的上傳文件保存到數據庫中,這里我們使用FileActionForm作為方法入參,FileActionForm是Web層的表單數據對象,它封裝了提交表單的數據。將FileActionForm直接作為業(yè)務(wù)層的接口入參,相當于將Web層傳播到業(yè)務(wù)層中去,即將業(yè)務(wù)層綁定在特定的Web層實(shí)現技術(shù)中,按照分層模型學(xué)院派的觀(guān)點(diǎn),這是一種反模塊化的設計,但在"一般"的業(yè)務(wù)系統并無(wú)需提供多種UI界面,系統Web層將來(lái)切換到另一種實(shí)現技術(shù)的可能性也微乎其微,所以筆者覺(jué)得沒(méi)有必要為了這個(gè)業(yè)務(wù)層完全獨立于調用層的過(guò)高目標而去搞一個(gè)額外的隔離層,浪費了原材料不說(shuō),還將系統搞得過(guò)于復雜,相比于其它原則,"簡(jiǎn)單"始終是最大的一條原則。
getAllFile()負責獲取T_FILE表所有記錄,以便在網(wǎng)頁(yè)上顯示出來(lái)。
而getFileName(String fileId)和write(OutputStream os,String fileId)則用于下載某個(gè)特定的文件。具體的調用是將Web層將response.getOutputStream()傳給write(OutputStream os,String fileId)接口,業(yè)務(wù)層直接將文件數據輸出到這個(gè)響應流中。具體實(shí)現請參見(jiàn)錯誤!未找到引用源。節下載文件部分。
2、業(yè)務(wù)層接口實(shí)現類(lèi)
FileService的實(shí)現類(lèi)為FileServiceImpl,其中save(FileActionForm fileForm)的實(shí)現如下所示:
代碼 8 業(yè)務(wù)接口實(shí)現類(lèi)之save()
1. … 2. public class FileServiceImpl 3. implements FileService 4. { 5. private TfileDAO tfileDAO; 6. public void save(FileActionForm fileForm) 7. { 8. Tfile tfile = new Tfile(); 9. try 10. { 11. tfile.setFileContent(fileForm.getFileContent().getFileData()); 12. } 13. catch (FileNotFoundException ex) 14. { 15. throw new RuntimeException(ex); 16. } 17. catch (IOException ex) 18. { 19. throw new RuntimeException(ex); 20. } 21. tfile.setFileName(fileForm.getFileContent().getFileName()); 22. tfile.setRemark(fileForm.getRemark()); 23. tfileDAO.save(tfile); 24. } 25. … 26. } |
在save(FileActionForm fileForm)方法里,完成兩個(gè)步驟:
其一,象在水桶間倒水一樣,將FileActionForm對象中的數據倒入到Tfile對象中;
其二,調用TfileDAO保存數據。
需要特別注意的是代碼的第11行,FileActionForm的fileContent屬性為org.apache.struts.upload.FormFile類(lèi)型,FormFile提供了一個(gè)方便的方法getFileData(),即可獲取文件的二進(jìn)制數據。通過(guò)解讀FormFile接口實(shí)現類(lèi)DiskFile的原碼,我們可能知道FormFile本身并不緩存文件的數據,只有實(shí)際調用getFileData()時(shí),才從磁盤(pán)文件輸入流中獲取數據。由于FormFile使用流讀取方式獲取數據,本身沒(méi)有緩存文件的所有數據,所以對于上傳超大體積的文件,也是沒(méi)有問(wèn)題的;但是,由于數據持久層的Tfile使用byte[]來(lái)緩存文件的數據,所以并不適合處理超大體積的文件(如100M),對于超大體積的文件,依然需要使用java.sql.Blob類(lèi)型以常規流操作的方式來(lái)處理。
此外,通過(guò)FileForm的getFileName()方法就可以獲得上傳文件的文件名,如第21行代碼所示。
write(OutputStream os,String fileId)方法的實(shí)現,如代碼 9所示:
代碼 9 業(yè)務(wù)接口實(shí)現類(lèi)之write()
1. … 2. public class FileServiceImpl 3. implements FileService 4. { 5. 6. public void write(OutputStream os, String fileId) 7. { 8. Tfile tfile = tfileDAO.findByFildId(fileId); 9. try 10. { 11. os.write(tfile.getFileContent()); 12. os.flush(); 13. } 14. catch (IOException ex) 15. { 16. throw new RuntimeException(ex); 17. } 18. } 19. … 20. } |
write(OutputStream os,String fileId)也簡(jiǎn)單地分為兩個(gè)操作步驟,首先,根據fileId加載表記錄,然后將fileContent寫(xiě)入到輸出流中。
3、Spring事務(wù)配置
下面,我們來(lái)看如何在Spring配置文件中為FileService配置聲明性的事務(wù)
1. <beans> 2. … 3. <bean id="transactionManager" 4. class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 5. <property name="sessionFactory" ref="sessionFactory"/> 6. </bean> 7. <!-- 事務(wù)處理的AOP配置 //--> 8. <bean id="txProxyTemplate" abstract="true" 9. class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 10. <property name="transactionManager" ref="transactionManager"/> 11. <property name="transactionAttributes"> 12. <props> 13. <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> 14. <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> 15. <prop key="save">PROPAGATION_REQUIRED</prop> 16. <prop key="write">PROPAGATION_REQUIRED,readOnly</prop> 17. </props> 18. </property> 19. </bean> 20. <bean id="fileService" parent="txProxyTemplate"> 21. <property name="target"> 22. <bean class="sshfile.service.FileServiceImpl"> 23. <property name="tfileDAO" ref="tfileDAO"/> 24. </bean> 25. </property> 26. </bean> 27. </beans> |
Spring的事務(wù)配置包括兩個(gè)部分:
其一,定義事務(wù)管理器transactionManager,使用HibernateTransactionManager實(shí)現事務(wù)管理;
其二,對各個(gè)業(yè)務(wù)接口進(jìn)行定義,其實(shí)txProxyTemplate和fileService是父子節點(diǎn)的關(guān)系,本來(lái)可以將txProxyTemplate定義的內容合并到fileService中一起定義,由于我們的系統僅有一個(gè)業(yè)務(wù)接口需要定義,所以將其定義的一部分抽象到父節點(diǎn)txProxyTemplate中意義確實(shí)不大,但是對于真實(shí)的系統,往往擁有為數眾多的業(yè)務(wù)接口需要定義,將這些業(yè)務(wù)接口定義內容的共同部分抽取到一個(gè)父節點(diǎn)中,然后在子節點(diǎn)中通過(guò)parent進(jìn)行關(guān)聯(lián),就可以大大簡(jiǎn)化業(yè)務(wù)接口的配置了。
父節點(diǎn)txProxyTemplate注入了事務(wù)管理器,此外還定義了業(yè)務(wù)接口事務(wù)管理的方法(允許通過(guò)通配符的方式進(jìn)行匹配聲明,如前兩個(gè)接口方法),有些接口方法僅對數據進(jìn)行讀操作,而另一些接口方法需要涉及到數據的更改。對于前者,可以通過(guò)readOnly標識出來(lái),這樣有利于操作性能的提高,需要注意的是由于父類(lèi)節點(diǎn)定義的Bean僅是子節點(diǎn)配置信息的抽象,并不能具體實(shí)現化一個(gè)Bean對象,所以需要特別標注為abstract="true",如第8行所示。
fileService作為一個(gè)目標類(lèi)被注入到事務(wù)代理器中,而fileService實(shí)現類(lèi)所需要的tfileDAO實(shí)例,通過(guò)引用3.2節中定義的tfileDAO Bean注入。
Web層實(shí)現
1、Web層的構件和交互流程
Web層包括主要3個(gè)功能:
·上傳文件。
·列出所有已經(jīng)上傳的文件列表,以供點(diǎn)擊下載。
·下載文件。
Web層實(shí)現構件包括與2個(gè)JSP頁(yè)面,1個(gè)ActionForm及一個(gè)Action:
·file-upload.jsp:上傳文件的頁(yè)面。
·file-list.jsp:已經(jīng)上傳文件的列表頁(yè)面。
·FileActionForm:file-upload.jsp頁(yè)面表單對應的ActionForm。
·FileAction:繼承org.apache.struts.actions.DispatchAction的Action,這樣這個(gè)Action就可以通過(guò)一個(gè)URL參數區分中響應不同的請求。
Web層的這些構件的交互流程如圖 6所示:
其中,在執行文件上傳的請求時(shí),FileAction在執行文件上傳后,forward到loadAllFile出口中,loadAllFile加載數據庫中所有已經(jīng)上傳的記錄,然后forward到名為fileListPage的出口中,調用file-list.jsp頁(yè)面顯示已經(jīng)上傳的記錄。
2、FileAction功能
Struts 1.0的Action有一個(gè)弱項:一個(gè)Action只能處理一種請求,Struts 1.1中引入了一個(gè)DispatchAction,允許通過(guò)URL參數指定調用Action中的某個(gè)方法,如http://yourwebsite/fileAction.do?method=upload即調用FileAction中的upload方法。通過(guò)這種方式,我們就可以將一些相關(guān)的請求集中到一個(gè)Action當中編寫(xiě),而沒(méi)有必要為某個(gè)請求操作編寫(xiě)一個(gè)Action類(lèi)。但是參數名是要在struts-config.xml中配置的:
1. <struts-config> 2. <form-beans> 3. <form-bean name="fileActionForm" type="sshfile.web.FileActionForm" /> 4. </form-beans> 5. <action-mappings> 6. <action name="fileActionForm" parameter="method" path="/fileAction" 7. type="sshfile.web.FileAction"> 8. <forward name="fileListPage" path="/file-list.jsp" /> 9. <forward name="loadAllFile" path="/fileAction.do?method=listAllFile" /> 10. </action> 11. </action-mappings> 12. </struts-config> |
第6行的parameter="method"指定了承載方法名的參數,第9行中,我們還配置了一個(gè)調用FileAction不同方法的Action出口。
FileAction共有3個(gè)請求響應的方法,它們分別是:
·upload(…):處理上傳文件的請求。
·listAllFile(…):處理加載數據庫表中所有記錄的請求。
·download(…):處理下載文件的請求。
下面我們分別對這3個(gè)請求處理方法進(jìn)行講解。
2.1 上傳文件
上傳文件的請求處理方法非常簡(jiǎn)單,簡(jiǎn)之言之,就是從Spring容器中獲取業(yè)務(wù)層處理類(lèi)FileService,調用其save(FileActionForm form)方法上傳文件,如下所示:
1. public class FileAction 2. extends DispatchAction 3. { 4. //將上傳文件保存到數據庫中 5. public ActionForward upload(ActionMapping mapping, ActionForm form, 6. HttpServletRequest request, 7. HttpServletResponse response) 8. { 9. FileActionForm fileForm = (FileActionForm) form; 10. FileService fileService = getFileService(); 11. fileService.save(fileForm); 12. return mapping.findForward("loadAllFile"); 13. } 14. //從Spring容器中獲取FileService對象 15. private FileService getFileService() 16. { 17. ApplicationContext appContext = WebApplicationContextUtils. 18. getWebApplicationContext(this.getServlet().getServletContext()); 19. return (FileService) appContext.getBean("fileService"); 20. } 21. … 22. } |
由于FileAction其它兩個(gè)請求處理方法也需要從Spring容器中獲取FileService實(shí)例,所以我們特別提供了一個(gè)getFileService()方法(第15~21行)。重構的一條原則就是:"發(fā)現代碼中有重復的表達式,將其提取為一個(gè)變量;發(fā)現類(lèi)中有重復的代碼段,將其提取為一個(gè)方法;發(fā)現不同類(lèi)中有相同的方法,將其提取為一個(gè)類(lèi)"。在真實(shí)的系統中,往往擁有多個(gè)Action和多個(gè)Service類(lèi),這時(shí)一個(gè)比較好的設置思路是,提供一個(gè)獲取所有Service實(shí)現對象的工具類(lèi),這樣就可以將Spring 的Service配置信息屏蔽在一個(gè)類(lèi)中,否則Service的配置名字散落在程序各處,維護性是很差的。
2.2 列出所有已經(jīng)上傳的文件
listAllFile方法調用Servie層方法加載T_FILE表中所有記錄,并將其保存在Request域中,然后forward到列表頁(yè)面中:
1. public class FileAction 2. extends DispatchAction 3. { 4. … 5. public ActionForward listAllFile(ActionMapping mapping, ActionForm form, 6. HttpServletRequest request, 7. HttpServletResponse response) 8. throws ModuleException 9. { 10. FileService fileService = getFileService(); 11. List fileList = fileService.getAllFile(); 12. request.setAttribute("fileList",fileList); 13. return mapping.findForward("fileListPage"); 14. } 15. } |
file-list.jsp頁(yè)面使用Struts標簽展示出保存在Request域中的記錄:
1. <%@page contentType="text/html; charset=GBK"%> 2. <%@taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%> 3. <%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> 4. <html> 5. <head> 6. <title>file-download</title> 7. </head> 8. <body bgcolor="#ffffff"> 9. <ol> 10. <logic:iterate id="item" name="fileList" scope="request"> 11. <li> 12. <a href=‘fileAction.do?method=download&fileId= 13. <bean:write name="item"property="fileId"/>‘> 14. <bean:write name="item" property="fileName"/> 15. </a> 16. </li> 17. </logic:iterate> 18. </ol> 19. </body> 20. </html> |
展現頁(yè)面的每條記錄掛接著(zhù)一個(gè)鏈接地址,形如:fileAction.do?method=download&fileId=xxx,method參數指定了這個(gè)請求由FileAction的download方法來(lái)響應,fileId指定了記錄的主鍵。
由于在FileActionForm中,我們定義了fileId的屬性,所以在download響應方法中,我們將可以從FileActionForm中取得fileId的值。這里涉及到一個(gè)處理多個(gè)請求Action所對應的ActionForm的設計問(wèn)題,由于原來(lái)的Action只能對應一個(gè)請求,那么原來(lái)的ActionForm非常簡(jiǎn)單,它僅需要將這個(gè)請求的參數項作為其屬性就可以了,但現在一個(gè)Action對應多個(gè)請求,每個(gè)請求所對應的參數項是不一樣的,此時(shí)的ActionForm的屬性就必須是多請求參數項的并集了。所以,除了文件上傳請求所對應的fileContent和remark屬性外還包括文件下載的fileId屬性:
當然這樣會(huì )造成屬性的冗余,比如在文件上傳的請求中,只會(huì )用到fileContent和remark屬性,而在文件下載的請求時(shí),只會(huì )使用到fileId屬性。但這種冗余是會(huì )帶來(lái)好處的--它使得一個(gè)Action可以處理多個(gè)請求。
2.3 下載文件
在列表頁(yè)面中點(diǎn)擊一個(gè)文件下載,其請求由FileAction的download方法來(lái)響應,download方法調用業(yè)務(wù)層的FileService方法,獲取文件數據并寫(xiě)出到response的響應流中。通過(guò)合理設置HTTP響應頭參數,將響應流在客戶(hù)端表現為一個(gè)下載文件對話(huà)框,其代碼如下所示:
代碼 10 業(yè)務(wù)接口實(shí)現類(lèi)之download
1. public class FileAction 2. extends DispatchAction 3. { 4. … 5. public ActionForward download(ActionMapping mapping, ActionForm form, 6. HttpServletRequest request, 7. HttpServletResponse response) 8. throws ModuleException 9. { 10. FileActionForm fileForm = (FileActionForm) form; 11. FileService fileService = getFileService(); 12. String fileName = fileService.getFileName(fileForm.getFileId()); 13. try 14. { 15. response.setContentType("application/x-msdownload"); 16. response.setHeader("Content-Disposition", 17. "attachment;" + " filename="+ 18. new String(fileName.getBytes(), "ISO-8859-1")); 19. fileService.write(response.getOutputStream(), fileForm.getFileId()); 20. } 21. catch (Exception e) 22. { 23. throw new ModuleException(e.getMessage()); 24. } 25. return null; 26. } 27. } |
第15~18行,設置HTTP響應頭,將響應類(lèi)型設置為application/x-msdownload MIME類(lèi)型,則響應流在IE中將彈出一個(gè)文件下載的對話(huà)框,如圖 4所示。IE所支持的MIME類(lèi)型多達26種,您可以通過(guò)這個(gè)網(wǎng)址查看其他的MIME類(lèi)型:
http://msdn.microsoft.com/workshop/networking/moniker/overview/appendix_a.asp。
如果下載文件的文件名含有中文字符,如果不對其進(jìn)行硬編碼,如第18行所示,客戶(hù)文件下載對話(huà)框中出現的文件名將會(huì )發(fā)生亂碼。
第19行代碼獲得response的輸出流,作為FileServie write(OutputStream os,String fileId)的入參,這樣文件的內容將寫(xiě)到response的輸出流中。
3、web.xml文件的配置
Spring容器在何時(shí)啟動(dòng)呢?我可以在Web容器初始化來(lái)執行啟動(dòng)Spring容器的操作,Spring提供了兩種方式啟動(dòng)的方法:
·通過(guò)org.springframework.web.context .ContextLoaderListener容器監聽(tīng)器,在Web容器初始化時(shí)觸發(fā)初始化Spring容器,在web.xml中通過(guò)<listener></listener>對其進(jìn)行配置。
·通過(guò)Servlet org.springframework.web.context.ContextLoaderServlet,將其配置為自動(dòng)啟動(dòng)的Servlet,在Web容器初始化時(shí),通過(guò)這個(gè)Servlet啟動(dòng)Spring容器。
在初始化Spring容器之前,必須先初始化log4J的引擎,Spring也提供了容器監聽(tīng)器和自動(dòng)啟動(dòng)Servlet兩種方式對log4J引擎進(jìn)行初始化:
·org.springframework.web.util .Log4jConfigListener
·org.springframework.web.util.Log4jConfigServlet
下面我們來(lái)說(shuō)明如何配置web.xml啟動(dòng)Spring容器:
代碼 11 web.xml中對應Spring的配置內容
1. <web-app> 2. <context-param> 3. <param-name>contextConfigLocation</param-name> 4. <param-value>/WEB-INF/applicationContext.xml</param-value> 5. </context-param> 6. <context-param> 7. <param-name>log4jConfigLocation</param-name> 8. <param-value>/WEB-INF/log4j.properties</param-value> 9. </context-param> 10. <servlet> 11. <servlet-name>log4jInitServlet</servlet-name> 12. <servlet-class>org.springframework.web.util.Log4jConfigServlet</servlet-class> 13. <load-on-startup>1</load-on-startup> 14. </servlet> 15. <servlet> 16. <servlet-name>springInitServlet</servlet-name> 17. <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> 18. <load-on-startup>2</load-on-startup> 19. </servlet> 20. … 21. </web-app> |
啟動(dòng)Spring容器時(shí),需要得到兩個(gè)信息:Spring配置文件的地址和Log4J屬性文件,這兩上信息分別通過(guò)contextConfigLocationWeb和log4jConfigLocation容器參數指定,如果有多個(gè)Spring配置文件,則用逗號隔開(kāi),如:
/WEB-INF/applicationContext_1.xml, /WEB-INF/applicationContext_1.xm2
由于在啟動(dòng)ContextLoaderServlet之前,必須事先初始化Log4J的引擎,所以L(fǎng)og4jConfigServlet必須在ContextLoaderServlet之前啟動(dòng),這通過(guò)<load-on-startup>來(lái)指定它們啟動(dòng)的先后順序。
亂碼是開(kāi)發(fā)Web應用程序一個(gè)比較老套又常見(jiàn)問(wèn)題,由于不同Web應用服務(wù)器的默認編碼是不一樣的,為了方便Web應用在不同的Web應用服務(wù)器上移植,最好的做法是Web程序自身來(lái)處理編碼轉換的工作。經(jīng)典的作法是在web.xml中配置一個(gè)編碼轉換過(guò)濾器,Spring就提供了一個(gè)編碼過(guò)濾器類(lèi)CharacterEncodingFilter,下面,我們?yōu)閼门渲蒙线@個(gè)過(guò)濾器:
1. <web-app> 2. … 3. <filter> 4. <filter-name>encodingFilter</filter-name> 5. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 6. <init-param> 7. <param-name>encoding</param-name> 8. <param-value>GBK</param-value> 9. </init-param> 10. </filter> 11. <filter-mapping> 12. <filter-name>encodingFilter</filter-name> 13. <url-pattern>/*</url-pattern> 14. </filter-mapping> 15. … 16. </web-app> |
Spring的過(guò)濾器類(lèi)是org.springframework.web.filter.CharacterEncodingFilter,通過(guò)encoding參數指定編碼轉換類(lèi)型為GBK,<filter-mapping>的配置使該過(guò)濾器截獲所有的請示。
Struts的框架也需要在web.xml中配置,想必讀者朋友對Struts的配置都很熟悉,故在此不再提及,請參見(jiàn)本文所提供的源碼。
總結 本文通過(guò)一個(gè)文件上傳下載的Web應用,講解了如何構建基于SSH的Web應用,通過(guò)Struts和FormFile,Spring的LobHandler以及Spring為HibernateBlob處理所提供的用戶(hù)類(lèi)BlobByteArrayType ,實(shí)現上傳和下載文件的功能僅需要廖廖數行的代碼即告完成。讀者只需對程序作稍許的調整,即可處理Clob字段:
·領(lǐng)域對象對應Clob字段的屬性聲明為String類(lèi)型;
·映射文件對應Clob字段的屬性聲明為org.springframework.orm.hibernate3.support.ClobStringType類(lèi)型。
本文通過(guò)SSH對文件上傳下載簡(jiǎn)捷完美的實(shí)現得以管中窺豹了解SSH強強聯(lián)合構建Web應用的強大優(yōu)勢。在行文中,還穿插了一些分層的設計經(jīng)驗,配置技巧和Spring所提供的方便類(lèi),相信這些知識對您的開(kāi)發(fā)都有所裨益