http://zxmzfbdc.javaeye.com/blog/258946
【原創(chuàng)】Spring、Hibernate、Struts1整合的方式
完整的例子源碼已經(jīng)作為附件上傳,為了減小體積,沒有包含用到的jar文件,請讀者自己下載,不便處還請見諒。
9.1 模型概述
正如我們所知,Struts是一個單純的web框架,因此對于其他方面的操作無能為力,例如對數(shù)據(jù)庫的操作。此外它也不能管理對象之間的依賴關(guān)系,因此需要和持久化框架和IoC框架結(jié)合,才能構(gòu)建一個完整健壯的系統(tǒng)。本節(jié)分析Struts+Spring+Hibernate結(jié)合的必要性。
9.1.1 三種模型結(jié)合的必要性
對于從事web開發(fā)的人來說,使用Struts可以大大簡化開發(fā)過程,對于直接使用Servlet+JSP來說,它提供了優(yōu)秀的封裝機制,使得用戶可以嚴(yán)格按照MVC架構(gòu)來開發(fā)應(yīng)用。想想我們沒有使用web框架之前,是怎樣的混亂狀態(tài),需要自己將數(shù)據(jù)從請求中取出來,然后組裝起來,在數(shù)據(jù)很多的情況下,這簡直是一個噩夢。驗證代碼散落在各處,維護它們同樣是一件累人的活。為了處理不同的請求,可能需要寫很多的Servlet,且不說容器的負擔(dān)問題,單單是web.xml文件就變得分外的臃腫,最后可能變成一個難以維護的龐然大物。這是我在實際開發(fā)中遇到過的真實情況。盡管使用Struts,在早期也會遇到配置文件(struts-config.xml)膨脹的問題,但后來的Struts通過模塊化很好的解決了這個問題。
關(guān)于驗證,需要多說一些,事實上,Struts的驗證并不像后來的一些web框架而言,如Webwork、Spring MVC,是可插拔的,相反,它被包含在ActionForm中,這是一種耦合性較強的設(shè)計。form負擔(dān)了的太多的責(zé)任,使得重用它變得困難,比如在業(yè)務(wù)層和持久層,另外,form是Struts框架的一部分,出于降低耦合性的考慮,也不能在業(yè)務(wù)層和持久層中出現(xiàn)。它之后的一些框架都很好的解決了這個問題:使用一個普通的pojo,僅僅用于封裝數(shù)據(jù),不包含任何的邏輯。但是,Struts是一個可擴展的框架,它的任何一部份都是可以替換的。我們可以通過它提供的插件機制,來提供自己的功能,例如通常情況下,Spring和Struts的整合的一種方式,就是通過插件實現(xiàn)的。通過插件,我們也可以提供可插拔的驗證功能。
Struts是一個經(jīng)歷了無數(shù)項目考驗的框架,有著為數(shù)最為龐大的社群,有著成熟的經(jīng)驗可供參考,所以在項目中使用Struts風(fēng)險很小。而且,Struts比較容易上手,因此學(xué)習(xí)成本也相對較小。
正如我們前面所說的,Struts是一個純粹的web框架,不涉及業(yè)務(wù)層和持久層的處理,因此,對于其他層需要借助于其他的框架。比如,在持久層使用 Hibernate。
數(shù)據(jù)庫操作是一個很復(fù)雜的問題,盡管在Java中,JDBC進行了很好的抽象,但是用戶需要直接操作SQL。這里也存在一個數(shù)據(jù)組裝的問題,實際開發(fā)中直接從ResultSet中取出數(shù)據(jù),然后放入一個model中,是很常見的方式。冗長的set操作令人厭煩。我們在實際開發(fā)中,都是將數(shù)據(jù)存放到model中,然后操作model,在這個意義上說,model和數(shù)據(jù)庫之間有著某種內(nèi)在的關(guān)系。怎樣映射這種關(guān)系,正是ORM所要做的。ORM不僅僅要映射對象的屬性和數(shù)據(jù)庫表的字段間的關(guān)系,還要把表之間的關(guān)系映射為對象之間的關(guān)系,從而把對數(shù)據(jù)庫表的操作,完全轉(zhuǎn)為對對象的操作。這種處理方式,更為程序員所熟悉,而且通過工具,我們也很容易保持表關(guān)系和對象關(guān)系的同步。Hibernate正是這樣一個強大的ORM工具,除了通常的O-R映射,還提供了其他的廣受歡迎的功能,比如臟數(shù)據(jù)檢查、延遲加載等。這些,我們在前面的章節(jié)中,已經(jīng)做過介紹。Hibernate在實際開發(fā)中獲得了廣泛的應(yīng)用,成為事實上的標(biāo)準(zhǔn)。所以,我們在開發(fā)中,持久層實現(xiàn),可以選擇Hibernate。
Struts同樣也缺少IoC和AOP功能,因此存在一定的耦合,例如,在Action調(diào)用業(yè)務(wù)對象的時候,需要new一個實例,這是很常見的情況。當(dāng)然,我們可以使用工廠模式消除這種情況,但是又需要引入工廠類。使用Spring可以有效地消除這種耦合。我們在上一章中,介紹的Spring和Struts整合的方式中,可我們在Action中通過Spring的ApplicationContext獲取對象,但是這樣Action中又嵌入了Spring的代碼。整合的第三種方式,通過Struts的plug-in機制,將Action交由Spring托管,這樣,我們就可以在Action中使用依賴注入,完全消除了這種耦合。除此之外,我們還可以使用Spring的AOP機制。通過整合,我們可以充分利用各個框架的優(yōu)勢,從而達到構(gòu)建完美的應(yīng)用的目標(biāo)。
9.2 模型特征
我們分析了三種框架結(jié)合的必要性,現(xiàn)在我們分析一下使用三個框架的模型特性。
9.2.1 模型的構(gòu)成特點
在Spring+Hibernate+Struts模型中,Hibernate負責(zé)持久化操作,Struts負責(zé)web操作,兩者之間完全獨立。Spring在這個模型中的地位很特殊,它提供對對象的管理,而不管這個對象是處在哪一層,完成哪些任務(wù)。Spring為用戶設(shè)置對象間的依賴關(guān)系,并且通過AOP提供了統(tǒng)一的方式,處理橫向的需求,這些需求通常很難使用OO實現(xiàn),比如權(quán)限驗證。這樣,Spring就涉及到廣泛的領(lǐng)域,而不管它處在哪一層。或者說,Spring是一個更為基礎(chǔ)的框架,而不像Struts和Hibernate專注于各自的領(lǐng)域。
現(xiàn)在已經(jīng)清楚了Spring、Hibernate、Struts的作用,現(xiàn)在從分層的角度,分析一下這個模型。
業(yè)務(wù)層,處理實際的邏輯操作,這一部分是由用戶獨立實現(xiàn)的,擁有最大的靈活性。有些時候,持久層和業(yè)務(wù)層是混合在一起的,但是數(shù)據(jù)庫一旦設(shè)計完成,就很少修改,所有擁有相當(dāng)?shù)姆€(wěn)定性,所以持久層也擁有相當(dāng)?shù)姆€(wěn)定性,因此可以將它們獨立出來。還有一個原因,持久化操作,很多時候很復(fù)雜,特別是涉及到事物的時候,因此,維護一個穩(wěn)定的持久層,有利于分工,也減少了維護的成本。Hibernate的出現(xiàn),使得持久化操作不再像以前那么復(fù)雜繁瑣,所以也有將這兩層合并的做法。我們下面給出的例子,會看到很多時候業(yè)務(wù)層僅僅是將請求委托給持久層,本身什么也不做。但我認為,維護一個獨立的業(yè)務(wù)層,還是有好處的,至少在將來切換持久化實現(xiàn)的時候,比如用JDBC替換Hibernate,業(yè)務(wù)層就不用作任何的改動,雖然這種情況很少發(fā)生。
持久層,我們可以直接使用Hibernate API,更好的方式是使用Spring的Hibernate封裝。Spring提供了對Hibernate的良好封裝,這樣我們在持久化操作中,可以使用Spring提供的封裝,簡化持久化的操作。Hibernate的Session是很昂貴的資源,因此,每個DAO(這里指持久層的類)方法,單獨使用一個Session是一種浪費,使用Spring的封裝,就可以使用統(tǒng)一的方式使用Session。通過使用Spring托管的Session,我們實現(xiàn)了OpenSessionInView模式,即在每一個請求中使用一個Session。當(dāng)然我們可以提供自己的OpenSessionInView實現(xiàn),最簡單的就是實現(xiàn)一個Filter。事實上Spring提供的其中一種實現(xiàn)就是通過這種方式實現(xiàn)的。在Spring托管的環(huán)境下,我們就可以實現(xiàn)Hibernate的聲明式管理。使用Spring封裝的一個好處就是對異常的處理,Spring可以給出很明確的異常信息,不像JDBC統(tǒng)一拋出SQLException,Hibernate拋出HibernateException。Spring還提供了異常轉(zhuǎn)換的功能,用戶可以在配置文件中聲明轉(zhuǎn)換成什么樣的異常。
web層,就是Struts的責(zé)任了,這里通常不會涉及到Hibernate和Spring,我們僅僅是由Spring管理依賴對象的注入。但是,很多時候web請求需要一些橫向的需求,比如上面提到的權(quán)限管理,一個更常見的情況是用戶登錄和注冊。當(dāng)然我們可以使用Filter實現(xiàn),也可以寫一個頂層的Action,統(tǒng)一處理這些請求,需要處理這些請求的Action繼承它即可。但是這是一種硬編碼的實現(xiàn),不能通過配置改變,因此缺少靈活性。用戶是否登錄的判斷也是一個典型的橫切應(yīng)用,因此可以通過AOP來實現(xiàn)。我們下面的例子,會給出一個使用攔截器判斷用戶是否登錄的實現(xiàn)。為了使用攔截器,我們這里給出了Spring和Struts整合的第四種方式,涉及到了SpringMVC的內(nèi)容。相對來說,SpringMVC在設(shè)計上更加倚重IoC和AOP,也提供了更為開放的方式,因此更為強大,但是卻過于復(fù)雜。但是相對于它提供的豐富特性,這些復(fù)雜性是可以克服的,因此在實際開發(fā)中,可以考慮用SpringMVC實現(xiàn)web層,即用它替換Struts。
9.2.2 實現(xiàn)模型的目的
使用Spring+Hibernate+Struts模型的目的,實際上面已經(jīng)涉及到了,這些框架的優(yōu)點是使用它們的一個重要原因。還有一些可能更重要的原因,總結(jié)如下:
去耦合 這是一個老生常談的話題了。去耦合是如此的重要,以至于人們要將這方面的經(jīng)驗總結(jié)成設(shè)計模式。Spring通過IoC,實現(xiàn)了去耦合,這樣用戶可以隨時替換現(xiàn)在的實現(xiàn),而不用修改源碼。這正是軟件開發(fā)中開-閉原則的要求。Spring倡導(dǎo)面向接口編程,這是最為推崇的最佳實踐之一,繼承僅僅作為內(nèi)部實現(xiàn)的一個手段。面向接口編程,不僅僅是提供了更好的抽象,實際上也更有利于去耦合。
擴展的需要 Struts、Spring甚至Hibernate都提供了良好的擴展功能,Struts通過插件機制,可以很方便的添加功能。Spring對Bean的管理也提供了擴展,例如用戶可以提供自己的作用域。Hibernate用戶可以定制自己的持久化策略。
維護的需要 這些框架都很容易維護,它們都很好的實現(xiàn)了開-閉原則,因此需要替換功能的時候,只需簡單的修改配置文件。它們提供的一些公用功能,也能通過配置的方式提供,比如Spring的聲明式事務(wù)。因此維護成本大大降低。
還有一個原因,由于它們都有龐大的社區(qū)支持,因此尋找技術(shù)支持很容易。由于它們很流行,因此招一個開發(fā)人員也很容易。
9.3 Hibernate+Spring+Struts模型應(yīng)用分析
通過上面的學(xué)習(xí),我們對三者結(jié)合的模型有了一個概念性的理解,現(xiàn)在我們通過一個具體的例子,演示怎樣將它們整合在一起。
9.3.1 應(yīng)用事例
我們考慮一個購物車的例子,相信讀者或多或少都接觸過這方面的例子。為了簡單起見,我們不考慮庫存的問題,商品的信息完全由用戶填寫,包括商品名稱、商品介紹、商品價格、購買數(shù)量。填寫完商品信息,會被暫時放入購物車內(nèi),每個購物車抽象成一個訂單。當(dāng)用戶確認購買的商品后,就可以提交購物車,從而這些信息被存入數(shù)據(jù)庫,購物車清空。在做這些操作之前,用戶首先需要登錄,如果用戶沒有注冊,可以選擇注冊。用戶不僅可以購買商品,還可以查看已經(jīng)提交的訂單的詳細信息。一個用戶可以有多個訂單,一個訂單只能屬于一個用戶。同理,一個訂單可以包含多個商品,而一個商品只能屬于一個訂單。用戶的姓名是唯一的,因為這是確認用戶的唯一憑證。這就是全部的需求。
9.3.2 事例分析
好,現(xiàn)在我們開始構(gòu)建整個應(yīng)用。應(yīng)用用到的類庫如圖9-1所示:
圖9-1 應(yīng)用用到的jar包
首先,我們需要建立所需的數(shù)據(jù)庫表,我們用到的表有三個,每個表都有一個用于標(biāo)識的id,這三個表分別是:
•tb_user——用戶表,用來存放用戶信息,包括用戶名和密碼;
•tb_order——訂單表,用來存放訂單信息,包括訂單描述、生成時間、用戶id;
•tb_item——商品表,用來存放商品信息,包括商品名稱、商品描述、商品價格、購買數(shù)量、定單id。
我用的數(shù)據(jù)庫是Oracle9i,生成三個表用到的SQL如下:
Sql代碼
1./*商品表*/
2.create table tb_item (
3. id number(10,0) not null,
4. orderid number(10,0) not null, /*訂單id*/
5. name varchar2(20 char) not null, /*商品名稱*/
6. description varchar2(800 char), /*商品描述*/
7. price number(18,2), /*商品價格*/
8. count number(10,0), /*購買數(shù)量*/
9. primary key (id)
10.)
11.
12./*訂單表*/
13.create table tb_order (
14. id number(10,0) not null,
15. userid number(10,0) not null, /*用戶id*/
16. description varchar2(800 char), /*訂單描述*/
17. createTime timestamp, /*生成時間*/
18. primary key (id)
19.)
20.
21./*用戶表*/
22.create table tb_user (
23. id number(10,0) not null,
24. name varchar2(20 char) not null, /*用戶名*/
25. password varchar2(20 char) not null, /*密碼*/
26. primary key (id)
27.)
28.
29.alter table tb_item
30. add constraint FKA4F9FA443A36B48F
31. foreign key (orderid)
32. references tb_order
33.
34.alter table tb_order
35. add constraint FKFA98EE3D6705FE99
36. foreign key (userid)
37. references zxm.tb_user
38.
39.create sequence hibernate_sequence
/*商品表*/
create table tb_item (
id number(10,0) not null,
orderid number(10,0) not null, /*訂單id*/
name varchar2(20 char) not null, /*商品名稱*/
description varchar2(800 char), /*商品描述*/
price number(18,2), /*商品價格*/
count number(10,0), /*購買數(shù)量*/
primary key (id)
)
/*訂單表*/
create table tb_order (
id number(10,0) not null,
userid number(10,0) not null, /*用戶id*/
description varchar2(800 char), /*訂單描述*/
createTime timestamp, /*生成時間*/
primary key (id)
)
/*用戶表*/
create table tb_user (
id number(10,0) not null,
name varchar2(20 char) not null, /*用戶名*/
password varchar2(20 char) not null, /*密碼*/
primary key (id)
)
alter table tb_item
add constraint FKA4F9FA443A36B48F
foreign key (orderid)
references tb_order
alter table tb_order
add constraint FKFA98EE3D6705FE99
foreign key (userid)
references zxm.tb_user
create sequence hibernate_sequence
接下來,我們生成pojo和相關(guān)的hibernate映射文件,這可以通過hibernate-tools實現(xiàn),也可以通過Middlegen實現(xiàn)。pojo對應(yīng)的名字分別是User、Order和Item,映射文件和pojo同名。注意包名的命名,通過包,我們可以將類進行分組,便于管理。我們在開發(fā)中,應(yīng)該重視包的管理,至少在一個應(yīng)用中,應(yīng)該有統(tǒng)一的命名規(guī)范。這里使用的命名習(xí)慣和放置的文件是這樣的:
•cn.zxm.order.pojo——pojo和相關(guān)的映射文件;
•cn.zxm.order.dao——持久化接口;
•cn.zxm.order.dao.impl——持久化接口的實現(xiàn)類;
•cn.zxm.order.service——業(yè)務(wù)接口;
•cn.zxm.order.service.impl——業(yè)務(wù)接口的實現(xiàn)類;
•cn.zxm.order.form——應(yīng)用用到的ActionForm;
•cn.zxm.order.action——應(yīng)用用到的Action;
•cn.zxm.order.util——應(yīng)用用到的工具類。
這里我們只列出pojo的源碼,省略映射文件,其他的接口和類會在后面列出:
User.java:
Java代碼
1.public class User implements java.io.Serializable {
2. private Integer id;
3. private String name;
4. private String password;
5. private Set<Order> orders = new HashSet<Order>(0);
6.
7.……對應(yīng)的setter和getter方法……
8.}
public class User implements java.io.Serializable {
private Integer id;
private String name;
private String password;
private Set<Order> orders = new HashSet<Order>(0);
……對應(yīng)的setter和getter方法……
}
Order.java:
Java代碼
1.public class Order implements java.io.Serializable {
2. private Integer id;
3. private User user;
4. private String description;
5. private Date createTime;
6. private Set<Item> items = new HashSet<Item>(0);
7.
8.……對應(yīng)的setter和getter方法……
9.}
public class Order implements java.io.Serializable {
private Integer id;
private User user;
private String description;
private Date createTime;
private Set<Item> items = new HashSet<Item>(0);
……對應(yīng)的setter和getter方法……
}
Item.java:
Java代碼
1.public class Item implements java.io.Serializable {
2. private Integer id;
3. private Order order;
4. private String name;
5. private String description;
6. private BigDecimal price;
7. private Integer count;
8.
9.……對應(yīng)的setter和getter方法……
10.}
public class Item implements java.io.Serializable {
private Integer id;
private Order order;
private String name;
private String description;
private BigDecimal price;
private Integer count;
……對應(yīng)的setter和getter方法……
}
第三步,構(gòu)建持久化接口和它們的實現(xiàn)。開始構(gòu)建時,我們可能不知道需要哪些方法,沒有關(guān)系,我們從最小需求來考慮,只定義肯定用到的方法即可,至于應(yīng)用中需要新的方法,可以通過重構(gòu)添加,現(xiàn)在的IDE很多都提供了很好的重構(gòu)功能。那么重構(gòu)時是先從接口開始,還是從實現(xiàn)開始?一般來說,從接口開始保證實現(xiàn)必須包含這個方法,但是從類開始,我們可能會忘記將方法上推到接口。不過,這個問題不大,因為我們實際開發(fā)時用到的是接口,所以,這個不是問題。我的建議是從實現(xiàn)開始重構(gòu)。
針對用戶的操作,我們需要根據(jù)主鍵獲取用戶信息。由于要處理用戶注冊,所以需要保存用戶信息。要處理登錄,所以需要根據(jù)用戶名獲取用戶信息。這就是需要用到的三個方法,借口定義如下:
Java代碼
1.public interface UserDAO {
2. /**
3. * 保存用戶
4. * @param user
5. */
6. void save(User user);
7.
8. /**
9. * 根據(jù)用戶id獲取用戶信息
10. * @param id
11. * @return
12. */
13. User getUser(int id);
14.
15. /**
16. * 根據(jù)用戶名獲取用戶信息
17. * @param name
18. * @return
19. */
20. User getUserByName(String name);
21.}
public interface UserDAO {
/**
* 保存用戶
* @param user
*/
void save(User user);
/**
* 根據(jù)用戶id獲取用戶信息
* @param id
* @return
*/
User getUser(int id);
/**
* 根據(jù)用戶名獲取用戶信息
* @param name
* @return
*/
User getUserByName(String name);
}
對應(yīng)的實現(xiàn)我們用到了Spring的HibernateTemplate類,這個類封裝了對于Hibernate的通用操作。實現(xiàn)如下:
UserDAOImpl:
Java代碼
1.public class UserDAOImpl implements UserDAO {
2. protected HibernateTemplate hibernate;
3.
4. public void setHibernate(HibernateTemplate hibernate) {
5. this.hibernate = hibernate;
6. }
7.
8. public User getUser(int id) {
9. return (User)hibernate.load(User.class, id);
10. }
11.
12. public User getUserByName(String name) {
13. String hql = "from User where name=?";
14. User user = null;
15. //使用iterate方法,第二個參數(shù)可以是一個數(shù)組,用來給hql中的參數(shù)賦值
16. Iterator<User> iterator = hibernate.iterate(hql, name);
17. //獲取用戶信息,沒有將返回null
18. if(iterator.hasNext()){
19. user = iterator.next();
20. }
21. return user;
22. }
23.
24. public void save(User user) {
25. hibernate.saveOrUpdate(user);
26. }
27.}
public class UserDAOImpl implements UserDAO {
protected HibernateTemplate hibernate;
public void setHibernate(HibernateTemplate hibernate) {
this.hibernate = hibernate;
}
public User getUser(int id) {
return (User)hibernate.load(User.class, id);
}
public User getUserByName(String name) {
String hql = "from User where name=?";
User user = null;
//使用iterate方法,第二個參數(shù)可以是一個數(shù)組,用來給hql中的參數(shù)賦值
Iterator<User> iterator = hibernate.iterate(hql, name);
//獲取用戶信息,沒有將返回null
if(iterator.hasNext()){
user = iterator.next();
}
return user;
}
public void save(User user) {
hibernate.saveOrUpdate(user);
}
}
由于HibernateTemplate在其他的DAO實現(xiàn)中也要用到,因此我們可以把它放到一個超類中,這樣所有的DAO實現(xiàn)先就可以通過繼承這個超類,而不在需要自己聲明HibernateTemplate。超類命名為BaseDAO,代碼如下:
Java代碼
1.public class BaseDAO {
2. protected HibernateTemplate hibernate;
3.
4. public BaseDAO() {
5. super();
6. }
7.
8. public void setHibernate(HibernateTemplate hibernate) {
9. this.hibernate = hibernate;
10. }
11.}
public class BaseDAO {
protected HibernateTemplate hibernate;
public BaseDAO() {
super();
}
public void setHibernate(HibernateTemplate hibernate) {
this.hibernate = hibernate;
}
}
修改UserDAOImpl,使他繼承BaseDAO,代碼如下:
Java代碼
1.public class UserDAOImpl extends BaseDAO implements UserDAO {
2. public User getUser(int id) {
3. ……
4. }
5.
6. public User getUserByName(String name) {
7. ……
8. }
9.
10. public void save(User user) {
11. ……
12. }
13.}
public class UserDAOImpl extends BaseDAO implements UserDAO {
public User getUser(int id) {
……
}
public User getUserByName(String name) {
……
}
public void save(User user) {
……
}
}
訂單操作同樣需要保存操作,可能需要刪除操作,因為要查看訂單,顯然需要一個查詢方法,為了簡單,這里不考慮修改操作(由于Hibernate有臟數(shù)據(jù)檢查功能,實際上,通過查詢方法,我們也可以實現(xiàn)修改操作)。代碼如下:
Java代碼
1.public interface OrderDAO {
2. /**
3. * 保存訂單
4. * @param order
5. */
6. void save(Order order);
7.
8. /**
9. * 根據(jù)訂單id獲取訂單信息
10. * @param id
11. * @return
12. */
13. Order getOrder(int id);
14.
15. /**
16. * 刪除訂單
17. * @param order
18. */
19. void delete(Order order);
20.}
public interface OrderDAO {
/**
* 保存訂單
* @param order
*/
void save(Order order);
/**
* 根據(jù)訂單id獲取訂單信息
* @param id
* @return
*/
Order getOrder(int id);
/**
* 刪除訂單
* @param order
*/
void delete(Order order);
}
OrderDAO的實現(xiàn)代碼如下:
Java代碼
1.public class OrderDAOImpl extends BaseDAO implements OrderDAO {
2. public Order getOrder(int id) {
3. return (Order)hibernate.load(Order.class, id);
4. }
5.
6. public void save(Order order) {
7. hibernate.save(order);
8. }
9.
10. public void delete(Order order) {
11. hibernate.delete(order);
12. }
13.}
public class OrderDAOImpl extends BaseDAO implements OrderDAO {
public Order getOrder(int id) {
return (Order)hibernate.load(Order.class, id);
}
public void save(Order order) {
hibernate.save(order);
}
public void delete(Order order) {
hibernate.delete(order);
}
}
商品的操作,我們是直接放在session中,只有提交訂單時才會涉及到保存操作,這里我們使用Hibernate的級聯(lián)保存,因此不涉及單獨操作Item的情況,ItemDAO在這個事例中不需要,這里省略。讀者可以自己完善這個應(yīng)用,比如刪除訂單和查看單個商品信息等。
持久層邏輯完成了,現(xiàn)在我們考慮一下邏輯層的實現(xiàn)。
用戶操作和DAO的操作相似,只不過這里只有業(yè)務(wù)處理,不會涉及數(shù)據(jù)存取的操作,代碼如下:
Java代碼
1.public interface UserService {
2. /**
3. * 保存用戶
4. * @param user
5. */
6. void save(User user);
7.
8. /**
9. * 根據(jù)用戶id獲取用戶信息
10. * @param id
11. * @return
12. */
13. User getUser(int id);
14.
15. /**
16. * 根據(jù)傳入的用戶數(shù)據(jù)獲取完整的用戶信息
17. * @param user
18. * @return
19. */
20. User getUser(User user);
21.}
public interface UserService {
/**
* 保存用戶
* @param user
*/
void save(User user);
/**
* 根據(jù)用戶id獲取用戶信息
* @param id
* @return
*/
User getUser(int id);
/**
* 根據(jù)傳入的用戶數(shù)據(jù)獲取完整的用戶信息
* @param user
* @return
*/
User getUser(User user);
}
對應(yīng)的實現(xiàn)如下:
Java代碼
1.public class UserServiceImpl implements UserService {
2. private final Log log = LogFactory.getLog(UserServiceImpl.class);
3. private UserDAO userDAO;
4.
5. public void setUserDAO(UserDAO userDAO) {
6. this.userDAO = userDAO;
7. }
8.
9. public User getUser(User user) {
10. log.debug("驗證用戶");
11. return userDAO.getUserByName(user.getName());
12. }
13.
14. public User getUser(int id) {
15. return userDAO.getUser(id);
16. }
17.
18. /**
19. * 這里使用了事務(wù)標(biāo)注,這樣可以使用聲明式事務(wù)
20. * 如果用戶名為空,將不會保存
21. */
22. @Transactional
23. public void save(User user) {
24. log.debug("保存用戶");
25. if(userDAO.getUserByName(user.getName())!=null)
26. return;
27. userDAO.save(user);
28. }
29.}
public class UserServiceImpl implements UserService {
private final Log log = LogFactory.getLog(UserServiceImpl.class);
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
public User getUser(User user) {
log.debug("驗證用戶");
return userDAO.getUserByName(user.getName());
}
public User getUser(int id) {
return userDAO.getUser(id);
}
/**
* 這里使用了事務(wù)標(biāo)注,這樣可以使用聲明式事務(wù)
* 如果用戶名為空,將不會保存
*/
@Transactional
public void save(User user) {
log.debug("保存用戶");
if(userDAO.getUserByName(user.getName())!=null)
return;
userDAO.save(user);
}
}
訂單的處理,我們需要一個獲取訂單信息的方法,還需要一個保存訂單的方法,接口代碼如下:
Java代碼
1.public interface OrderService {
2. /**
3. * 獲取訂單詳細信息
4. * @param id
5. * @return
6. */
7. Order getOrder(int id);
8.
9. /**
10. * 保存訂單
11. * @param order
12. */
13. void save(Order order);
14.}
public interface OrderService {
/**
* 獲取訂單詳細信息
* @param id
* @return
*/
Order getOrder(int id);
/**
* 保存訂單
* @param order
*/
void save(Order order);
}
對應(yīng)的實現(xiàn)如下:
Java代碼
1.public class OrderServiceImpl implements OrderService {
2. private OrderDAO orderDAO;
3.
4. public void setOrderDAO(OrderDAO orderDAO) {
5. this.orderDAO = orderDAO;
6. }
7.
8. public Order getOrder(int id) {
9. return orderDAO.getOrder(id);
10. }
11.
12. /**
13. * 保存訂單,我們在這里給訂單設(shè)置了生成時間
14. */
15. @Transactional
16. public void save(Order order) {
17. order.setCreateTime(new Timestamp(System.currentTimeMillis()));
18. orderDAO.save(order);
19. }
20.}
public class OrderServiceImpl implements OrderService {
private OrderDAO orderDAO;
public void setOrderDAO(OrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
public Order getOrder(int id) {
return orderDAO.getOrder(id);
}
/**
* 保存訂單,我們在這里給訂單設(shè)置了生成時間
*/
@Transactional
public void save(Order order) {
order.setCreateTime(new Timestamp(System.currentTimeMillis()));
orderDAO.save(order);
}
}
我們的購物車是通過HttpSession實現(xiàn)的,因此我們在這里不是定義一個Service,還是通過一個工具類,實現(xiàn)操作購物車的功能。這里購物車中保存的是一個尚未提交的訂單。由于需要向訂單內(nèi)保存商品,我們在Order中增加一個方法,代碼如下:
Java代碼
1. /**
2. * 添加商品,注意我們手動為商品添加了訂單關(guān)聯(lián),
3. * 這是為了使用Hibernate的級聯(lián)保存
4. * @param item
5. */
6.public void addItem(Item item){
7. item.setOrder(this);
8. items.add(item);
9.}
/**
* 添加商品,注意我們手動為商品添加了訂單關(guān)聯(lián),
* 這是為了使用Hibernate的級聯(lián)保存
* @param item
*/
public void addItem(Item item){
item.setOrder(this);
items.add(item);
}
工具類的代碼如下:
Java代碼
1.public final class OrderUtils {
2. /**
3. * 將商品放入購物車
4. * @param request
5. * @param item
6. */
7. public static void saveItem(HttpServletRequest request, Item item) {
8. HttpSession session = request.getSession();
9. User user = (User)session.getAttribute("user");
10. Order order = getOrder(request);
11. order.addItem(item);
12. session.setAttribute(user.getName(), order);
13. }
14.
15. /**
16. * 獲取購物車信息
17. * @param request
18. * @return
19. */
20.
21. public static Order getOrder(HttpServletRequest request) {
22. HttpSession session = request.getSession();
23. User user = (User)session.getAttribute("user");
24. Order order = (Order)session.getAttribute(user.getName());
25. //如果session沒有Order對象,那么就是實例化一個
26. if(order==null) {
27. order = new Order();
28. }
29. return order;
30. }
31.
32. /**
33. * 清空購物車
34. * @param request
35. */
36. public static void clearCart(HttpServletRequest request){
37. HttpSession session = request.getSession();
38. User user = (User)session.getAttribute("user");
39. session.removeAttribute(user.getName());
40. }
41.}
public final class OrderUtils {
/**
* 將商品放入購物車
* @param request
* @param item
*/
public static void saveItem(HttpServletRequest request, Item item) {
HttpSession session = request.getSession();
User user = (User)session.getAttribute("user");
Order order = getOrder(request);
order.addItem(item);
session.setAttribute(user.getName(), order);
}
/**
* 獲取購物車信息
* @param request
* @return
*/
public static Order getOrder(HttpServletRequest request) {
HttpSession session = request.getSession();
User user = (User)session.getAttribute("user");
Order order = (Order)session.getAttribute(user.getName());
//如果session沒有Order對象,那么就是實例化一個
if(order==null) {
order = new Order();
}
return order;
}
/**
* 清空購物車
* @param request
*/
public static void clearCart(HttpServletRequest request){
HttpSession session = request.getSession();
User user = (User)session.getAttribute("user");
session.removeAttribute(user.getName());
}
}
現(xiàn)在我們需要將這些類配置在Spring配置文件中,需要注意的是我們這里使用了聲明式事務(wù),因此需要引入對應(yīng)的schema,引入的方式在上一章已經(jīng)介紹,這里不再重復(fù)。配置文件的代碼如下:
Xml代碼
1.<beans default-autowire="byName">
2. <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
3. <property name="jndiName" value="java:/comp/env/jdbc/order"/>
4. </bean>
5. <bean id="transactionManager"
6.class="org.springframework.orm.hibernate3.HibernateTransactionManager">
7. <property name="dataSource" ref="dataSource"/>
8. <property name="sessionFactory" ref="sessionFactory"/>
9. </bean>
10. <tx:annotation-driven />
11. <bean id="sessionFactory"
12.class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
13. <property name="dataSource" ref="dataSource"/>
14. <property name="mappingResources">
15. <list>
16. <value>cn/zxm/order/pojo/User.hbm.xml</value>
17. <value>cn/zxm/order/pojo/Order.hbm.xml</value>
18. <value>cn/zxm/order/pojo/Item.hbm.xml</value>
19. </list>
20. </property>
21. <property name="hibernateProperties">
22. <props>
23. <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>
24. <prop key="hibernate.show_sql">false</prop>
25. <prop key="hibernate.format_sql">true</prop>
26. </props>
27. </property>
28. </bean>
29. <bean id="hibernate" class="org.springframework.orm.hibernate3.HibernateTemplate"/>
30. <bean id="baseDAO" class="cn.zxm.order.dao.impl.BaseDAO" abstract="true"/>
31. <bean id="user" class="cn.zxm.order.pojo.User"/>
32. <bean id="userDAO" class="cn.zxm.order.dao.impl.UserDAOImpl" parent="baseDAO"/>
33. <bean id="orderDAO" class="cn.zxm.order.dao.impl.OrderDAOImpl" parent="baseDAO"/>
34. <bean id="itemDAO" class="cn.zxm.order.dao.impl.ItemDAOImpl" parent="baseDAO"/>
35. <bean id="userService" class="cn.zxm.order.service.impl.UserServiceImpl"/>
36. <bean id="orderService" class="cn.zxm.order.service.impl.OrderServiceImpl"/>
37. <bean id="itemService" class="cn.zxm.order.service.impl.ItemServiceImpl"/>
38.</beans>
<beans default-autowire="byName">
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:/comp/env/jdbc/order"/>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="dataSource" ref="dataSource"/>
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>cn/zxm/order/pojo/User.hbm.xml</value>
<value>cn/zxm/order/pojo/Order.hbm.xml</value>
<value>cn/zxm/order/pojo/Item.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
<bean id="hibernate" class="org.springframework.orm.hibernate3.HibernateTemplate"/>
<bean id="baseDAO" class="cn.zxm.order.dao.impl.BaseDAO" abstract="true"/>
<bean id="user" class="cn.zxm.order.pojo.User"/>
<bean id="userDAO" class="cn.zxm.order.dao.impl.UserDAOImpl" parent="baseDAO"/>
<bean id="orderDAO" class="cn.zxm.order.dao.impl.OrderDAOImpl" parent="baseDAO"/>
<bean id="itemDAO" class="cn.zxm.order.dao.impl.ItemDAOImpl" parent="baseDAO"/>
<bean id="userService" class="cn.zxm.order.service.impl.UserServiceImpl"/>
<bean id="orderService" class="cn.zxm.order.service.impl.OrderServiceImpl"/>
<bean id="itemService" class="cn.zxm.order.service.impl.ItemServiceImpl"/>
</beans>
這里需要說明的是,為了簡化配置,我們使用了自動裝載。Hibernate所需要的信息完全在這里配置,從而取代了hibernate.cfg.xml的地位。事務(wù)管理器用的是專門為Hibernate3設(shè)計的,數(shù)據(jù)源是通過JNDI讀取的,這些都是需要特別注意的。
現(xiàn)在可以開發(fā)web層了。由于Action和Form依賴于頁面,所以我們先考慮構(gòu)建頁面,所有的頁面位于應(yīng)用的order目錄下。根據(jù)需求,首先需要一個登錄頁,只需要用戶名和密碼輸入框。代碼如下(這里只列出和輸入相關(guān)部分的代碼,其它部分省略):
Html代碼
1. <html:form action="/loginAction.do" method="get">
2. <table width="400" border="0" align="center" cellpadding="10" cellspacing="1" class="table-bgcolor">
3. <tr class="table-body">
4. <td class="table-head">賬號</td><td><html:text property="name" style="width:98%;"/></td>
5. </tr>
6. <tr class="table-body">
7. <td class="table-head">密碼</td><td><html:password property="password" style="width:98%;"/></td>
8. </tr>
9. <tr class="table-body"><td></td><td><input type="submit" class="button" value="登錄"/></td></tr>
10. </table>
11.</html:form>
<html:form action="/loginAction.do" method="get">
<table width="400" border="0" align="center" cellpadding="10" cellspacing="1" class="table-bgcolor">
<tr class="table-body">
<td class="table-head">賬號</td><td><html:text property="name" style="width:98%;"/></td>
</tr>
<tr class="table-body">
<td class="table-head">密碼</td><td><html:password property="password" style="width:98%;"/></td>
</tr>
<tr class="table-body"><td></td><td><input type="submit" class="button" value="登錄"/></td></tr>
</table>
</html:form>
這里使用了Struts標(biāo)簽,使用它們時,需要確保在頁面頭部正確引入。注意在下面的form類中,屬性名和這里的名稱要一致。相關(guān)的form類代碼如下:
Java代碼
1.public class UserForm extends ActionForm {
2. private String name;
3. private String password;
4. private User user;
5.
6. //setter和getter方法
7.}
public class UserForm extends ActionForm {
private String name;
private String password;
private User user;
//setter和getter方法
}
然后寫一個Action用來處理登陸,代碼如下:
Java代碼
1.public class LoginAction extends Action {
2. private final Log log = LogFactory.getLog(LoginAction.class);
3. private UserService userService;
4. private User user;
5.
6. public void setUserService(UserService userService) {
7. this.userService = userService;
8. }
9.
10. public void setUser(User user) {
11. this.user = user;
12. }
13.
14. /**
15.@see
16. org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
17. */
18. @Override
19. public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
20. UserForm uf = (UserForm)form;
21. //克隆一份User對象,以此保證登錄信息的獨立性
22. User user2 = (User)BeanUtils.cloneBean(user);
23. //將用戶發(fā)來的數(shù)據(jù)放入User對象中
24. copyProperties(user2, uf);
25. //重新初始化表單
26. uf.reset(mapping, request);
27. //查詢用戶信息
28. User u = userService.getUser(user2);
29. //如果用戶不存在或者輸入信息不正確,就返回指定的頁面
30. if(u==null || !u.getPassword().equals(user2.getPassword()))
31. return mapping.findForward("fail");
32. user2.setId(u.getId());
33. user2.setPassword(u.getPassword());
34. //將查詢獲得的User對象放入form中,這樣頁面可以使用Hibernate的延遲加載
35. uf.setUser(u);
36. //將用戶信息存入session
37. request.getSession().setAttribute("user", user2);
38. //登錄成功,返回指定頁面
39. return mapping.findForward("welcome");
40. }
41.}
public class LoginAction extends Action {
private final Log log = LogFactory.getLog(LoginAction.class);
private UserService userService;
private User user;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setUser(User user) {
this.user = user;
}
/**
@see
org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
UserForm uf = (UserForm)form;
//克隆一份User對象,以此保證登錄信息的獨立性
User user2 = (User)BeanUtils.cloneBean(user);
//將用戶發(fā)來的數(shù)據(jù)放入User對象中
copyProperties(user2, uf);
//重新初始化表單
uf.reset(mapping, request);
//查詢用戶信息
User u = userService.getUser(user2);
//如果用戶不存在或者輸入信息不正確,就返回指定的頁面
if(u==null || !u.getPassword().equals(user2.getPassword()))
return mapping.findForward("fail");
user2.setId(u.getId());
user2.setPassword(u.getPassword());
//將查詢獲得的User對象放入form中,這樣頁面可以使用Hibernate的延遲加載
uf.setUser(u);
//將用戶信息存入session
request.getSession().setAttribute("user", user2);
//登錄成功,返回指定頁面
return mapping.findForward("welcome");
}
}
針對登陸的類至此開發(fā)完成。剩下的就是配置,我們首先使用上一章講到的Spring與Struts整合的第三種方式,即使用Struts的Plug-in機制,struts-config.xml配置如下:
Xml代碼
1.<?xml version="1.0"?>
2.<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd">
3.<struts-config>
4. <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
5. <set-property property="contextConfigLocation"
6. value="/WEB-INF/classes/beans.xml" />
7. </plug-in>
8.</struts-config>
<?xml version="1.0"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd">
<struts-config>
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/classes/beans.xml" />
</plug-in>
</struts-config>
在WEB-INF目錄下新建一個文件struts-order-config.xml,這個文件用于配置我們現(xiàn)在使用的模塊。Struts的模塊是按照目錄劃分的。在加入了這個模塊以后,相應(yīng)的web.xml中,給ActionServlet加入如下的參數(shù):
Xml代碼
1.<init-param>
2. <param-name>config/order</param-name>
3. <param-value>struts-order-config.xml</param-value>
4. </init-param>
<init-param>
<param-name>config/order</param-name>
<param-value>struts-order-config.xml</param-value>
</init-param>
如果有其它的模塊加入的話,只需按照這個格式添加即可。需要注意的是 confg/ 后面的參數(shù)必須是模塊對應(yīng)的目錄名。這樣應(yīng)用訪問特定模塊的時候,Struts會自動追加模塊的名稱。比如現(xiàn)在的登錄,頁面端表單中action對應(yīng)的值是/loginDispatcher.do,Struts會自動將它轉(zhuǎn)為/order/loginDispatcher.do。而Struts本身的配置文件在改動模塊名的時候,不需要作任何改動。這個應(yīng)用很小,沒有必要使用模塊化功能,這里使用它,只是為了演示。這樣的需求實際應(yīng)用中很常見。
Struts-order-config.xml的配置如下:
Xml代碼
1.<struts-config>
2. <form-beans>
3. <form-bean name="userForm"
4.type="cn.zxm.order.form.UserForm"/>
5. </form-beans>
6. <action-mappings>
7. <action path="/loginDispatcher" forward="/login.jsp"/>
8. <action path="/loginAction"
9.input="/login.jsp"
10.type="org.springframework.web.struts.DelegatingActionProxy" name="userForm">
11. <forward name="fail" path="/loginDispatcher.do"/>
12. <forward name="welcome" path="/orderList.jsp"/>
13. </action>
14. </action-mappings>
15.</struts-config>
<struts-config>
<form-beans>
<form-bean name="userForm"
type="cn.zxm.order.form.UserForm"/>
</form-beans>
<action-mappings>
<action path="/loginDispatcher" forward="/login.jsp"/>
<action path="/loginAction"
input="/login.jsp"
type="org.springframework.web.struts.DelegatingActionProxy" name="userForm">
<forward name="fail" path="/loginDispatcher.do"/>
<forward name="welcome" path="/orderList.jsp"/>
</action>
</action-mappings>
</struts-config>
orderList.jsp用來顯示用戶的所有訂單。path的命名,應(yīng)該有一定的約定,比如只是簡單映射頁面,使用頁面名稱+Dispatcher的形式;處理頁面的請求的路徑使用頁面名稱+Action的形式,等等,這有利于將來我們使用通配符來簡化配置。這個問題我們后面會涉及到。
在Spring配置文件中加入Action的定義,bean的名字對應(yīng)Struts中的path,只不過這個bean的名字包含了模塊名:
Xml代碼
1.<bean name="/order/loginAction"
2.class="cn.zxm.order.action.LoginAction"/>
<bean name="/order/loginAction"
class="cn.zxm.order.action.LoginAction"/>
現(xiàn)在我們可以測試登錄了,我們在數(shù)據(jù)庫中輸入一個用戶的信息,用戶名和密碼都是zxm。打開瀏覽器,在地址欄輸入
http://www.zxm.net:8090/order/order/loginDispatcher.do
,頁面如圖9-2。
圖9-2 登錄
輸入用戶名和密碼,輸入不正確的用戶名或者密碼,應(yīng)該返回登錄頁面,成功則進入訂單列表頁面,由于頁面端需要用到Hibernate的延遲加載特性,第一次訪問會拋出session closed的異常。
現(xiàn)在我們構(gòu)建訂單顯示頁,訂單頁的代碼如下:
Html代碼
1.<%@ page language="java" contentType="text/html; charset=GBK"
2. pageEncoding="GBK"%>
3.<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
4.<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
5.<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
6.<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
7.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
8.<html>
9. <head>
10. <meta http-equiv="Content-Type" content="text/html; charset=GBK">
11. <link rel="stylesheet" href="style.css" />
12. <title>用戶訂單列表</title>
13. </head>
14. <body>
15. ${userForm.user.name},您好,您共提交了${fn:length(userForm.user.orders)}個訂單<br/>
16. <table width="100%" border="0" align="center" cellpadding="10" cellspacing="1" class="table-bgcolor">
17. <tr class="table-head">
18. <th>id</th><th>訂單名稱</th><th>訂單描述</th><th>訂單生成日期</th>
19. </tr>
20. <logic:iterate id="order" property="user.orders" name="userForm">
21. <tr class="table-body">
22. <td><html:link action="/search_items.do?id=${order.id}">${order.id}</html:link></td>
23. <td>無</td>
24. <td>${order.description}</td>
25. <td>${order.createTime}</td>
26. </tr>
27. </logic:iterate>
28.
29. </table>
30. <html:link action="/search_items.do">購物車</html:link><br/>
31. <html:link action="/itemDispatcher.do" style="button">訂購商品</html:link>
32. </body>
33.</html>
<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBK"%>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<link rel="stylesheet" href="style.css" />
<title>用戶訂單列表</title>
</head>
<body>
${userForm.user.name},您好,您共提交了${fn:length(userForm.user.orders)}個訂單<br/>
<table width="100%" border="0" align="center" cellpadding="10" cellspacing="1" class="table-bgcolor">
<tr class="table-head">
<th>id</th><th>訂單名稱</th><th>訂單描述</th><th>訂單生成日期</th>
</tr>
<logic:iterate id="order" property="user.orders" name="userForm">
<tr class="table-body">
<td><html:link action="/search_items.do?id=${order.id}">${order.id}</html:link></td>
<td>無</td>
<td>${order.description}</td>
<td>${order.createTime}</td>
</tr>
</logic:iterate>
</table>
<html:link action="/search_items.do">購物車</html:link><br/>
<html:link action="/itemDispatcher.do" style="button">訂購商品</html:link>
</body>
</html>
這里不但使用了Struts標(biāo)簽,還使用了JSTL,fn定義了一些有用的函數(shù),可以在EL中使用,比如計算集合長度的length。Logic標(biāo)簽庫是用于邏輯處理的標(biāo)簽庫,iterator用于遍歷集合,name屬性用于從request、session、pageContext或者application取值,相當(dāng)于調(diào)用它們的getAttribute()方法,property是對應(yīng)的屬性,可以使用表達式的方式,比如這里user.orders,相當(dāng)于調(diào)用對象的getUser().getOrders()方法,這種形式很常見,比如在Spring和ognl中就支持這種形式的表達式。id屬性是引用的名稱,其作用和下面的變量order相當(dāng):
Java代碼
1.for(Order order: 包含Order對象的集合){
2.……
3.}
for(Order order: 包含Order對象的集合){
……
}
上面用到的EL語言,前面已經(jīng)介紹過,這里不再贅述。
這里引人注目的是Orders集合是從User對象中取出的,而在我們的代碼中并沒有這樣的賦值操作。事實上,這里用到了Hibernate的延遲加載特性,因此要求這里用到的User對象必須是一個持久化對象。這要求在處理頁面的時候,必須保證Hibernate的Session打開。因此我們應(yīng)該采用OpenSessionInView的模式,我們可以通過使用Spring提供的OpenSessionInViewFilter來實現(xiàn)這一模式,也可以通過使用Spring提供的OpenSessionInViewInterceptor,二者的作用相同。使用Filter的好處是開發(fā)者對它比較熟悉,使用Interceptor的方式要更為優(yōu)雅。因為我們將來要借助Spring的攔截器實現(xiàn)對用戶是否登錄的判斷,這里我們使用Interceptor的方式。不過這需要借助于SpringMVC來實現(xiàn),稍顯復(fù)雜。這就是Struts與Spring整合的第四種方式。詳細介紹如下。
我們使用Spring的DispatcherServlet來分發(fā)請求,使用這個Servlet需要注冊一個Listener,web.xml配置如下:
Xml代碼
1.<?xml version="1.0" encoding="UTF-8"?>
2.<web-app id="WebApp_ID" version="2.5"
3. xmlns="http://java.sun.com/xml/ns/javaee"
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
6. <listener>
7. <listener-class>
8.org.springframework.web.context.ContextLoaderListener
9.</listener-class>
10. </listener>
11. <servlet>
12. <servlet-name>actions</servlet-name>
13. <servlet-class>
14.org.springframework.web.servlet.DispatcherServlet
15.</servlet-class>
16. <load-on-startup>1</load-on-startup>
17. </servlet>
18. <servlet-mapping>
19. <servlet-name>actions</servlet-name>
20. <url-pattern>*.do</url-pattern>
21. </servlet-mapping>
22.</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>actions</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>actions</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
DispatcherServlet使用的配置文件,默認遵循這樣的規(guī)則:Servlet的名字-servlet.xml。這里是actions-servlet,它應(yīng)該位于WEB-INF下,這是一個標(biāo)準(zhǔn)的Spring配置文件。ContextLoaderListener也有需要一個默認的配置文件,名字是applicationContext.xml,也位于WEB-INFO下。我把我的配置信息都放入了applicationContext.xml中。我們需要去掉struts-config.xml中plug-in配置。好了,web.xml的信息就這么多?,F(xiàn)在需要修改Spring配置文件applicationContext.xml,在里面添加如下的配置:
Xml代碼
1.<bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
2. <property name="flushMode" value="0"/>
3. </bean>
<bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
<property name="flushMode" value="0"/>
</bean>
flushMode屬性設(shè)置session的flush方式,這里設(shè)為0,表示永遠不使用自動flush,這是為了使用聲明式事務(wù)。
Xml代碼
1.<bean id="strutsWrappingController" class="org.springframework.web.servlet.mvc.ServletWrappingController">
2. <property name="servletClass">
3. <value>org.apache.struts.action.ActionServlet</value>
4. </property>
5. <property name="servletName" value="action"/>
6. <property name="initParameters">
7. <props>
8. <prop key="config">/WEB-INF/struts-config.xml</prop>
9. <prop key="config/order">/WEB-INF/struts-order-config.xml</prop>
10. <prop key="debug">true</prop>
11. </props>
12. </property>
13. </bean>
<bean id="strutsWrappingController" class="org.springframework.web.servlet.mvc.ServletWrappingController">
<property name="servletClass">
<value>org.apache.struts.action.ActionServlet</value>
</property>
<property name="servletName" value="action"/>
<property name="initParameters">
<props>
<prop key="config">/WEB-INF/struts-config.xml</prop>
<prop key="config/order">/WEB-INF/struts-order-config.xml</prop>
<prop key="debug">true</prop>
</props>
</property>
</bean>
這里聲明了一個Controller,ServletWrappingController是為了Struts專門設(shè)計的,作用相當(dāng)于代理Struts的ActionServlet,下面的屬性都很好理解,我們看到和在web.xml的配置差不多,只不過形式的差異。
最后我們需要把Struts的請求路徑和Controller關(guān)聯(lián)起來:
Xml代碼
1.<bean id="urlMapping"
2.class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
3. <property name="interceptors">
4. <list>
5. <ref bean="openSessionInViewInterceptor" />
6. </list>
7. </property>
8. <property name="mappings">
9. <props>
10. <prop key="/*/*.do">strutsWrappingController</prop>
11. </props>
12. </property>
13. </bean>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="openSessionInViewInterceptor" />
</list>
</property>
<property name="mappings">
<props>
<prop key="/*/*.do">strutsWrappingController</prop>
</props>
</property>
</bean>
這樣我們就可以使用Hibernate的延遲加載特性和Spring的聲明式事務(wù)了。注意intercptors屬性,我們可以添加自己的攔截器實現(xiàn),稍后我們會給出一個具體的實例。攔截器的順序也是要格外留意的。登錄成功后頁面如圖9-3。
圖 9-3 用戶訂單列表
用戶注冊的流程和登錄一樣,注冊頁面和登錄一樣,這里我們就不在列出,只列出處理注冊的Action,代碼如下:
Java代碼
1.public class RegisterAction extends Action {
2. protected final Log log = LogFactory.getLog (RegisterAction.class);
3. private UserService userService;
4. private User user;
5.
6. public void setUserService(UserService userService) {
7. this.userService = userService;
8. }
9.
10. public void setUser(User user) {
11. this.user = user;
12. }
13.
14./**
15. * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
16. */
17. @Override
18. public ActionForward execute(ActionMapping mapping,
19. ActionForm form, HttpServletRequest request,
20. HttpServletResponse response) throws Exception {
21. UserForm uf = (UserForm)form;
22. User u = (User)cloneBean(user);
23. copyProperties(u, uf);
24. uf.reset(mapping, request);
25. userService.save(u);
26. //用戶注冊失敗,則返回失敗頁面
27. if(u.getId()==null || u.getId()==0)
28. return mapping.findForward("fail");
29. //注冊成功,就返回訂單列表頁
30. uf.setUser(u);
31. request.getSession().setAttribute("user", u);
32. return mapping.findForward("welcome");
33. }
34.}
public class RegisterAction extends Action {
protected final Log log = LogFactory.getLog (RegisterAction.class);
private UserService userService;
private User user;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setUser(User user) {
this.user = user;
}
/**
* @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception {
UserForm uf = (UserForm)form;
User u = (User)cloneBean(user);
copyProperties(u, uf);
uf.reset(mapping, request);
userService.save(u);
//用戶注冊失敗,則返回失敗頁面
if(u.getId()==null || u.getId()==0)
return mapping.findForward("fail");
//注冊成功,就返回訂單列表頁
uf.setUser(u);
request.getSession().setAttribute("user", u);
return mapping.findForward("welcome");
}
}
注冊頁面是register.jsp,對應(yīng)的path是/registerAction。然后我們在登錄頁面下加一個注冊按鈕,這樣用戶就可以從登錄頁轉(zhuǎn)到注冊頁。給注冊按鈕注冊一個響應(yīng)單擊的方法,代碼如下:
Js代碼
1.<script language="javascript">
2.function register(){
3. window.location="registerDispatcher.do";
4.}
5.</script>
<script language="javascript">
function register(){
window.location="registerDispatcher.do";
}
</script>
在struts-order-config.xml添加如下的配置:
Xml代碼
1. <action path="/registerDispatcher" forward="/register.jsp"/>
2. <action path="/registerAction"
3.input="/register.jsp"
4.type="org.springframework.web.struts.DelegatingActionProxy" name="userForm">
5. <forward name="fail" path="/registerDispatcher.do"/>
6. <forward name="welcome" path="/orderList.jsp"/>
7. </action>
<action path="/registerDispatcher" forward="/register.jsp"/>
<action path="/registerAction"
input="/register.jsp"
type="org.springframework.web.struts.DelegatingActionProxy" name="userForm">
<forward name="fail" path="/registerDispatcher.do"/>
<forward name="welcome" path="/orderList.jsp"/>
</action>
在applicationContext.xml中加入對應(yīng)的Action聲明:
Xml代碼
1.<bean name="/order/registerAction"
2.class="cn.zxm.order.action.RegisterAction"/>
<bean name="/order/registerAction"
class="cn.zxm.order.action.RegisterAction"/>
這樣就可以進行登錄測試了。讀者可以測試注冊一個中文名,成功后,會發(fā)現(xiàn)用戶名是亂碼,這是由于Tomcat默認使用ISO-8859-1編碼,而我們使用的是GBK編碼。解決這個問題,針對請求方式,有不同的處理策略。比如對POST請求,我們需要使用過濾器進行重新編碼,對于GET請求,需要在Tomcat端口配置中設(shè)置編碼方式。下面我們分別予以介紹。
我們可以自己實現(xiàn)這個過濾器,不過Spring已經(jīng)給我們提供了一個現(xiàn)成的實現(xiàn),它的配置方式如下:
Xml代碼
1. <filter>
2. <filter-name>encodeFilter</filter-name>
3. <filter-class>
4.org.springframework.web.filter.CharacterEncodingFilter
5.</filter-class>
6. <init-param>
7. <param-name>forceEncoding</param-name>
8. <param-value>true</param-value>
9. </init-param>
10. <init-param>
11. <param-name>encoding</param-name>
12. <param-value>GBK</param-value>
13. </init-param>
14. </filter>
15. <filter-mapping>
16. <filter-name>encodeFilter</filter-name>
17. <url-pattern>*.do</url-pattern>
18. </filter-mapping>
<filter>
<filter-name>encodeFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-