http://zxmzfbdc.javaeye.com/blog/258946
【原創】Spring、Hibernate、Struts1整合的方式
完整的例子源码已经作为附件上传,为了减小体积,没有包含用到的jar文件,请读者自己下载,不便处还请见谅。
9.1 模型概述
正如我们所知,Struts是一个单纯的web框架,因此对于其他方面的操作无能为力,例如对数据库的操作。此外它也不能管理对象之间的依赖关系,因此需要和持久化框架和IoC框架结合,才能构建一个完整健壮的系统。本节分析Struts+Spring+Hibernate结合的必要性。
9.1.1 三种模型结合的必要性
对于从事web开发的人来说,使用Struts可以大大简化开发过程,对于直接使用Servlet+JSP来说,它提供了优秀的封装机制,使得用户可以严格按照MVC架构来开发应用。想想我们没有使用web框架之前,是怎样的混乱状态,需要自己将数据从请求中取出来,然后组装起来,在数据很多的情况下,这简直是一个噩梦。验证代码散落在各处,维护它们同样是一件累人的活。为了处理不同的请求,可能需要写很多的Servlet,且不说容器的负担问题,单单是web.xml文件就变得分外的臃肿,最后可能变成一个难以维护的庞然大物。这是我在实际开发中遇到过的真实情况。尽管使用Struts,在早期也会遇到配置文件(struts-config.xml)膨胀的问题,但后来的Struts通过模块化很好的解决了这个问题。
关于验证,需要多说一些,事实上,Struts的验证并不像后来的一些web框架而言,如Webwork、Spring MVC,是可插拔的,相反,它被包含在ActionForm中,这是一种耦合性较强的设计。form负担了的太多的责任,使得重用它变得困难,比如在业务层和持久层,另外,form是Struts框架的一部分,出于降低耦合性的考虑,也不能在业务层和持久层中出现。它之后的一些框架都很好的解决了这个问题:使用一个普通的pojo,仅仅用于封装数据,不包含任何的逻辑。但是,Struts是一个可扩展的框架,它的任何一部份都是可以替换的。我们可以通过它提供的插件机制,来提供自己的功能,例如通常情况下,Spring和Struts的整合的一种方式,就是通过插件实现的。通过插件,我们也可以提供可插拔的验证功能。
Struts是一个经历了无数项目考验的框架,有着为数最为庞大的社群,有着成熟的经验可供参考,所以在项目中使用Struts风险很小。而且,Struts比较容易上手,因此学习成本也相对较小。
正如我们前面所说的,Struts是一个纯粹的web框架,不涉及业务层和持久层的处理,因此,对于其他层需要借助于其他的框架。比如,在持久层使用 Hibernate。
数据库操作是一个很复杂的问题,尽管在Java中,JDBC进行了很好的抽象,但是用户需要直接操作SQL。这里也存在一个数据组装的问题,实际开发中直接从ResultSet中取出数据,然后放入一个model中,是很常见的方式。冗长的set操作令人厌烦。我们在实际开发中,都是将数据存放到model中,然后操作model,在这个意义上说,model和数据库之间有着某种内在的关系。怎样映射这种关系,正是ORM所要做的。ORM不仅仅要映射对象的属性和数据库表的字段间的关系,还要把表之间的关系映射为对象之间的关系,从而把对数据库表的操作,完全转为对对象的操作。这种处理方式,更为程序员所熟悉,而且通过工具,我们也很容易保持表关系和对象关系的同步。Hibernate正是这样一个强大的ORM工具,除了通常的O-R映射,还提供了其他的广受欢迎的功能,比如脏数据检查、延迟加载等。这些,我们在前面的章节中,已经做过介绍。Hibernate在实际开发中获得了广泛的应用,成为事实上的标准。所以,我们在开发中,持久层实现,可以选择Hibernate。
Struts同样也缺少IoC和AOP功能,因此存在一定的耦合,例如,在Action调用业务对象的时候,需要new一个实例,这是很常见的情况。当然,我们可以使用工厂模式消除这种情况,但是又需要引入工厂类。使用Spring可以有效地消除这种耦合。我们在上一章中,介绍的Spring和Struts整合的方式中,可我们在Action中通过Spring的ApplicationContext获取对象,但是这样Action中又嵌入了Spring的代码。整合的第三种方式,通过Struts的plug-in机制,将Action交由Spring托管,这样,我们就可以在Action中使用依赖注入,完全消除了这种耦合。除此之外,我们还可以使用Spring的AOP机制。通过整合,我们可以充分利用各个框架的优势,从而达到构建完美的应用的目标。
9.2 模型特征
我们分析了三种框架结合的必要性,现在我们分析一下使用三个框架的模型特性。
9.2.1 模型的构成特点
在Spring+Hibernate+Struts模型中,Hibernate负责持久化操作,Struts负责web操作,两者之间完全独立。Spring在这个模型中的地位很特殊,它提供对对象的管理,而不管这个对象是处在哪一层,完成哪些任务。Spring为用户设置对象间的依赖关系,并且通过AOP提供了统一的方式,处理横向的需求,这些需求通常很难使用OO实现,比如权限验证。这样,Spring就涉及到广泛的领域,而不管它处在哪一层。或者说,Spring是一个更为基础的框架,而不像Struts和Hibernate专注于各自的领域。
现在已经清楚了Spring、Hibernate、Struts的作用,现在从分层的角度,分析一下这个模型。
业务层,处理实际的逻辑操作,这一部分是由用户独立实现的,拥有最大的灵活性。有些时候,持久层和业务层是混合在一起的,但是数据库一旦设计完成,就很少修改,所有拥有相当的稳定性,所以持久层也拥有相当的稳定性,因此可以将它们独立出来。还有一个原因,持久化操作,很多时候很复杂,特别是涉及到事物的时候,因此,维护一个稳定的持久层,有利于分工,也减少了维护的成本。Hibernate的出现,使得持久化操作不再像以前那么复杂繁琐,所以也有将这两层合并的做法。我们下面给出的例子,会看到很多时候业务层仅仅是将请求委托给持久层,本身什么也不做。但我认为,维护一个独立的业务层,还是有好处的,至少在将来切换持久化实现的时候,比如用JDBC替换Hibernate,业务层就不用作任何的改动,虽然这种情况很少发生。
持久层,我们可以直接使用Hibernate API,更好的方式是使用Spring的Hibernate封装。Spring提供了对Hibernate的良好封装,这样我们在持久化操作中,可以使用Spring提供的封装,简化持久化的操作。Hibernate的Session是很昂贵的资源,因此,每个DAO(这里指持久层的类)方法,单独使用一个Session是一种浪费,使用Spring的封装,就可以使用统一的方式使用Session。通过使用Spring托管的Session,我们实现了OpenSessionInView模式,即在每一个请求中使用一个Session。当然我们可以提供自己的OpenSessionInView实现,最简单的就是实现一个Filter。事实上Spring提供的其中一种实现就是通过这种方式实现的。在Spring托管的环境下,我们就可以实现Hibernate的声明式管理。使用Spring封装的一个好处就是对异常的处理,Spring可以给出很明确的异常信息,不像JDBC统一抛出SQLException,Hibernate抛出HibernateException。Spring还提供了异常转换的功能,用户可以在配置文件中声明转换成什么样的异常。
web层,就是Struts的责任了,这里通常不会涉及到Hibernate和Spring,我们仅仅是由Spring管理依赖对象的注入。但是,很多时候web请求需要一些横向的需求,比如上面提到的权限管理,一个更常见的情况是用户登录和注册。当然我们可以使用Filter实现,也可以写一个顶层的Action,统一处理这些请求,需要处理这些请求的Action继承它即可。但是这是一种硬编码的实现,不能通过配置改变,因此缺少灵活性。用户是否登录的判断也是一个典型的横切应用,因此可以通过AOP来实现。我们下面的例子,会给出一个使用拦截器判断用户是否登录的实现。为了使用拦截器,我们这里给出了Spring和Struts整合的第四种方式,涉及到了SpringMVC的内容。相对来说,SpringMVC在设计上更加倚重IoC和AOP,也提供了更为开放的方式,因此更为强大,但是却过于复杂。但是相对于它提供的丰富特性,这些复杂性是可以克服的,因此在实际开发中,可以考虑用SpringMVC实现web层,即用它替换Struts。
9.2.2 实现模型的目的
使用Spring+Hibernate+Struts模型的目的,实际上面已经涉及到了,这些框架的优点是使用它们的一个重要原因。还有一些可能更重要的原因,总结如下:
去耦合 这是一个老生常谈的话题了。去耦合是如此的重要,以至于人们要将这方面的经验总结成设计模式。Spring通过IoC,实现了去耦合,这样用户可以随时替换现在的实现,而不用修改源码。这正是软件开发中开-闭原则的要求。Spring倡导面向接口编程,这是最为推崇的最佳实践之一,继承仅仅作为内部实现的一个手段。面向接口编程,不仅仅是提供了更好的抽象,实际上也更有利于去耦合。
扩展的需要 Struts、Spring甚至Hibernate都提供了良好的扩展功能,Struts通过插件机制,可以很方便的添加功能。Spring对Bean的管理也提供了扩展,例如用户可以提供自己的作用域。Hibernate用户可以定制自己的持久化策略。
维护的需要 这些框架都很容易维护,它们都很好的实现了开-闭原则,因此需要替换功能的时候,只需简单的修改配置文件。它们提供的一些公用功能,也能通过配置的方式提供,比如Spring的声明式事务。因此维护成本大大降低。
还有一个原因,由于它们都有庞大的社区支持,因此寻找技术支持很容易。由于它们很流行,因此招一个开发人员也很容易。
9.3 Hibernate+Spring+Struts模型应用分析
通过上面的学习,我们对三者结合的模型有了一个概念性的理解,现在我们通过一个具体的例子,演示怎样将它们整合在一起。
9.3.1 应用事例
我们考虑一个购物车的例子,相信读者或多或少都接触过这方面的例子。为了简单起见,我们不考虑库存的问题,商品的信息完全由用户填写,包括商品名称、商品介绍、商品价格、购买数量。填写完商品信息,会被暂时放入购物车内,每个购物车抽象成一个订单。当用户确认购买的商品后,就可以提交购物车,从而这些信息被存入数据库,购物车清空。在做这些操作之前,用户首先需要登录,如果用户没有注册,可以选择注册。用户不仅可以购买商品,还可以查看已经提交的订单的详细信息。一个用户可以有多个订单,一个订单只能属于一个用户。同理,一个订单可以包含多个商品,而一个商品只能属于一个订单。用户的姓名是唯一的,因为这是确认用户的唯一凭证。这就是全部的需求。
9.3.2 事例分析
好,现在我们开始构建整个应用。应用用到的类库如图9-1所示:
图9-1 应用用到的jar包
首先,我们需要建立所需的数据库表,我们用到的表有三个,每个表都有一个用于标识的id,这三个表分别是:
•tb_user——用户表,用来存放用户信息,包括用户名和密码;
•tb_order——订单表,用来存放订单信息,包括订单描述、生成时间、用户id;
•tb_item——商品表,用来存放商品信息,包括商品名称、商品描述、商品价格、购买数量、定单id。
我用的数据库是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), /*购买数量*/
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), /*购买数量*/
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和相关的hibernate映射文件,这可以通过hibernate-tools实现,也可以通过Middlegen实现。pojo对应的名字分别是User、Order和Item,映射文件和pojo同名。注意包名的命名,通过包,我们可以将类进行分组,便于管理。我们在开发中,应该重视包的管理,至少在一个应用中,应该有统一的命名规范。这里使用的命名习惯和放置的文件是这样的:
•cn.zxm.order.pojo——pojo和相关的映射文件;
•cn.zxm.order.dao——持久化接口;
•cn.zxm.order.dao.impl——持久化接口的实现类;
•cn.zxm.order.service——业务接口;
•cn.zxm.order.service.impl——业务接口的实现类;
•cn.zxm.order.form——应用用到的ActionForm;
•cn.zxm.order.action——应用用到的Action;
•cn.zxm.order.util——应用用到的工具类。
这里我们只列出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.……对应的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);
……对应的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.……对应的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);
……对应的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.……对应的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;
……对应的setter和getter方法……
}
第三步,构建持久化接口和它们的实现。开始构建时,我们可能不知道需要哪些方法,没有关系,我们从最小需求来考虑,只定义肯定用到的方法即可,至于应用中需要新的方法,可以通过重构添加,现在的IDE很多都提供了很好的重构功能。那么重构时是先从接口开始,还是从实现开始?一般来说,从接口开始保证实现必须包含这个方法,但是从类开始,我们可能会忘记将方法上推到接口。不过,这个问题不大,因为我们实际开发时用到的是接口,所以,这个不是问题。我的建议是从实现开始重构。
针对用户的操作,我们需要根据主键获取用户信息。由于要处理用户注册,所以需要保存用户信息。要处理登录,所以需要根据用户名获取用户信息。这就是需要用到的三个方法,借口定义如下:
Java代码
1.public interface UserDAO {
2. /**
3. * 保存用户
4. * @param user
5. */
6. void save(User user);
7.
8. /**
9. * 根据用户id获取用户信息
10. * @param id
11. * @return
12. */
13. User getUser(int id);
14.
15. /**
16. * 根据用户名获取用户信息
17. * @param name
18. * @return
19. */
20. User getUserByName(String name);
21.}
public interface UserDAO {
/**
* 保存用户
* @param user
*/
void save(User user);
/**
* 根据用户id获取用户信息
* @param id
* @return
*/
User getUser(int id);
/**
* 根据用户名获取用户信息
* @param name
* @return
*/
User getUserByName(String name);
}
对应的实现我们用到了Spring的HibernateTemplate类,这个类封装了对于Hibernate的通用操作。实现如下:
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方法,第二个参数可以是一个数组,用来给hql中的参数赋值
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方法,第二个参数可以是一个数组,用来给hql中的参数赋值
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实现中也要用到,因此我们可以把它放到一个超类中,这样所有的DAO实现先就可以通过继承这个超类,而不在需要自己声明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有脏数据检查功能,实际上,通过查询方法,我们也可以实现修改操作)。代码如下:
Java代码
1.public interface OrderDAO {
2. /**
3. * 保存订单
4. * @param order
5. */
6. void save(Order order);
7.
8. /**
9. * 根据订单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);
/**
* 根据订单id获取订单信息
* @param id
* @return
*/
Order getOrder(int id);
/**
* 删除订单
* @param order
*/
void delete(Order order);
}
OrderDAO的实现代码如下:
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的级联保存,因此不涉及单独操作Item的情况,ItemDAO在这个事例中不需要,这里省略。读者可以自己完善这个应用,比如删除订单和查看单个商品信息等。
持久层逻辑完成了,现在我们考虑一下逻辑层的实现。
用户操作和DAO的操作相似,只不过这里只有业务处理,不会涉及数据存取的操作,代码如下:
Java代码
1.public interface UserService {
2. /**
3. * 保存用户
4. * @param user
5. */
6. void save(User user);
7.
8. /**
9. * 根据用户id获取用户信息
10. * @param id
11. * @return
12. */
13. User getUser(int id);
14.
15. /**
16. * 根据传入的用户数据获取完整的用户信息
17. * @param user
18. * @return
19. */
20. User getUser(User user);
21.}
public interface UserService {
/**
* 保存用户
* @param user
*/
void save(User user);
/**
* 根据用户id获取用户信息
* @param id
* @return
*/
User getUser(int id);
/**
* 根据传入的用户数据获取完整的用户信息
* @param user
* @return
*/
User getUser(User user);
}
对应的实现如下:
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. * 这里使用了事务标注,这样可以使用声明式事务
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);
}
/**
* 这里使用了事务标注,这样可以使用声明式事务
* 如果用户名为空,将不会保存
*/
@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);
}
对应的实现如下:
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. * 保存订单,我们在这里给订单设置了生成时间
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);
}
/**
* 保存订单,我们在这里给订单设置了生成时间
*/
@Transactional
public void save(Order order) {
order.setCreateTime(new Timestamp(System.currentTimeMillis()));
orderDAO.save(order);
}
}
我们的购物车是通过HttpSession实现的,因此我们在这里不是定义一个Service,还是通过一个工具类,实现操作购物车的功能。这里购物车中保存的是一个尚未提交的订单。由于需要向订单内保存商品,我们在Order中增加一个方法,代码如下:
Java代码
1. /**
2. * 添加商品,注意我们手动为商品添加了订单关联,
3. * 这是为了使用Hibernate的级联保存
4. * @param item
5. */
6.public void addItem(Item item){
7. item.setOrder(this);
8. items.add(item);
9.}
/**
* 添加商品,注意我们手动为商品添加了订单关联,
* 这是为了使用Hibernate的级联保存
* @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());
}
}
现在我们需要将这些类配置在Spring配置文件中,需要注意的是我们这里使用了声明式事务,因此需要引入对应的schema,引入的方式在上一章已经介绍,这里不再重复。配置文件的代码如下:
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的地位。事务管理器用的是专门为Hibernate3设计的,数据源是通过JNDI读取的,这些都是需要特别注意的。
现在可以开发web层了。由于Action和Form依赖于页面,所以我们先考虑构建页面,所有的页面位于应用的order目录下。根据需求,首先需要一个登录页,只需要用户名和密码输入框。代码如下(这里只列出和输入相关部分的代码,其它部分省略):
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标签,使用它们时,需要确保在页面头部正确引入。注意在下面的form类中,属性名和这里的名称要一致。相关的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. //将用户发来的数据放入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);
//将用户发来的数据放入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");
}
}
针对登陆的类至此开发完成。剩下的就是配置,我们首先使用上一章讲到的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,这个文件用于配置我们现在使用的模块。Struts的模块是按照目录划分的。在加入了这个模块以后,相应的web.xml中,给ActionServlet加入如下的参数:
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/ 后面的参数必须是模块对应的目录名。这样应用访问特定模块的时候,Struts会自动追加模块的名称。比如现在的登录,页面端表单中action对应的值是/loginDispatcher.do,Struts会自动将它转为/order/loginDispatcher.do。而Struts本身的配置文件在改动模块名的时候,不需要作任何改动。这个应用很小,没有必要使用模块化功能,这里使用它,只是为了演示。这样的需求实际应用中很常见。
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的命名,应该有一定的约定,比如只是简单映射页面,使用页面名称+Dispatcher的形式;处理页面的请求的路径使用页面名称+Action的形式,等等,这有利于将来我们使用通配符来简化配置。这个问题我们后面会涉及到。
在Spring配置文件中加入Action的定义,bean的名字对应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"/>
现在我们可以测试登录了,我们在数据库中输入一个用户的信息,用户名和密码都是zxm。打开浏览器,在地址栏输入
http://www.zxm.net:8090/order/order/loginDispatcher.do
,页面如图9-2。
图9-2 登录
输入用户名和密码,输入不正确的用户名或者密码,应该返回登录页面,成功则进入订单列表页面,由于页面端需要用到Hibernate的延迟加载特性,第一次访问会抛出session closed的异常。
现在我们构建订单显示页,订单页的代码如下:
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标签,还使用了JSTL,fn定义了一些有用的函数,可以在EL中使用,比如计算集合长度的length。Logic标签库是用于逻辑处理的标签库,iterator用于遍历集合,name属性用于从request、session、pageContext或者application取值,相当于调用它们的getAttribute()方法,property是对应的属性,可以使用表达式的方式,比如这里user.orders,相当于调用对象的getUser().getOrders()方法,这种形式很常见,比如在Spring和ognl中就支持这种形式的表达式。id属性是引用的名称,其作用和下面的变量order相当:
Java代码
1.for(Order order: 包含Order对象的集合){
2.……
3.}
for(Order order: 包含Order对象的集合){
……
}
上面用到的EL语言,前面已经介绍过,这里不再赘述。
这里引人注目的是Orders集合是从User对象中取出的,而在我们的代码中并没有这样的赋值操作。事实上,这里用到了Hibernate的延迟加载特性,因此要求这里用到的User对象必须是一个持久化对象。这要求在处理页面的时候,必须保证Hibernate的Session打开。因此我们应该采用OpenSessionInView的模式,我们可以通过使用Spring提供的OpenSessionInViewFilter来实现这一模式,也可以通过使用Spring提供的OpenSessionInViewInterceptor,二者的作用相同。使用Filter的好处是开发者对它比较熟悉,使用Interceptor的方式要更为优雅。因为我们将来要借助Spring的拦截器实现对用户是否登录的判断,这里我们使用Interceptor的方式。不过这需要借助于SpringMVC来实现,稍显复杂。这就是Struts与Spring整合的第四种方式。详细介绍如下。
我们使用Spring的DispatcherServlet来分发请求,使用这个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使用的配置文件,默认遵循这样的规则:Servlet的名字-servlet.xml。这里是actions-servlet,它应该位于WEB-INF下,这是一个标准的Spring配置文件。ContextLoaderListener也有需要一个默认的配置文件,名字是applicationContext.xml,也位于WEB-INFO下。我把我的配置信息都放入了applicationContext.xml中。我们需要去掉struts-config.xml中plug-in配置。好了,web.xml的信息就这么多。现在需要修改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属性设置session的flush方式,这里设为0,表示永远不使用自动flush,这是为了使用声明式事务。
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专门设计的,作用相当于代理Struts的ActionServlet,下面的属性都很好理解,我们看到和在web.xml的配置差不多,只不过形式的差异。
最后我们需要把Struts的请求路径和Controller关联起来:
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的声明式事务了。注意intercptors属性,我们可以添加自己的拦截器实现,稍后我们会给出一个具体的实例。拦截器的顺序也是要格外留意的。登录成功后页面如图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,对应的path是/registerAction。然后我们在登录页面下加一个注册按钮,这样用户就可以从登录页转到注册页。给注册按钮注册一个响应单击的方法,代码如下:
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中加入对应的Action声明:
Xml代码
1.<bean name="/order/registerAction"
2.class="cn.zxm.order.action.RegisterAction"/>
<bean name="/order/registerAction"
class="cn.zxm.order.action.RegisterAction"/>
这样就可以进行登录测试了。读者可以测试注册一个中文名,成功后,会发现用户名是乱码,这是由于Tomcat默认使用ISO-8859-1编码,而我们使用的是GBK编码。解决这个问题,针对请求方式,有不同的处理策略。比如对POST请求,我们需要使用过滤器进行重新编码,对于GET请求,需要在Tomcat端口配置中设置编码方式。下面我们分别予以介绍。
我们可以自己实现这个过滤器,不过Spring已经给我们提供了一个现成的实现,它的配置方式如下:
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-
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。