一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Struts+Spring+Hibernate實(shí)現(xiàn)上傳下載

 天落雨 2006-01-16
下載本文源代碼

  引言

  文件的上傳和下載在J2EE編程已經(jīng)是一個(gè)非常古老的話題了,也許您馬上就能掰著指頭數(shù)出好幾個(gè)著名的大件:如SmartUpload、Apache的FileUpload。但如果您的項(xiàng)目是構(gòu)建在Struts+Spring+Hibernate(以下稱SSH)框架上的,這些大件就顯得笨重而滄桑了,SSH提供了一個(gè)簡(jiǎn)捷方便的文件上傳下載的方案,我們只需要通過(guò)一些配置并輔以少量的代碼就可以完好解決這個(gè)問(wèn)題了。

  本文將圍繞SSH文件上傳下載的主題,向您詳細(xì)講述如何開(kāi)發(fā)基于SSH的Web程序。SSH各框架的均為當(dāng)前最新版本:

  ·Struts 1.2

  ·Spring 1.2.5

  ·Hibernate 3.0

  本文選用的數(shù)據(jù)庫(kù)為Oracle 9i,當(dāng)然你可以在不改動(dòng)代碼的情況下,通過(guò)配置文件的調(diào)整將其移植到任何具有Blob字段類型的數(shù)據(jù)庫(kù)上,如MySQL,SQLServer等。

  總體實(shí)現(xiàn)

  上傳文件保存到T_FILE表中,T_FILE表結(jié)構(gòu)如下:


圖 1 T_FILE表結(jié)構(gòu)

  其中:

  ·FILE_ID:文件ID,32個(gè)字符,用Hibernate的uuid.hex算法生成。

  ·FILE_NAME:文件名。

  ·FILE_CONTENT:文件內(nèi)容,對(duì)應(yīng)Oracle的Blob類型。

  ·REMARK:文件備注。

  文件數(shù)據(jù)存儲(chǔ)在Blob類型的FILE_CONTENT表字段上,在Spring中采用OracleLobHandler來(lái)處理Lob字段(包括Clob和Blob),由于在程序中不需要引用到oracle數(shù)據(jù)驅(qū)動(dòng)程序的具體類且屏蔽了不同數(shù)據(jù)庫(kù)處理Lob字段方法上的差別,從而撤除程序在多數(shù)據(jù)庫(kù)移植上的樊籬。

  1.首先數(shù)據(jù)表中的Blob字段在Java領(lǐng)域?qū)ο笾新暶鳛閎yte[]類型,而非java.sql.Blob類型。

  2.?dāng)?shù)據(jù)表Blob字段在Hibernate持久化映射文件中的type為org.springframework.orm.hibernate3.support.BlobByteArrayType,即Spring所提供的用戶自定義的類型,而非java.sql.Blob。

  3.在Spring中使用org.springframework.jdbc.support.lob.OracleLobHandler處理Oracle數(shù)據(jù)庫(kù)的Blob類型字段。

  通過(guò)這樣的設(shè)置和配置,我們就可以象持久化表的一般字段類型一樣處理Blob字段了。

  以上是Spring+Hibernate將文件二進(jìn)制數(shù)據(jù)持久化到數(shù)據(jù)庫(kù)的解決方案,而Struts通過(guò)將表單中file類型的組件映射為ActionForm中類型為org.apache.struts.upload. FormFile的屬性來(lái)獲取表單提交的文件數(shù)據(jù)。

  綜上所述,我們可以通過(guò)圖 2,描繪出SSH處理文件上傳的方案:


圖 2 SSH處理文件上傳技術(shù)方案

  文件上傳的頁(yè)面如圖 3所示:


圖 3 文件上傳頁(yè)面

  文件下載的頁(yè)面如圖 4所示:


圖 4 文件下載頁(yè)面

  該工程的資源結(jié)構(gòu)如圖 5所示:


