Struts+Spring+Hibernate實現上傳下載(spring的最低框架配置,web.xml等)
檔案的上傳和下載在J2EE程式設計已經是一個非常古老的話題了,也許您馬上就能掰著指頭數出好幾個著名的大件:如SmartUpload、Apache的FileUpload。但如果您的專案是構建在Struts+Spring+Hibernate(以下稱SSH)框架上的,這些大件就顯得笨重而滄桑了,SSH提供了一個簡捷方便的檔案上傳下載的方案,我們只需要通過一些配置並輔以少量的程式碼就可以完好解決這個問題了。
本文將圍繞SSH檔案上傳下載的主題,向您詳細講述如何開發基於SSH的Web程式。SSH各框架的均為當前最新版本:
·Struts 1.2
·Spring 1.2.5
·Hibernate 3.0
本文選用的資料庫為Oracle 9i,當然你可以在不改動程式碼的情況下,通過配置檔案的調整將其移植到任何具有Blob欄位型別的資料庫上,如MySQL,SQLServer等。
總體實現
上傳檔案儲存到T_FILE表中,T_FILE表結構如下:
圖 1 T_FILE表結構 |
其中:
·FILE_ID:檔案ID,32個字元,用Hibernate的uuid.hex演算法生成。
·FILE_NAME:檔名。
·FILE_CONTENT:檔案內容,對應Oracle的Blob型別。
·REMARK:檔案備註。
檔案資料儲存在Blob型別的FILE_CONTENT表字段上,在Spring中採用OracleLobHandler來處理Lob欄位(包括Clob和Blob),由於在程式中不需要引用到oracle資料驅動程式的具體類且遮蔽了不同資料庫處理Lob欄位方法上的差別,從而撤除程式在多資料庫移植上的樊籬。
1.首先資料表中的Blob欄位在Java領域物件中宣告為byte[]型別,而非java.sql.Blob型別。
2.資料表Blob欄位在Hibernate持久化對映檔案中的type為org.springframework.orm.hibernate3.support.BlobByteArrayType,即Spring所提供的使用者自定義的型別,而非java.sql.Blob。
3.在Spring中使用org.springframework.jdbc.support.lob.OracleLobHandler處理Oracle資料庫的Blob型別欄位。
通過這樣的設定和配置,我們就可以象持久化表的一般欄位型別一樣處理Blob欄位了。
以上是Spring+Hibernate將檔案二進位制資料持久化到資料庫的解決方案,而Struts通過將表單中file型別的元件對映為ActionForm中型別為org.apache.struts.upload. FormFile的屬性來獲取表單提交的檔案資料。
綜上所述,我們可以通過圖 2,描繪出SSH處理檔案上傳的方案:
圖 2 SSH處理檔案上傳技術方案 |
檔案上傳的頁面如圖 3所示:
圖 3 檔案上傳頁面 |
檔案下載的頁面如圖 4所示:
圖 4 檔案下載頁面 |
該工程的資源結構如圖 5所示:
圖 5 工程資源結構 |
工程的類按SSH的層次結構劃分為資料持久層、業務層和Web層;WEB-INF下的applicationContext.xml為Spring的配置檔案,struts-config.xml為Struts的配置檔案,file-upload.jsp為檔案上傳頁面,file-list.jsp為檔案列表頁面。
本文後面的章節將從資料持久層->業務層->Web層的開發順序,逐層講解檔案上傳下載的開發過程。
資料持久層
1、領域物件及對映檔案
您可以使用Hibernate Middlegen、HIbernate Tools、Hibernate Syhchronizer等工具或手工的方式,編寫Hibernate的領域物件和對映檔案。其中對應T_FILE表的領域物件Tfile.java為:
程式碼 1 領域物件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型別的欄位在Tfile中的fileContent型別為byte[]。Tfile的Hibernate對映檔案Tfile.hbm.xml放在Tfile .java類檔案的相同目錄下:
程式碼 2 領域物件對映檔案
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型別,BlobByteArrayType是使用者自定義的資料型別,它實現了Hibernate 的org.hibernate.usertype.UserType介面。BlobByteArrayType使用從sessionFactory獲取的Lob操作控制代碼lobHandler將byte[]的資料儲存到Blob資料庫欄位中。這樣,我們就再沒有必要通過硬編碼的方式,先insert然後再update來完成Blob型別資料的持久化,這個原來難伺候的老爺終於被平民化了。關於lobHandler的配置請見本文後面的內容。
此外lazy="true"說明地返回整個Tfile物件時,並不返回fileContent這個欄位的資料,只有在顯式呼叫tfile.getFileContent()方法時才真正從資料庫中獲取fileContent的資料。這是Hibernate3引入的新特性,對於包含重量級大資料的表字段,這種抽取方式提高了對大欄位操作的靈活性,否則載入Tfile物件的結果集時如果總是返回fileContent,這種批量的資料抽取將可以引起資料庫的"洪泛效應"。
2、DAO編寫和配置
Spring強調面向介面程式設計,所以我們將所有對Tfile的資料操作的方法定義在TfileDAO介面中,這些介面方法分別是:
·findByFildId(String fileId)
·save(Tfile tfile)
·List findAll()
TfileDAOHibernate提供了對TfileDAO介面基於Hibernate的實現,如程式碼 3所示:
程式碼 3 基於Hibernate 的fileDAO實現類
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通過擴充套件Spring提供的Hibernate支援類HibernateDaoSupport而建立,HibernateDaoSupport封裝了HibernateTemplate,而HibernateTemplate封裝了Hibernate所提供幾乎所有的的資料操作方法,如execute(HibernateCallback action),load(Class entityClass, Serializable id),save(final Object entity)等等。
所以我們的DAO只需要簡單地呼叫父類的HibernateTemplate就可以完成幾乎所有的資料庫操作了。
由於Spring通過代理Hibernate完成資料層的操作,所以原Hibernate的配置檔案hibernate.cfg.xml的資訊也轉移到Spring的配置檔案中:
程式碼 4 Spring中有關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會話工廠配置 //--> 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行定義了一個數據源,其實現類是apache的BasicDataSource,第11~25行定義了Hibernate的會話工廠,會話工廠類用Spring提供的LocalSessionFactoryBean維護,它注入了資料來源和資源對映檔案,此外還通過一些鍵值對設定了Hibernate所需的屬性。
其中第16行通過類路徑的對映方式,將sshfile.model類包目錄下的所有領域物件的對映檔案裝載進來,在本文的例子裡,它將裝載進Tfile.hbm.xml對映檔案。如果有多個對映檔案需要宣告,使用類路徑對映方式顯然比直接單獨指定對映檔名的方式要簡便。
第27~30行定義了Spring代理Hibernate資料操作的HibernateTemplate模板,而第32~34行將該模板注入到tfileDAO中。
需要指定的是Spring 1.2.5提供了兩套Hibernate的支援包,其中Hibernate 2相關的封裝類位於org.springframework.orm.hibernate2.*包中,而Hibernate 3.0的封裝類位於org.springframework.orm.hibernate3.*包中,需要根據您所選用Hibernate版本進行正確選擇。
3、Lob欄位處理的配置
我們前面已經指出Oracle的Lob欄位和一般型別的欄位在操作上有一個明顯的區別--那就是你必須首先通過Oracle的empty_blob()/empty_clob()初始化Lob欄位,然後獲取該欄位的引用,通過這個引用更改其值。所以要完成對Lob欄位的操作,Hibernate必須執行兩步資料庫訪問操作,先Insert再Update。
使用BlobByteArrayType欄位型別後,為什麼我們就可以象一般的欄位型別一樣操作Blob欄位呢?可以確定的一點是:BlobByteArrayType不可能逾越Blob天生的操作方式,原來是BlobByteArrayType資料型別本身具體資料訪問的功能,它通過LobHandler將兩次資料訪問的動作隱藏起來,使Blob欄位的操作在表現上和其他一般欄位業型別無異,所以LobHandler即是那個"苦了我一個,幸福十億人"的那位幕後英雄。
LobHandler必須注入到Hibernate會話工廠sessionFactory中,因為sessionFactory負責產生與資料庫互動的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> |
首先,必須定義一個能夠從連線池中抽取出本地資料庫JDBC物件(如OracleConnection,OracleResultSet等)的抽取器:nativeJdbcExtractor,這樣才可以執行一些特定資料庫的操作。對於那些僅封裝了Connection而未包括Statement的簡單資料連線池,SimpleNativeJdbcExtractor是效率最高的抽取器實現類,但具體到apache的BasicDataSource連線池,它封裝了所有JDBC的物件,這時就需要使用CommonsDbcpNativeJdbcExtractor了。Spring針對幾個著名的Web伺服器的資料來源提供了相應的JDBC抽取器:
·WebLogic:WebLogicNativeJdbcExtractor
·WebSphere:WebSphereNativeJdbcExtractor
·JBoss:JBossNativeJdbcExtractor
在定義了JDBC抽取器後,再定義lobHandler。Spring 1.2.5提供了兩個lobHandler:
·DefaultLobHandler:適用於大部分的資料庫,如SqlServer,MySQL,對Oracle 10g也適用,但不適用於Oracle 9i(看來Oracle 9i確實是個怪胎,誰叫Oracle 公司自己都說Oracle 9i是一個過渡性的產品呢)。
·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型別欄位的控制代碼宣告 //--> 7. <property name="lobHandler" ref="lobHandler"/> 8. … 9. </bean> 10. … 11. </beans> |
如第7所示,通過sessionFactory的lobHandler屬性進行注入。
業務層
1、業務層介面
"面向介面而非面向類程式設計"是Spring不遺餘力所推薦的程式設計原則,這條原則也已經為大部開發者所接受;此外,JDK的動態代理只對介面有效,否則必須使用CGLIB生成目標類的子類。我們依從於Spring的倡導為業務類定義一個介面:
程式碼 7 業務層操作介面
1. public interface FileService 2. { 3. void save(FileActionForm fileForm);//將提交的上傳檔案儲存到資料表中 4. List getAllFile();//得到T_FILE所示記錄 5. void write(OutputStream os,String fileId);//將某個檔案的檔案資料寫出到輸出流中 6. String getFileName(String fileId);//獲取檔名 7. } |
其中save(FileActionForm fileForm)方法,將封裝在fileForm中的上傳檔案儲存到資料庫中,這裡我們使用FileActionForm作為方法入參,FileActionForm是Web層的表單資料物件,它封裝了提交表單的資料。將FileActionForm直接作為業務層的介面入參,相當於將Web層傳播到業務層中去,即將業務層繫結在特定的Web層實現技術中,按照分層模型學院派的觀點,這是一種反模組化的設計,但在"一般"的業務系統並無需提供多種UI介面,系統Web層將來切換到另一種實現技術的可能性也微乎其微,所以筆者覺得沒有必要為了這個業務層完全獨立於呼叫層的過高目標而去搞一個額外的隔離層,浪費了原材料不說,還將系統搞得過於複雜,相比於其它原則,"簡單"始終是最大的一條原則。
getAllFile()負責獲取T_FILE表所有記錄,以便在網頁上顯示出來。
而getFileName(String fileId)和write(OutputStream os,String fileId)則用於下載某個特定的檔案。具體的呼叫是將Web層將response.getOutputStream()傳給write(OutputStream os,String fileId)介面,業務層直接將檔案資料輸出到這個響應流中。具體實現請參見錯誤!未找到引用源。節下載檔案部分。
2、業務層介面實現類
FileService的實現類為FileServiceImpl,其中save(FileActionForm fileForm)的實現如下所示:
程式碼 8 業務介面實現類之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)方法裡,完成兩個步驟:
其一,象在水桶間倒水一樣,將FileActionForm物件中的資料倒入到Tfile物件中;
其二,呼叫TfileDAO儲存資料。
需要特別注意的是程式碼的第11行,FileActionForm的fileContent屬性為org.apache.struts.upload.FormFile型別,FormFile提供了一個方便的方法getFileData(),即可獲取檔案的二進位制資料。通過解讀FormFile介面實現類DiskFile的原碼,我們可能知道FormFile本身並不快取檔案的資料,只有實際呼叫getFileData()時,才從磁碟檔案輸入流中獲取資料。由於FormFile使用流讀取方式獲取資料,本身沒有快取檔案的所有資料,所以對於上傳超大體積的檔案,也是沒有問題的;但是,由於資料持久層的Tfile使用byte[]來快取檔案的資料,所以並不適合處理超大體積的檔案(如100M),對於超大體積的檔案,依然需要使用java.sql.Blob型別以常規流操作的方式來處理。
此外,通過FileForm的getFileName()方法就可以獲得上傳檔案的檔名,如第21行程式碼所示。
write(OutputStream os,String fileId)方法的實現,如程式碼 9所示:
程式碼 9 業務介面實現類之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)也簡單地分為兩個操作步驟,首先,根據fileId載入表記錄,然後將fileContent寫入到輸出流中。
3、Spring事務配置
下面,我們來看如何在Spring配置檔案中為FileService配置宣告性的事務
1. <beans> 2. … 3. <bean id="transactionManager" 4. class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 5. <property name="sessionFactory" ref="sessionFactory"/> 6. </bean> 7. <!-- 事務處理的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的事務配置包括兩個部分:
其一,定義事務管理器transactionManager,使用HibernateTransactionManager實現事務管理;
其二,對各個業務介面進行定義,其實txProxyTemplate和fileService是父子節點的關係,本來可以將txProxyTemplate定義的內容合併到fileService中一起定義,由於我們的系統僅有一個業務介面需要定義,所以將其定義的一部分抽象到父節點txProxyTemplate中意義確實不大,但是對於真實的系統,往往擁有為數眾多的業務介面需要定義,將這些業務介面定義內容的共同部分抽取到一個父節點中,然後在子節點中通過parent進行關聯,就可以大大簡化業務介面的配置了。
父節點txProxyTemplate注入了事務管理器,此外還定義了業務介面事務管理的方法(允許通過萬用字元的方式進行匹配宣告,如前兩個介面方法),有些介面方法僅對資料進行讀操作,而另一些介面方法需要涉及到資料的更改。對於前者,可以通過readOnly標識出來,這樣有利於操作效能的提高,需要注意的是由於父類節點定義的Bean僅是子節點配置資訊的抽象,並不能具體實現化一個Bean物件,所以需要特別標註為abstract="true",如第8行所示。
fileService作為一個目標類被注入到事務代理器中,而fileService實現類所需要的tfileDAO例項,通過引用3.2節中定義的tfileDAO Bean注入。
Web層實現
1、Web層的構件和互動流程
Web層包括主要3個功能:
·上傳檔案。
·列出所有已經上傳的檔案列表,以供點選下載。
·下載檔案。
Web層實現構件包括與2個JSP頁面,1個ActionForm及一個Action:
·file-upload.jsp:上傳檔案的頁面。
·file-list.jsp:已經上傳檔案的列表頁面。
·FileActionForm:file-upload.jsp頁面表單對應的ActionForm。
·FileAction:繼承org.apache.struts.actions.DispatchAction的Action,這樣這個Action就可以通過一個URL引數區分中響應不同的請求。
Web層的這些構件的互動流程如圖 6所示:
圖 6 Web層Struts流程圖 |
其中,在執行檔案上傳的請求時,FileAction在執行檔案上傳後,forward到loadAllFile出口中,loadAllFile載入資料庫中所有已經上傳的記錄,然後forward到名為fileListPage的出口中,呼叫file-list.jsp頁面顯示已經上傳的記錄。
2、FileAction功能
Struts 1.0的Action有一個弱項:一個Action只能處理一種請求,Struts 1.1中引入了一個DispatchAction,允許通過URL引數指定呼叫Action中的某個方法,如http://yourwebsite/fileAction.do?method=upload即呼叫FileAction中的upload方法。通過這種方式,我們就可以將一些相關的請求集中到一個Action當中編寫,而沒有必要為某個請求操作編寫一個Action類。但是引數名是要在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行中,我們還配置了一個呼叫FileAction不同方法的Action出口。
FileAction共有3個請求響應的方法,它們分別是:
·upload(…):處理上傳檔案的請求。
·listAllFile(…):處理載入資料庫表中所有記錄的請求。
·download(…):處理下載檔案的請求。
下面我們分別對這3個請求處理方法進行講解。
2.1 上傳檔案
上傳檔案的請求處理方法非常簡單,簡之言之,就是從Spring容器中獲取業務層處理類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其它兩個請求處理方法也需要從Spring容器中獲取FileService例項,所以我們特別提供了一個getFileService()方法(第15~21行)。重構的一條原則就是:"發現程式碼中有重複的表示式,將其提取為一個變數;發現類中有重複的程式碼段,將其提取為一個方法;發現不同類中有相同的方法,將其提取為一個類"。在真實的系統中,往往擁有多個Action和多個Service類,這時一個比較好的設定思路是,提供一個獲取所有Service實現物件的工具類,這樣就可以將Spring 的Service配置資訊遮蔽在一個類中,否則Service的配置名字散落在程式各處,維護性是很差的。
2.2 列出所有已經上傳的檔案
listAllFile方法呼叫Servie層方法載入T_FILE表中所有記錄,並將其儲存在Request域中,然後forward到列表頁面中:
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頁面使用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> |
展現頁面的每條記錄掛接著一個連結地址,形如:fileAction.do?method=download&fileId=xxx,method引數指定了這個請求由FileAction的download方法來響應,fileId指定了記錄的主鍵。
由於在FileActionForm中,我們定義了fileId的屬性,所以在download響應方法中,我們將可以從FileActionForm中取得fileId的值。這裡涉及到一個處理多個請求Action所對應的ActionForm的設計問題,由於原來的Action只能對應一個請求,那麼原來的ActionForm非常簡單,它僅需要將這個請求的引數項作為其屬性就可以了,但現在一個Action對應多個請求,每個請求所對應的引數項是不一樣的,此時的ActionForm的屬性就必須是多請求引數項的並集了。所以,除了檔案上傳請求所對應的fileContent和remark屬性外還包括檔案下載的fileId屬性:
圖 7 FileActionForm |
當然這樣會造成屬性的冗餘,比如在檔案上傳的請求中,只會用到fileContent和remark屬性,而在檔案下載的請求時,只會使用到fileId屬性。但這種冗餘是會帶來好處的--它使得一個Action可以處理多個請求。
2.3 下載檔案
在列表頁面中點選一個檔案下載,其請求由FileAction的download方法來響應,download方法呼叫業務層的FileService方法,獲取檔案資料並寫出到response的響應流中。通過合理設定HTTP響應頭引數,將響應流在客戶端表現為一個下載檔案對話方塊,其程式碼如下所示:
程式碼 10 業務介面實現類之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響應頭,將響應型別設定為application/x-msdownload MIME型別,則響應流在IE中將彈出一個檔案下載的對話方塊,如圖 4所示。IE所支援的MIME型別多達26種,您可以通過這個網址檢視其他的MIME型別:
http://msdn.microsoft.com/workshop/networking/moniker/overview/appendix_a.asp。
如果下載檔案的檔名含有中文字元,如果不對其進行硬編碼,如第18行所示,客戶檔案下載對話方塊中出現的檔名將會發生亂碼。
第19行程式碼獲得response的輸出流,作為FileServie write(OutputStream os,String fileId)的入參,這樣檔案的內容將寫到response的輸出流中。
3、web.xml檔案的配置
Spring容器在何時啟動呢?我可以在Web容器初始化來執行啟動Spring容器的操作,Spring提供了兩種方式啟動的方法:
·通過org.springframework.web.context .ContextLoaderListener容器監聽器,在Web容器初始化時觸發初始化Spring容器,在web.xml中通過<listener></listener>對其進行配置。
·通過Servlet org.springframework.web.context.ContextLoaderServlet,將其配置為自動啟動的Servlet,在Web容器初始化時,通過這個Servlet啟動Spring容器。
在初始化Spring容器之前,必須先初始化log4J的引擎,Spring也提供了容器監聽器和自動啟動Servlet兩種方式對log4J引擎進行初始化:
·org.springframework.web.util .Log4jConfigListener
·org.springframework.web.util.Log4jConfigServlet
下面我們來說明如何配置web.xml啟動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> |
啟動Spring容器時,需要得到兩個資訊:Spring配置檔案的地址和Log4J屬性檔案,這兩上資訊分別通過contextConfigLocationWeb和log4jConfigLocation容器引數指定,如果有多個Spring配置檔案,則用逗號隔開,如:
/WEB-INF/applicationContext_1.xml, /WEB-INF/applicationContext_1.xm2
由於在啟動ContextLoaderServlet之前,必須事先初始化Log4J的引擎,所以Log4jConfigServlet必須在ContextLoaderServlet之前啟動,這通過<load-on-startup>來指定它們啟動的先後順序。
亂碼是開發Web應用程式一個比較老套又常見問題,由於不同Web應用伺服器的預設編碼是不一樣的,為了方便Web應用在不同的Web應用伺服器上移植,最好的做法是Web程式自身來處理編碼轉換的工作。經典的作法是在web.xml中配置一個編碼轉換過濾器,Spring就提供了一個編碼過濾器類CharacterEncodingFilter,下面,我們為應用配置上這個過濾器:
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的過濾器類是org.springframework.web.filter.CharacterEncodingFilter,通過encoding引數指定編碼轉換型別為GBK,<filter-mapping>的配置使該過濾器截獲所有的請示。
Struts的框架也需要在web.xml中配置,想必讀者朋友對Struts的配置都很熟悉,故在此不再提及,請參見本文所提供的原始碼。
總結
本文通過一個檔案上傳下載的Web應用,講解了如何構建基於SSH的Web應用,通過Struts和FormFile,Spring的LobHandler以及Spring為HibernateBlob處理所提供的使用者類BlobByteArrayType ,實現上傳和下載檔案的功能僅需要廖廖數行的程式碼即告完成。讀者只需對程式作稍許的調整,即可處理Clob欄位:
·領域物件對應Clob欄位的屬性宣告為String型別;
·對映檔案對應Clob欄位的屬性宣告為org.springframework.orm.hibernate3.support.ClobStringType型別。
本文通過SSH對檔案上傳下載簡捷完美的實現得以管中窺豹瞭解SSH強強聯合構建Web應用的強大優勢。在行文中,還穿插了一些分層的設計經驗,配置技巧和Spring所提供的方便類,相信這些知識對您的開發都有所裨益。
相關推薦
Struts+Spring+Hibernate實現上傳下載(spring的最低框架配置,web.xml等)
引言 檔案的上傳和下載在J2EE程式設計已經是一個非常古老的話題了,也許您馬上就能掰著指頭數出好幾個著名的大件:如SmartUpload、Apache的FileUpload。但如果您的專案是構建在Struts+Spring+Hibernate(以下稱SSH)框架上的,這些大
使用httpclient實現上傳下載(javaWeb系統資料傳輸http實現)
目的:專案被拆分為兩個javaWeb專案,實現專案之間資料傳輸以及上傳、下載功能。 前臺展示專案A,後臺提供資料支援專案B 題外話: 兩個javaWeb傳輸之間使用json比較方便,如果恰好使用SpringMVC,配置相關JSON轉換工具(功能很強大非常好用),在@Con
spring boot使用nginx和ftp伺服器實現圖片上傳下載(windows server)
本人使用的springboot為1.5.6版本<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-pa
Spring框架學習筆記(7)——Spring Boot 實現上傳和下載
最近忙著都沒時間寫部落格了,做了個專案,實現了下載功能,沒用到上傳,寫這篇文章也是順便參考學習瞭如何實現上傳,上傳和下載做一篇筆記吧 下載 主要有下面的兩種方式: 通過ResponseEntity實現 通過寫HttpServletResponse的OutputStream實現 我只測試了ResponseE
FTPClient實現ftp的上傳下載(包含中文檔名和中文路徑問題)
整理一個ftp上傳下載的工具類,轉編碼的問題經測試都已經很好的解決,我這裡用的ftp為Windows系統下,Linux下的ftp操作寫法不一樣,下次有用到再整理: FtpUtil jar commons-net-3.3.jar maven依賴
在linux上安裝nexus作為私有倉庫並實現上傳下載jar包
最近的專案用到了分散式架構,分散式的好處自然不用多說,但有一個問題就是如何處理公共類或者說工具類,比方說時間格式轉換、生成隨機數、生成訂單號這些開發人員都要用到的函式,不可能讓每個開發人員都維護一個這樣的工具類,因此,想到了利用打包成jar包並上傳到maven倉庫的方式,讓開發人員可以共享公有類
檔案上傳下載(簡易體驗版)
檔案上傳 寫在servlet dopost中的方法: //建立讀取的檔案的工廠類 DiskFileItemFactory factory = new DiskFileItemFactory(); //讀取request裡面流的 解析類 ServletFileUpload up
kodexplorer建立家庭私有云實現上傳下載
個人電腦一般都是內網,要實現外網訪問費用是很高的,一般個人很難支付的起,但也有廉價的實現方法,相對個人來說能承受的了,記住一點免費的要不是不穩定,要不就是太慢,用樹莓派加內網版花生殼就可以輕鬆實現 工具/原料 樹莓派一臺 花生殼賬號內網版的 1.用vnc
國慶七篇-----struts2的檔案上傳下載(一)
struts2提供了檔案的上傳下載功能,不過需要我們對其提供相關的檔案引數。 比如檔案上傳,必須提供三種屬性,並對其提供setter和getter方法,而且必須按照以下規範命名: private File XXX; private String XXXFi
hdfs+zookeeper+ha叢集實現上傳下載刪除的功能
package cn.test; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IO
利用FtpClient實現上傳下載及獲得檔案目錄
sun程式碼中有個FtpClient,雖然沒有把它用做公開的工具包,但我們也還是可以拿它來利用一下. Java程式碼 1./** 2. * FTP檔案上傳與下載 3. * notice: 4. * 之所以每次都要連線一次ftp是讓它的目錄重新返回到
HDFS 使用Java api實現上傳/下載/刪除檔案
import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; publ
【WCF】利用WCF實現上傳下載檔案服務
引言 前段時間,用WCF做了一個小專案,其中涉及到檔案的上傳下載。出於複習鞏固的目的,今天簡單梳理了一下,整理出來,下面展示如何一步步實現一個上傳下載的WCF服務。 服務端 1.首先新建一個名為FileService的WCF服務庫專案,如下圖:
使用Xshell遠端登入(到伺服器的)Linux系統、使用Xftp上傳下載(伺服器)Linux系統裡面的檔案
一、遠端登入到伺服器(Xshell ) 介紹: 說明: Xshell 是目前最好的遠端登入到Linux操作的軟體,流暢的速度並且完美解決了中文亂碼的問題, 是目前程式設計師首選的軟體。 Xshell 是一個強大的安全終端模擬軟體,它支援SSH1, SSH2, 以及Micr
SSM框架下檔案的上傳下載(無內容時js彈窗提示)
SSM框架下檔案的上傳下載 非全部原創,僅用來記錄學習過的內容,自己添加了js判空彈窗的功能 1.首先我們建立一個測試用的jsp頁面,程式碼如下。 <%@ page language="java" contentType="text/html;
樹莓派使用百度網盤實現上傳下載
網上的教程看著都好複雜,那我就說的簡單一點。 首先需要安裝兩個庫,在樹莓派終端介面輸入: sudo pip install requests 和 sudo pip install bypy 安裝成功以後輸入: sudo bypy.py i
javaweb基於spring MVC的上傳下載例項(下)
檔案下載 要將Web應用系統中的檔案資源提供給使用者進行下載,首先我們要有一個頁面列出上傳檔案目錄下的所有檔案,當用戶點選檔案下載超連結時就進行下載操作,編寫一個ListFileServlet,用於列出Web應用系統中所有下載檔案。 1.1 搭建前臺頁面
java利用jcraft實現和遠端伺服器互動,實現上傳下載檔案
git地址:https://github.com/fusugongzi/upLoadAndDownloadFile 第一步:引入maven支援,新增maven依賴 <!-- https://mvnrepository.com/artifact/com.jcraft
hadoop生態系統學習之路(三)java實現上傳檔案(本地或ftp)至hdfs
在上一篇博文中,我們講了如何編寫、執行、測試一個MR,但是hdfs上的檔案是手動執行命令從本地linux上傳至hdfs的。在真實的執行環境中,我們不可能每次手動執行命令上傳的,這樣太過繁瑣。那麼,我們可以使用hdfs提供的java api實現檔案上傳至hdfs,
spring+mybatis+ajax上傳下載檔案
前端程式碼: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> <scr