打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Spring、Hibernate、Struts1整合的方式
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-
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
SSH实现的增删改查实例
spring面试题
spring+hibernate整合
一份完整的Spring Hibernate DWR extJs的生成树及下拉comBobo...
Struts2 Spring Hibernate搭建全解!
eclipse搭建SSH框架详解
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服