圖 5 工程資源結(jié)構(gòu)

  工程的類按SSH的層次結(jié)構(gòu)劃分為數(shù)據(jù)持久層、業(yè)務(wù)層和Web層;WEB-INF下的applicationContext.xml為Spring的配置文件,struts-config.xml為Struts的配置文件,file-upload.jsp為文件上傳頁(yè)面,file-list.jsp為文件列表頁(yè)面。

  本文后面的章節(jié)將從數(shù)據(jù)持久層->業(yè)務(wù)層->W(wǎng)eb層的開(kāi)發(fā)順序,逐層講解文件上傳下載的開(kāi)發(fā)過(guò)程。
數(shù)據(jù)持久層

  1、領(lǐng)域?qū)ο蠹坝成湮募?BR>
  您可以使用Hibernate Middlegen、HIbernate Tools、Hibernate Syhchronizer等工具或手工的方式,編寫(xiě)Hibernate的領(lǐng)域?qū)ο蠛陀成湮募?。其中?duì)應(yīng)T_FILE表的領(lǐng)域?qū)ο骉file.java為:

  代碼 1 領(lǐng)域?qū)ο骉file

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. }

  特別需要注意的是:數(shù)據(jù)庫(kù)表為Blob類型的字段在Tfile中的fileContent類型為byte[]。Tfile的Hibernate映射文件Tfile.hbm.xml放在Tfile .java類文件的相同目錄下:

  代碼 2 領(lǐng)域?qū)ο笥成湮募?BR>
1. <?xml version="1.0"?>
2. <!DOCTYPE hibernate-mapping PUBLIC
3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
4. "http://hibernate./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是用戶自定義的數(shù)據(jù)類型,它實(shí)現(xiàn)了Hibernate 的org.hibernate.usertype.UserType接口。BlobByteArrayType使用從sessionFactory獲取的Lob操作句柄lobHandler將byte[]的數(shù)據(jù)保存到Blob數(shù)據(jù)庫(kù)字段中。這樣,我們就再?zèng)]有必要通過(guò)硬編碼的方式,先insert然后再update來(lái)完成Blob類型數(shù)據(jù)的持久化,這個(gè)原來(lái)難伺候的老爺終于被平民化了。關(guān)于lobHandler的配置請(qǐng)見(jiàn)本文后面的內(nèi)容。

  此外lazy="true"說(shuō)明地返回整個(gè)Tfile對(duì)象時(shí),并不返回fileContent這個(gè)字段的數(shù)據(jù),只有在顯式調(diào)用tfile.getFileContent()方法時(shí)才真正從數(shù)據(jù)庫(kù)中獲取fileContent的數(shù)據(jù)。這是Hibernate3引入的新特性,對(duì)于包含重量級(jí)大數(shù)據(jù)的表字段,這種抽取方式提高了對(duì)大字段操作的靈活性,否則加載Tfile對(duì)象的結(jié)果集時(shí)如果總是返回fileContent,這種批量的數(shù)據(jù)抽取將可以引起數(shù)據(jù)庫(kù)的"洪泛效應(yīng)"。

  2、DAO編寫(xiě)和配置

  Spring強(qiáng)調(diào)面向接口編程,所以我們將所有對(duì)Tfile的數(shù)據(jù)操作的方法定義在TfileDAO接口中,這些接口方法分別是:

  ·findByFildId(String fileId)

  ·save(Tfile tfile)

  ·List findAll()

  TfileDAOHibernate提供了對(duì)TfileDAO接口基于Hibernate的實(shí)現(xiàn),如代碼 3所示:

  代碼 3 基于Hibernate 的fileDAO實(shí)現(xiàn)類

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ò)擴(kuò)展Spring提供的Hibernate支持類HibernateDaoSupport而建立,HibernateDaoSupport封裝了HibernateTemplate,而HibernateTemplate封裝了Hibernate所提供幾乎所有的的數(shù)據(jù)操作方法,如execute(HibernateCallback action),load(Class entityClass, Serializable id),save(final Object entity)等等。

  所以我們的DAO只需要簡(jiǎn)單地調(diào)用父類的HibernateTemplate就可以完成幾乎所有的數(shù)據(jù)庫(kù)操作了。

  由于Spring通過(guò)代理Hibernate完成數(shù)據(jù)層的操作,所以原Hibernate的配置文件hibernate.cfg.xml的信息也轉(zhuǎn)移到Spring的配置文件中:

  代碼 4 Spring中有關(guān)Hibernate的配置信息

1. <beans>
2. <!-- 數(shù)據(jù)源的配置 //-->
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ì)話工廠配置 //-->
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ù)據(jù)源,其實(shí)現(xiàn)類是apache的BasicDataSource,第11~25行定義了Hibernate的會(huì)話工廠,會(huì)話工廠類用Spring提供的LocalSessionFactoryBean維護(hù),它注入了數(shù)據(jù)源和資源映射文件,此外還通過(guò)一些鍵值對(duì)設(shè)置了Hibernate所需的屬性。

  其中第16行通過(guò)類路徑的映射方式,將sshfile.model類包目錄下的所有領(lǐng)域?qū)ο蟮挠成湮募b載進(jìn)來(lái),在本文的例子里,它將裝載進(jìn)Tfile.hbm.xml映射文件。如果有多個(gè)映射文件需要聲明,使用類路徑映射方式顯然比直接單獨(dú)指定映射文件名的方式要簡(jiǎn)便。

  第27~30行定義了Spring代理Hibernate數(shù)據(jù)操作的HibernateTemplate模板,而第32~34行將該模板注入到tfileDAO中。

  需要指定的是Spring 1.2.5提供了兩套Hibernate的支持包,其中Hibernate 2相關(guān)的封裝類位于org.springframework.orm.hibernate2.*包中,而Hibernate 3.0的封裝類位于org.springframework.orm.hibernate3.*包中,需要根據(jù)您所選用Hibernate版本進(jìn)行正確選擇。

  3、Lob字段處理的配置

  我們前面已經(jīng)指出Oracle的Lob字段和一般類型的字段在操作上有一個(gè)明顯的區(qū)別--那就是你必須首先通過(guò)Oracle的empty_blob()/empty_clob()初始化Lob字段,然后獲取該字段的引用,通過(guò)這個(gè)引用更改其值。所以要完成對(duì)Lob字段的操作,Hibernate必須執(zhí)行兩步數(shù)據(jù)庫(kù)訪問(wèn)操作,先Insert再Update。

  使用BlobByteArrayType字段類型后,為什么我們就可以象一般的字段類型一樣操作Blob字段呢?可以確定的一點(diǎn)是:BlobByteArrayType不可能逾越Blob天生的操作方式,原來(lái)是BlobByteArrayType數(shù)據(jù)類型本身具體數(shù)據(jù)訪問(wèn)的功能,它通過(guò)LobHandler將兩次數(shù)據(jù)訪問(wèn)的動(dòng)作隱藏起來(lái),使Blob字段的操作在表現(xiàn)上和其他一般字段業(yè)類型無(wú)異,所以LobHandler即是那個(gè)"苦了我一個(gè),幸福十億人"的那位幕后英雄。

  LobHandler必須注入到Hibernate會(huì)話工廠sessionFactory中,因?yàn)閟essionFactory負(fù)責(zé)產(chǎn)生與數(shù)據(jù)庫(kù)交互的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è)能夠從連接池中抽取出本地?cái)?shù)據(jù)庫(kù)JDBC對(duì)象(如OracleConnection,OracleResultSet等)的抽取器:nativeJdbcExtractor,這樣才可以執(zhí)行一些特定數(shù)據(jù)庫(kù)的操作。對(duì)于那些僅封裝了Connection而未包括Statement的簡(jiǎn)單數(shù)據(jù)連接池,SimpleNativeJdbcExtractor是效率最高的抽取器實(shí)現(xiàn)類,但具體到apache的BasicDataSource連接池,它封裝了所有JDBC的對(duì)象,這時(shí)就需要使用CommonsDbcpNativeJdbcExtractor了。Spring針對(duì)幾個(gè)著名的Web服務(wù)器的數(shù)據(jù)源提供了相應(yīng)的JDBC抽取器:

  ·WebLogic:WebLogicNativeJdbcExtractor

  ·WebSphere:WebSphereNativeJdbcExtractor

  ·JBoss:JBossNativeJdbcExtractor

  在定義了JDBC抽取器后,再定義lobHandler。Spring 1.2.5提供了兩個(gè)lobHandler:

  ·DefaultLobHandler:適用于大部分的數(shù)據(jù)庫(kù),如SqlServer,MySQL,對(duì)Oracle 10g也適用,但不適用于Oracle 9i(看來(lái)Oracle 9i確實(shí)是個(gè)怪胎,誰(shuí)叫Oracle 公司自己都說(shuō)Oracle 9i是一個(gè)過(guò)渡性的產(chǎn)品呢)。

  ·OracleLobHandler:適用于Oracle 9i和Oracle 10g。

  由于我們的數(shù)據(jù)庫(kù)是Oracle9i,所以使用OracleLobHandler。

  在配置完LobHandler后, 還需要將其注入到sessionFactory的Bean中,下面是調(diào)用后的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所示,通過(guò)sessionFactory的lobHandler屬性進(jìn)行注入。
業(yè)務(wù)層

  1、業(yè)務(wù)層接口

  "面向接口而非面向類編程"是Spring不遺余力所推薦的編程原則,這條原則也已經(jīng)為大部開(kāi)發(fā)者所接受;此外,JDK的動(dòng)態(tài)代理只對(duì)接口有效,否則必須使用CGLIB生成目標(biāo)類的子類。我們依從于Spring的倡導(dǎo)為業(yè)務(wù)類定義一個(gè)接口:

  代碼 7 業(yè)務(wù)層操作接口

1. public interface FileService
2. {
3. void save(FileActionForm fileForm);//將提交的上傳文件保存到數(shù)據(jù)表中
4. List getAllFile();//得到T_FILE所示記錄
5. void write(OutputStream os,String fileId);//將某個(gè)文件的文件數(shù)據(jù)寫(xiě)出到輸出流中
6. String getFileName(String fileId);//獲取文件名
7. }

  其中save(FileActionForm fileForm)方法,將封裝在fileForm中的上傳文件保存到數(shù)據(jù)庫(kù)中,這里我們使用FileActionForm作為方法入?yún)ⅲ現(xiàn)ileActionForm是Web層的表單數(shù)據(jù)對(duì)象,它封裝了提交表單的數(shù)據(jù)。將FileActionForm直接作為業(yè)務(wù)層的接口入?yún)?,相?dāng)于將Web層傳播到業(yè)務(wù)層中去,即將業(yè)務(wù)層綁定在特定的Web層實(shí)現(xiàn)技術(shù)中,按照分層模型學(xué)院派的觀點(diǎn),這是一種反模塊化的設(shè)計(jì),但在"一般"的業(yè)務(wù)系統(tǒng)并無(wú)需提供多種UI界面,系統(tǒng)Web層將來(lái)切換到另一種實(shí)現(xiàn)技術(shù)的可能性也微乎其微,所以筆者覺(jué)得沒(méi)有必要為了這個(gè)業(yè)務(wù)層完全獨(dú)立于調(diào)用層的過(guò)高目標(biāo)而去搞一個(gè)額外的隔離層,浪費(fèi)了原材料不說(shuō),還將系統(tǒng)搞得過(guò)于復(fù)雜,相比于其它原則,"簡(jiǎn)單"始終是最大的一條原則。

  getAllFile()負(fù)責(zé)獲取T_FILE表所有記錄,以便在網(wǎng)頁(yè)上顯示出來(lái)。

  而getFileName(String fileId)和write(OutputStream os,String fileId)則用于下載某個(gè)特定的文件。具體的調(diào)用是將Web層將response.getOutputStream()傳給write(OutputStream os,String fileId)接口,業(yè)務(wù)層直接將文件數(shù)據(jù)輸出到這個(gè)響應(yīng)流中。具體實(shí)現(xiàn)請(qǐng)參見(jiàn)錯(cuò)誤!未找到引用源。節(jié)下載文件部分。

  2、業(yè)務(wù)層接口實(shí)現(xiàn)類

  FileService的實(shí)現(xiàn)類為FileServiceImpl,其中save(FileActionForm fileForm)的實(shí)現(xiàn)如下所示:

  代碼 8 業(yè)務(wù)接口實(shí)現(xiàn)類之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對(duì)象中的數(shù)據(jù)倒入到Tfile對(duì)象中;

  其二,調(diào)用TfileDAO保存數(shù)據(jù)。

  需要特別注意的是代碼的第11行,F(xiàn)ileActionForm的fileContent屬性為org.apache.struts.upload.FormFile類型,F(xiàn)ormFile提供了一個(gè)方便的方法getFileData(),即可獲取文件的二進(jìn)制數(shù)據(jù)。通過(guò)解讀FormFile接口實(shí)現(xiàn)類DiskFile的原碼,我們可能知道FormFile本身并不緩存文件的數(shù)據(jù),只有實(shí)際調(diào)用getFileData()時(shí),才從磁盤(pán)文件輸入流中獲取數(shù)據(jù)。由于FormFile使用流讀取方式獲取數(shù)據(jù),本身沒(méi)有緩存文件的所有數(shù)據(jù),所以對(duì)于上傳超大體積的文件,也是沒(méi)有問(wèn)題的;但是,由于數(shù)據(jù)持久層的Tfile使用byte[]來(lái)緩存文件的數(shù)據(jù),所以并不適合處理超大體積的文件(如100M),對(duì)于超大體積的文件,依然需要使用java.sql.Blob類型以常規(guī)流操作的方式來(lái)處理。

  此外,通過(guò)FileForm的getFileName()方法就可以獲得上傳文件的文件名,如第21行代碼所示。

  write(OutputStream os,String fileId)方法的實(shí)現(xiàn),如代碼 9所示:

  代碼 9 業(yè)務(wù)接口實(shí)現(xiàn)類之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è)操作步驟,首先,根據(jù)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í)現(xiàn)事務(wù)管理;

  其二,對(duì)各個(gè)業(yè)務(wù)接口進(jìn)行定義,其實(shí)txProxyTemplate和fileService是父子節(jié)點(diǎn)的關(guān)系,本來(lái)可以將txProxyTemplate定義的內(nèi)容合并到fileService中一起定義,由于我們的系統(tǒng)僅有一個(gè)業(yè)務(wù)接口需要定義,所以將其定義的一部分抽象到父節(jié)點(diǎn)txProxyTemplate中意義確實(shí)不大,但是對(duì)于真實(shí)的系統(tǒng),往往擁有為數(shù)眾多的業(yè)務(wù)接口需要定義,將這些業(yè)務(wù)接口定義內(nèi)容的共同部分抽取到一個(gè)父節(jié)點(diǎn)中,然后在子節(jié)點(diǎn)中通過(guò)parent進(jìn)行關(guān)聯(lián),就可以大大簡(jiǎn)化業(yè)務(wù)接口的配置了。

  父節(jié)點(diǎn)txProxyTemplate注入了事務(wù)管理器,此外還定義了業(yè)務(wù)接口事務(wù)管理的方法(允許通過(guò)通配符的方式進(jìn)行匹配聲明,如前兩個(gè)接口方法),有些接口方法僅對(duì)數(shù)據(jù)進(jìn)行讀操作,而另一些接口方法需要涉及到數(shù)據(jù)的更改。對(duì)于前者,可以通過(guò)readOnly標(biāo)識(shí)出來(lái),這樣有利于操作性能的提高,需要注意的是由于父類節(jié)點(diǎn)定義的Bean僅是子節(jié)點(diǎn)配置信息的抽象,并不能具體實(shí)現(xiàn)化一個(gè)Bean對(duì)象,所以需要特別標(biāo)注為abstract="true",如第8行所示。

  fileService作為一個(gè)目標(biāo)類被注入到事務(wù)代理器中,而fileService實(shí)現(xiàn)類所需要的tfileDAO實(shí)例,通過(guò)引用3.2節(jié)中定義的tfileDAO Bean注入。

 Web層實(shí)現(xiàn)

  1、Web層的構(gòu)件和交互流程

  Web層包括主要3個(gè)功能:

  ·上傳文件。

  ·列出所有已經(jīng)上傳的文件列表,以供點(diǎn)擊下載。

  ·下載文件。

  Web層實(shí)現(xiàn)構(gòu)件包括與2個(gè)JSP頁(yè)面,1個(gè)ActionForm及一個(gè)Action:

  ·file-upload.jsp:上傳文件的頁(yè)面。

  ·file-list.jsp:已經(jīng)上傳文件的列表頁(yè)面。

  ·FileActionForm:file-upload.jsp頁(yè)面表單對(duì)應(yīng)的ActionForm。

  ·FileAction:繼承org.apache.struts.actions.DispatchAction的Action,這樣這個(gè)Action就可以通過(guò)一個(gè)URL參數(shù)區(qū)分中響應(yīng)不同的請(qǐng)求。

  Web層的這些構(gòu)件的交互流程如圖 6所示:


圖 6 Web層Struts流程圖

  其中,在執(zhí)行文件上傳的請(qǐng)求時(shí),F(xiàn)ileAction在執(zhí)行文件上傳后,forward到loadAllFile出口中,loadAllFile加載數(shù)據(jù)庫(kù)中所有已經(jīng)上傳的記錄,然后forward到名為fileListPage的出口中,調(diào)用file-list.jsp頁(yè)面顯示已經(jīng)上傳的記錄。

  2、FileAction功能

  Struts 1.0的Action有一個(gè)弱項(xiàng):一個(gè)Action只能處理一種請(qǐng)求,Struts 1.1中引入了一個(gè)DispatchAction,允許通過(guò)URL參數(shù)指定調(diào)用Action中的某個(gè)方法,如http://yourwebsite/fileAction.do?method=upload即調(diào)用FileAction中的upload方法。通過(guò)這種方式,我們就可以將一些相關(guān)的請(qǐng)求集中到一個(gè)Action當(dāng)中編寫(xiě),而沒(méi)有必要為某個(gè)請(qǐng)求操作編寫(xiě)一個(gè)Action類。但是參數(shù)名是要在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"指定了承載方法名的參數(shù),第9行中,我們還配置了一個(gè)調(diào)用FileAction不同方法的Action出口。

  FileAction共有3個(gè)請(qǐng)求響應(yīng)的方法,它們分別是:

  ·upload(…):處理上傳文件的請(qǐng)求。

  ·listAllFile(…):處理加載數(shù)據(jù)庫(kù)表中所有記錄的請(qǐng)求。

  ·download(…):處理下載文件的請(qǐng)求。

  下面我們分別對(duì)這3個(gè)請(qǐng)求處理方法進(jìn)行講解。

  2.1 上傳文件

  上傳文件的請(qǐng)求處理方法非常簡(jiǎn)單,簡(jiǎn)之言之,就是從Spring容器中獲取業(yè)務(wù)層處理類FileService,調(diào)用其save(FileActionForm form)方法上傳文件,如下所示:

1. public class FileAction
2. extends DispatchAction
3. {
4. //將上傳文件保存到數(shù)據(jù)庫(kù)中
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對(duì)象
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è)請(qǐng)求處理方法也需要從Spring容器中獲取FileService實(shí)例,所以我們特別提供了一個(gè)getFileService()方法(第15~21行)。重構(gòu)的一條原則就是:"發(fā)現(xiàn)代碼中有重復(fù)的表達(dá)式,將其提取為一個(gè)變量;發(fā)現(xiàn)類中有重復(fù)的代碼段,將其提取為一個(gè)方法;發(fā)現(xiàn)不同類中有相同的方法,將其提取為一個(gè)類"。在真實(shí)的系統(tǒng)中,往往擁有多個(gè)Action和多個(gè)Service類,這時(shí)一個(gè)比較好的設(shè)置思路是,提供一個(gè)獲取所有Service實(shí)現(xiàn)對(duì)象的工具類,這樣就可以將Spring 的Service配置信息屏蔽在一個(gè)類中,否則Service的配置名字散落在程序各處,維護(hù)性是很差的。

  2.2 列出所有已經(jīng)上傳的文件

  listAllFile方法調(diào)用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標(biāo)簽展示出保存在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. <o(jì)l>
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>

  展現(xiàn)頁(yè)面的每條記錄掛接著一個(gè)鏈接地址,形如:fileAction.do?method=download&fileId=xxx,method參數(shù)指定了這個(gè)請(qǐng)求由FileAction的download方法來(lái)響應(yīng),fileId指定了記錄的主鍵。

  由于在FileActionForm中,我們定義了fileId的屬性,所以在download響應(yīng)方法中,我們將可以從FileActionForm中取得fileId的值。這里涉及到一個(gè)處理多個(gè)請(qǐng)求Action所對(duì)應(yīng)的ActionForm的設(shè)計(jì)問(wèn)題,由于原來(lái)的Action只能對(duì)應(yīng)一個(gè)請(qǐng)求,那么原來(lái)的ActionForm非常簡(jiǎn)單,它僅需要將這個(gè)請(qǐng)求的參數(shù)項(xiàng)作為其屬性就可以了,但現(xiàn)在一個(gè)Action對(duì)應(yīng)多個(gè)請(qǐng)求,每個(gè)請(qǐng)求所對(duì)應(yīng)的參數(shù)項(xiàng)是不一樣的,此時(shí)的ActionForm的屬性就必須是多請(qǐng)求參數(shù)項(xiàng)的并集了。所以,除了文件上傳請(qǐng)求所對(duì)應(yīng)的fileContent和remark屬性外還包括文件下載的fileId屬性:


圖 7 FileActionForm

  當(dāng)然這樣會(huì)造成屬性的冗余,比如在文件上傳的請(qǐng)求中,只會(huì)用到fileContent和remark屬性,而在文件下載的請(qǐng)求時(shí),只會(huì)使用到fileId屬性。但這種冗余是會(huì)帶來(lái)好處的--它使得一個(gè)Action可以處理多個(gè)請(qǐng)求。

  2.3 下載文件

  在列表頁(yè)面中點(diǎn)擊一個(gè)文件下載,其請(qǐng)求由FileAction的download方法來(lái)響應(yīng),download方法調(diào)用業(yè)務(wù)層的FileService方法,獲取文件數(shù)據(jù)并寫(xiě)出到response的響應(yīng)流中。通過(guò)合理設(shè)置HTTP響應(yīng)頭參數(shù),將響應(yīng)流在客戶端表現(xiàn)為一個(gè)下載文件對(duì)話框,其代碼如下所示:

  代碼 10 業(yè)務(wù)接口實(shí)現(xiàn)類之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行,設(shè)置HTTP響應(yīng)頭,將響應(yīng)類型設(shè)置為application/x-msdownload MIME類型,則響應(yīng)流在IE中將彈出一個(gè)文件下載的對(duì)話框,如圖 4所示。IE所支持的MIME類型多達(dá)26種,您可以通過(guò)這個(gè)網(wǎng)址查看其他的MIME類型:

http://msdn.microsoft.com/workshop/networking/moniker/overview/appendix_a.asp。

  如果下載文件的文件名含有中文字符,如果不對(duì)其進(jìn)行硬編碼,如第18行所示,客戶文件下載對(duì)話框中出現(xiàn)的文件名將會(huì)發(fā)生亂碼。
第19行代碼獲得response的輸出流,作為FileServie write(OutputStream os,String fileId)的入?yún)ⅲ@樣文件的內(nèi)容將寫(xiě)到response的輸出流中。

  3、web.xml文件的配置

  Spring容器在何時(shí)啟動(dòng)呢?我可以在Web容器初始化來(lái)執(zhí)行啟動(dòng)Spring容器的操作,Spring提供了兩種方式啟動(dòng)的方法:

  ·通過(guò)org.springframework.web.context .ContextLoaderListener容器監(jiān)聽(tīng)器,在Web容器初始化時(shí)觸發(fā)初始化Spring容器,在web.xml中通過(guò)<listener></listener>對(duì)其進(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也提供了容器監(jiān)聽(tīng)器和自動(dòng)啟動(dòng)Servlet兩種方式對(duì)log4J引擎進(jìn)行初始化:

  ·org.springframework.web.util .Log4jConfigListener

  ·org.springframework.web.util.Log4jConfigServlet

  下面我們來(lái)說(shuō)明如何配置web.xml啟動(dòng)Spring容器:

  代碼 11 web.xml中對(duì)應(yīng)Spring的配置內(nèi)容

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容器參數(shù)指定,如果有多個(gè)Spring配置文件,則用逗號(hào)隔開(kāi),如:

/WEB-INF/applicationContext_1.xml, /WEB-INF/applicationContext_1.xm2

  由于在啟動(dòng)ContextLoaderServlet之前,必須事先初始化Log4J的引擎,所以Log4jConfigServlet必須在ContextLoaderServlet之前啟動(dòng),這通過(guò)<load-on-startup>來(lái)指定它們啟動(dòng)的先后順序。

  亂碼是開(kāi)發(fā)Web應(yīng)用程序一個(gè)比較老套又常見(jiàn)問(wèn)題,由于不同Web應(yīng)用服務(wù)器的默認(rèn)編碼是不一樣的,為了方便Web應(yīng)用在不同的Web應(yīng)用服務(wù)器上移植,最好的做法是Web程序自身來(lái)處理編碼轉(zhuǎn)換的工作。經(jīng)典的作法是在web.xml中配置一個(gè)編碼轉(zhuǎn)換過(guò)濾器,Spring就提供了一個(gè)編碼過(guò)濾器類CharacterEncodingFilter,下面,我們?yōu)閼?yīng)用配置上這個(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ò)濾器類是org.springframework.web.filter.CharacterEncodingFilter,通過(guò)encoding參數(shù)指定編碼轉(zhuǎn)換類型為GBK,<filter-mapping>的配置使該過(guò)濾器截獲所有的請(qǐng)示。

  Struts的框架也需要在web.xml中配置,想必讀者朋友對(duì)Struts的配置都很熟悉,故在此不再提及,請(qǐng)參見(jiàn)本文所提供的源碼。

  總結(jié)

  本文通過(guò)一個(gè)文件上傳下載的Web應(yīng)用,講解了如何構(gòu)建基于SSH的Web應(yīng)用,通過(guò)Struts和FormFile,Spring的LobHandler以及Spring為HibernateBlob處理所提供的用戶類BlobByteArrayType ,實(shí)現(xiàn)上傳和下載文件的功能僅需要廖廖數(shù)行的代碼即告完成。讀者只需對(duì)程序作稍許的調(diào)整,即可處理Clob字段:

  ·領(lǐng)域?qū)ο髮?duì)應(yīng)Clob字段的屬性聲明為String類型;

  ·映射文件對(duì)應(yīng)Clob字段的屬性聲明為org.springframework.orm.hibernate3.support.ClobStringType類型。

  本文通過(guò)SSH對(duì)文件上傳下載簡(jiǎn)捷完美的實(shí)現(xiàn)得以管中窺豹了解SSH強(qiáng)強(qiáng)聯(lián)合構(gòu)建Web應(yīng)用的強(qiáng)大優(yōu)勢(shì)。在行文中,還穿插了一些分層的設(shè)計(jì)經(jīng)驗(yàn),配置技巧和Spring所提供的方便類,相信這些知識(shí)對(duì)您的開(kāi)發(fā)都有所裨益

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    国产内射在线激情一区| 老司机精品视频在线免费看| 亚洲精品深夜福利视频| 国产欧美性成人精品午夜| 亚洲欧美中文日韩综合| 麻豆欧美精品国产综合久久| 91日韩欧美在线视频| 日韩一区二区三区18| 亚洲一级二级三级精品| 国产欧美一区二区三区精品视| 丰满少妇被猛烈插入在线观看| 1024你懂的在线视频| 国产伦精品一区二区三区高清版| 国产成人精品视频一区二区三区| 日本中文在线不卡视频| 老富婆找帅哥按摩抠逼视频| 扒开腿狂躁女人爽出白浆av| 日韩欧美好看的剧情片免费| 中文字幕av诱惑一区二区| 99精品国产一区二区青青| 熟妇久久人妻中文字幕| 国产欧美韩日一区二区三区| 99久久人妻精品免费一区| 色婷婷亚洲精品综合网| 亚洲精品熟女国产多毛| 亚洲天堂精品在线视频| 亚洲欧美日韩中文字幕二欧美| 男生和女生哪个更好色| 东京不热免费观看日本| 亚洲欧美黑人一区二区| 国产日韩精品欧美综合区| 日本一级特黄大片国产| 成人免费在线视频大香蕉 | 欧美日韩亚洲综合国产人| 一区二区三区日韩中文| 日本成人三级在线播放| 日本精品最新字幕视频播放 | 国产对白老熟女正在播放| 欧美中文字幕一区在线| 麻豆视传媒短视频在线看| 亚洲欧美日本成人在线|