`
zxmzfbdc
  • 浏览: 47925 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

【原創】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如下:
  
    /*商品表*/
    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:
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:
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:
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很多都提供了很好的重构功能。那么重构时是先从接口开始,还是从实现开始?一般来说,从接口开始保证实现必须包含这个方法,但是从类开始,我们可能会忘记将方法上推到接口。不过,这个问题不大,因为我们实际开发时用到的是接口,所以,这个不是问题。我的建议是从实现开始重构。
  针对用户的操作,我们需要根据主键获取用户信息。由于要处理用户注册,所以需要保存用户信息。要处理登录,所以需要根据用户名获取用户信息。这就是需要用到的三个方法,借口定义如下:
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:
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,代码如下:
public class BaseDAO {
  	protected HibernateTemplate hibernate;

  	public BaseDAO() {
	     	super();
 	}

 	public void setHibernate(HibernateTemplate hibernate) {
 		this.hibernate = hibernate;
 	}
}

  修改UserDAOImpl,使他继承BaseDAO,代码如下:
public class UserDAOImpl extends BaseDAO implements UserDAO {
	public User getUser(int id) {
		……
	}

	public User getUserByName(String name) {
		……
	}

	public void save(User user) {
		……
	}
}

  订单操作同样需要保存操作,可能需要删除操作,因为要查看订单,显然需要一个查询方法,为了简单,这里不考虑修改操作(由于Hibernate有脏数据检查功能,实际上,通过查询方法,我们也可以实现修改操作)。代码如下:
public interface OrderDAO {
	/**
	 * 保存订单
	 * @param order
	 */
	void save(Order order);
	
	/**
	 * 根据订单id获取订单信息
	 * @param id
	 * @return
	 */
	Order getOrder(int id);
	
	/**
	 * 删除订单
	 * @param order
	 */
	void delete(Order order);
}

  OrderDAO的实现代码如下:
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的操作相似,只不过这里只有业务处理,不会涉及数据存取的操作,代码如下:
public interface UserService {
	/**
	 * 保存用户
	 * @param user
	 */
	void save(User user);

	/**
	 * 根据用户id获取用户信息
	 * @param id
	 * @return
	 */
	User getUser(int id);

	/**
	 * 根据传入的用户数据获取完整的用户信息
	 * @param user
	 * @return
	 */
	User getUser(User user);
}

  对应的实现如下:
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);
	}
}

  订单的处理,我们需要一个获取订单信息的方法,还需要一个保存订单的方法,接口代码如下:
public interface OrderService {
	/**
	 * 获取订单详细信息
	 * @param id
	 * @return
	 */
	Order getOrder(int id);
	
	/**
	 * 保存订单
	 * @param order
	 */
	void save(Order order);
}

  对应的实现如下:
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中增加一个方法,代码如下:
       /**
	 * 添加商品,注意我们手动为商品添加了订单关联,
	 * 这是为了使用Hibernate的级联保存
	 * @param item
	 */
	public void addItem(Item item){
		item.setOrder(this);
		items.add(item);
	}

  工具类的代码如下:
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,引入的方式在上一章已经介绍,这里不再重复。配置文件的代码如下:
<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: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类代码如下:
public class UserForm extends ActionForm {
	private String name;
	private String password;
	private User user;

	//setter和getter方法
}

  然后写一个Action用来处理登陆,代码如下:
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 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加入如下的参数:
<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的配置如下:
<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的名字包含了模块名:
<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的异常。

  现在我们构建订单显示页,订单页的代码如下:
<%@ 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相当:
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 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,在里面添加如下的配置:
<bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
		<property name="flushMode" value="0"/>
	</bean>

  flushMode属性设置session的flush方式,这里设为0,表示永远不使用自动flush,这是为了使用声明式事务。
<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关联起来:
<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,代码如下:
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。然后我们在登录页面下加一个注册按钮,这样用户就可以从登录页转到注册页。给注册按钮注册一个响应单击的方法,代码如下:
<script language="javascript">
function register(){
    window.location="registerDispatcher.do";
}
</script>

  在struts-order-config.xml添加如下的配置:
			<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声明:
<bean name="/order/registerAction" 
class="cn.zxm.order.action.RegisterAction"/>

  这样就可以进行登录测试了。读者可以测试注册一个中文名,成功后,会发现用户名是乱码,这是由于Tomcat默认使用ISO-8859-1编码,而我们使用的是GBK编码。解决这个问题,针对请求方式,有不同的处理策略。比如对POST请求,我们需要使用过滤器进行重新编码,对于GET请求,需要在Tomcat端口配置中设置编码方式。下面我们分别予以介绍。
  我们可以自己实现这个过滤器,不过Spring已经给我们提供了一个现成的实现,它的配置方式如下:
	<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-value>
	    </init-param>
	</filter>
	<filter-mapping>
	    <filter-name>encodeFilter</filter-name>
	    <url-pattern>*.do</url-pattern>
	</filter-mapping>

  forceEncoding参数表示是否进行强制编码,这里当然是true。encoding表示编码方式,这里是GBK。
  针对GET请求,我们只需要在监听HTTP请求的端口中加入如下的属性即可:URIEncoding="GBK"。
  由于我们在应用中针对页面和Action的命名遵循了统一的规范,所以我们可以使用通配符简化配置。例如访问页面的方式可以使用如下的方式:
<action path="/*Dispatcher" forward="/{1}.jsp"/>

  *就是用到的占位符,下面引用的时候使用大括号,包含一个数字,数字表示占位符的位置,注意这个值是从1开始的。例如当用户访问/loginDispatcher的时候,访问到的页面就是login.jsp。
  相应的用于处理请求的Action的配置统一修改如下:
<action path="/*Action" input="/{1}.jsp" 
type="org.springframework.web.struts.DelegatingActionProxy" name="userForm">
		    <forward name="fail" path="/{1}Dispatcher.do"/>
	</action>

  登录和查看用户订单清单的任务已经完成了,现在我们看一下怎么订购商品。同上面的处理方式一样,我们需要一个输入商品信息的页面,页面的名字是item.jsp,代码如下:
<html:form action="/commit_item.do" method="post">
    <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:textarea property="description" style="width:98%;"/></td>
      </tr>
      <tr class="table-body">
        <td class="table-head">价格</td>
        <td><html:text property="price" style="width:98%;"/></td>
      </tr>
      <tr class="table-body">
        <td class="table-head">数量</td>
        <td><html:text property="count" style="width:98%;"/></td>
      </tr>
      <tr class="table-body"><td></td><td><input type="submit" class="button" value="购买"/></td></tr>
    </table>
</html:form>

  处理保存订单的Action代码如下:
public class BuyItemAction extends org.apache.struts.action.Action{
		private ItemService itemService;
	
		public void setItemService(ItemService itemService) {
		this.itemService = itemService;
	}
	
	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
			ItemForm itemForm = (ItemForm)form;
			Item item = new Item();
			copyProperties(item, itemForm);
        //这里将Item的id设为null,是为了使用Hibernate根据id是否为null,
        //选择是进行保存,还是进行修改
			item.setId(null);
			saveItem(request, item);
			itemForm.reset(mapping, request);
			return mapping.findForward("success");
	}
}

  提交订单后,会返回一个购物车的页面,页面的名称是order.jsp,显示用户现在放入购物车中的所有商品。由于这需要一个查询操作,所以我们需要一个新的Action,这个Action事实上会处理两种情况,一种是用户查阅订单内的商品,另一种就是查阅购物车内的商品,二者的区别不过一个是从数据库中读取数据,一个是从session中读取数据。这个Action的代码如下:
public class ViewOrderItemAction extends Action {
	private OrderService orderService;

	public void setOrderService(OrderService orderService) {
			this.orderService = orderService;
	}

	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
			OrderForm orderForm = (OrderForm) form;
			//如果不能获取的id不是0,表明这是一个订单查询
			if (orderForm.getId() > 0) {
				Order order = orderService.getOrder(orderForm.getId());
				orderForm.setId(0);
				orderForm.setOrder(order);
				return mapping.findForward("viewOrderItem");
			}
			//如果获取的id是0,表明这是查询购物车内的商品
			orderForm.setOrder(getOrder(request));
			return mapping.findForward("viewOrderItem");
	}
}

  struts-order-config.xml添加如下的配置:
<action path="/commit_*" type="org.springframework.web.struts.DelegatingActionProxy" 
input="/{1}.jsp" name="{1}Form">
		    <forward name="success" path="/search_{1}s.do"/>
	</action>
	<action path="/search_items" 
type="org.springframework.web.struts.DelegatingActionProxy" name="orderForm"/>

  在applicationContext.xml中需要加入的配置如下:
<bean name="/order/commit_item" class="cn.zxm.order.action.BuyItemAction"/>
<bean name="/order/search_items" class="cn.zxm.order.action.ViewOrderItemAction"/>

  现在我们可以通过/itemDispatcher.do来访问定购商品页了,如图9-4。
  提交商品后,应该返回给用户购物车的页面,显示购物车内的所有商品,这个页面是order.jsp。order.jsp的代码如下:
<table width="400" align="center">
        <tr>
          <td class="title">
            <logic:notEmpty name="orderForm" property="order.id">
                查看订单包含的商品信息
            </logic:notEmpty>
            <logic:empty name="orderForm" property="order.id">
                查看购物车内商品信息
            </logic:empty>
          </td>
        </tr>
      </table>
      <table width="100%"  border="0" align="center" cellpadding="10" cellspacing="1" class="table-bgcolor">
          <tr class="table-head">
              <th>商品名称</th><th>商品描述</th><th>商品价格(单位:人民币元)</th><th>购买数量</th>
          </tr>
          <logic:iterate id="item" property="order.items" name="orderForm">
          <tr class="table-body">
            <td>${item.name}</td>
            <td>  ${item.description}</td>
            <td>${item.price}</td>
            <td>${item.count}</td>
          </tr>
        </logic:iterate>
      </table>
      <html:form action="/commit_order.do" method="post">
      <table width="100%"  border="0" align="center" cellpadding="10" cellspacing="1">
        <tr class="table-title"><td class="title">订单描述</td></tr>
        <tr class="table-body">
            <td align="left">
            <logic:notEmpty name="orderForm" property="order.id">
              ${orderForm.order.description}
            </logic:notEmpty>
            <logic:empty name="orderForm" property="order.id">
            <html:textarea property="description" style="width:98%;" value="${orderForm.order.description}"/>
            </logic:empty>
            </td>
        </tr>
        <logic:empty name="orderForm" property="order.id">
        <tr>
            <td><html:submit style="center" value="提交订单"/></td>
        </tr>
        </logic:empty>
      </table>
      </html:form>
      <logic:empty name="orderForm" property="order.id">
        <html:link action="/itemDispatcher.do">
          <logic:empty name="orderForm" property="order.items">
              订购商品
          </logic:empty>
          <logic:notEmpty name="orderForm" property="order.items">
              继续订购
          </logic:notEmpty>
        </html:link><br/>
      </logic:empty>
      <html:link action="/search_orders.do">返回首页</html:link>


图9-4 订购商品
  logic标签用来处理逻辑的,从字面意思也很好理解,empty判断属性是否为空,notEmpty判断属性是否非空。我们把提交购物车和查询订单的页面做到了一起,为了清晰,读者可以考虑将它分为两个文件。查询订单的页面如图9-5,查询购物车的页面如图9-6。
  将商品放入购物车后,下一步就是提交购物车,反映在数据库中,是生成了一个订单,同时将购物车清空。处理提交订单的Action代码如下:
public class CommitOrderAction extends Action {
	private OrderService orderService;
	
	public void setOrderService(OrderService orderService) {
		this.orderService = orderService;
	}

	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
			Order order = getOrder(request);
			copyProperties(order, form);
			//设置订单所属的用户
		order.setUser((User)request.getSession().getAttribute("user"));
			//如果购物车内没有商品,不会进行保存操作
		if(order.getItems().size()>0)orderService.save(order);
			form.reset(mapping, request);
		//清空购物车
			clearCart(request);
		return mapping.findForward("success");
		}
}

  由于提交订单和提交商品有着相似的形式,因此我们使用通配符,不需要再次配置,只需要在applicationContext.xml中加入下面的配置即可:
<bean name="/order/commit_order" class="cn.zxm.order.action.CommitOrderAction"/>


图9-5 查询订单详细信息
  我们还忽略了一个问题,就是用户查询订单列表的时候,并非一定是从登录页面开始的,因此,我们还需要一个查询订单列表的Action,代码如下:
public class ViewOrdersAction extends Action {
	private UserService userService;

	public void setUserService(UserService userService) {
			this.userService = userService;
	}

	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
			UserForm userForm = (UserForm) form;
			User u = (User) request.getSession().getAttribute("user");
			User user = userService.getUser(u.getId());
			userForm.setUser(user);
			return mapping.findForward("welcome");
	}
}


图9-6 查询购物车内的信息
  struts-order-config.xml的配置信息如下:
<action path="/search_orders" 
type="org.springframework.web.struts.DelegatingActionProxy" name="userForm"/>

  applicationContext.xml需要加入的内容是:
<bean name="/order/search_orders" class="cn.zxm.order.action.ViewOrdersAction"/>

  为了访问方便,我们还使用全局性的配置,这里用到了三个,struts-order-config.xml中涉及的配置如下:
<global-forwards>
		<forward name="login" path="/loginDispatcher.do"/>
		<forward name="welcome" path="/orderListDispatcher.do"/>
		<forward name="viewOrderItem" path="/orderDispatcher.do"/>
	</global-forwards>

  这样整个应用就算完成了,剩下的工作就是在页面上加入必要的导航,比如返回首页的链接等。
  这个应用似乎还缺少些什么。如果session过期,怎么办?这是一个即为常见的需求,我们固然可以在访问每个action前进行判断,但是这样同样的代码会分布在不同的地方,维护起来很麻烦。重构中,有一条原则,就是"Once and only once",即所谓的相同的代码只能出现一次。这是一条很重要的原则,除了维护的需要外,还有重用的考虑。登录过期的判断,是一个典型的横切的应用,采用传统的方式实现要困难一些,我们可以使用一个基类进行判断,然后让需要这个功能的类继承这个基类,这是一个解决方案。还有一个更好的解决方案,就是使用拦截器,拦截请求。我们可以将它配置在文件中,这样就使用可配置的方式,防止了硬编码,这是一种极为灵活的方式。我们下面就介绍怎样使用拦截器,判断用户的登录信息是否过期。
  为了拦截HTTP请求,我们需要实现接口HandlerInterceptor,这个接口有三个方法,boolean preHandle(...),这个方法在处理HTTP请求之前执行,void postHandle(..)在输出页面之前执行,void afterCompletion(...)在请求处理完成之后进行。很显然preHandle()正是需要我们实现的方法。这个拦截器的代码如下:
public class LoginInterceptor implements HandlerInterceptor {
		private Properties urls = new Properties();
	private String suffix;
	
	public void setUrls(Properties urls){
			this.urls = urls;
	}
	
	public void setSuffix(String suffix){
		this.suffix = suffix;
	}
	
	@Override
	public void afterCompletion(HttpServletRequest request,
				HttpServletResponse response, Object handler, Exception ex)
				throws Exception {
	}

	@Override
	public void postHandle(HttpServletRequest request,
				HttpServletResponse response, Object handler,
				ModelAndView modelAndView) throws Exception {
			// TODO Auto-generated method stub
		}
	
	@Override
	public boolean preHandle(HttpServletRequest request,
				HttpServletResponse response, Object handler) throws Exception {
			//获取用户请求的URL
			StringBuffer url = request.getRequestURL();
			//取出请求中的action名
			String action = url.substring(url.lastIndexOf("/")+1, url.lastIndexOf("."));
			//判断这个action是否是不需要进行登录验证,
			//如果不需要,就返回true,处理流程照常进行
			if(matchUrl(action.toLowerCase())){
				return true;
			}
			//进行登录验证,如果没有登录或session过期,就转到登录页
			if(request.getSession().getAttribute("user")==null){
				request.getRequestDispatcher(urls.getProperty("login") + "." + suffix).forward(request, response);
				return false;
			}
			return true;
	}
	
	/**
	 * 用来判断url是否需要添加登录验证
	 */
	private boolean matchUrl(String action){
			Pattern p = Pattern.compile(urls.getProperty("urlMapping"));
			Matcher m = p.matcher(action);
			return m.matches();
	}
}

  为了增加灵活性,action的后缀和需要过滤的url已经登录页面,都使用依赖注入。在appliccationContext.xml首先需要添加拦截器的声明,它和一个普通的声明没有任何区别:
	<bean id="loginInterceptor" class="cn.zxm.interceptor.LoginInterceptor">
	    <property name="urls">
	        <props>
	            <prop key="login">/order/loginDispatcher</prop>
	            <prop key="urlMapping">[0-9a-zA-Z]*(login|register)[0-9a-zA-Z]*</prop>
	        </props>
	    </property>
	    <property name="suffix" value="do"/>
	</bean>

  接下来的工作就是在urlMapping注册拦截器:
<property name="interceptors">
			<list>
			    <ref bean="loginInterceptor"/>
				<ref bean="openSessionInViewInterceptor" />
			</list>
	</property>

  注意拦截器是有顺序的,loginInterceptor显然应该在openSessionInViewInterceptor之前。用户可以在没有登录的情况下,访问一个合法的url,例如/search_items.do,如果能返回到登录页面,证明拦截器配置成功。
  最后,为了对这个示例有更为清楚地理解,我们现在列出完整的struts-order-config.xml文件内容:
<?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>
		<form-beans>
			<form-bean name="userForm" type="cn.zxm.order.form.UserForm"/>
			<form-bean name="orderForm" type="cn.zxm.order.form.OrderForm"/>
			<form-bean name="itemForm" type="cn.zxm.order.form.ItemForm"/>
		</form-beans>
		<global-forwards>
			<forward name="login" path="/loginDispatcher.do"/>
			<forward name="welcome" path="/orderListDispatcher.do"/>
			<forward name="viewOrderItem" path="/orderDispatcher.do"/>
		</global-forwards>
		<action-mappings>
			<action path="/*Dispatcher" forward="/{1}.jsp"/>
			<action path="/*Action" input="/{1}.jsp" 
type="org.springframework.web.struts.DelegatingActionProxy" 
name="userForm">
		    <forward name="fail" path="/{1}Dispatcher.do"/>
			</action>
			<action path="/commit_*" 
type="org.springframework.web.struts.DelegatingActionProxy" input="/{1}.jsp" 
name="{1}Form">
		    		<forward name="success" path="/search_{1}s.do"/>
			</action>
			<action path="/search_items" 
type="org.springframework.web.struts.DelegatingActionProxy" 
name="orderForm"/>
		 <action path="/search_orders" 
type="org.springframework.web.struts.DelegatingActionProxy" name="userForm"/>
		</action-mappings>
</struts-config>

  9.3.3 预期的效果
  上面的例子虽然很简单,但是让我们感到还是很杂乱,我们暂时不必理会这个问题,我们需要做的是看看通过整合,是否达到我们预期的效果。我们整合的目的之一就是通过使用Spring的IoC解除代码的耦合,很显然,我们达到了这个目的。第二个,使用Hibernate,在页面端也能够使用延迟加载,同时在请求时保证共享Hibernate Session,以达到提高资源利用率的目的,这些通过使用OpenSessionInVew模式,也得到了很好的解决。此外,我们使用了声明式事务,以一种极为简洁的方式实现了这个平时需要硬编码才能实现的功能。统一对登录信息的判断,是应用中经常遇到的问题,在这里,通过使用拦截器,我们也提供了一种简洁的实现,而且它是可插拔的。
  在整合的过程中,通过将Action交给Spring托管,我们得以在web层使用Spring提供的优秀技术,IoC、AOP等,使得web层的功能更单一,结构更清晰,代码也变得更为简洁。当然这种简洁不仅仅限于web层,同样包括持久层,这是Hibernate的威力。通过三者的结合,我们就可以各取所长,从而开发出一个强大而又容易维护的系统。
  9.4 小结
  Struts是一个单纯的web框架,没有涉及业务层和持久层的东西,因此在持久层实现中,可以自由选择优秀的持久化实现,而不用担心对web层有什么影响。Hibernate是业内顶级的ORM框架,可以作为持久层实现的基础。Struts本身没有IoC和AOP功能,而Spring正是优秀的IoC和AOP框架。因此在一个应用中,采用Spring+Hibernate+Struts的模式就有必要了。

  在这个模型中,Struts负责对web请求的处理和响应,Hibernate提供持久化实现,Spring则提供bean的管理,通过AOP提供对横切需求的处理。除此之外,Spring还可以提供一些企业级的服务,例如声明式事务。

  实现这个模型的目的,是去耦合的需要,也是扩展的需要,因此三者都很容易进行扩展,同时也是维护的需要,因为它们良好的设计,都很容易维护。由于它们比较流行,因此招聘一个熟悉它们的人也很容易。

  我们通过一个购物车的例子演示了怎样进行整合。用户需要登录,如果没有注册,还要进行注册。一个用户需要自己填写商品信息,然后把它放入购物车,最后提交购物车,生成定单。用户可以查询自己的所有订单,也可以查询单个订单内包含的信息,比如订单内的所有商品。这是一个很常见的例子。
  我们演示了怎样通过Interceptor使用Spring提供的openSessionInVew模式,从而可以在页面上使用Hibernate的延迟加载。这需要借助于SpringMVC来实现,这种整合方式不同于上一章所提到的三种方式,它要更为复杂,也更为强大。SpringMVC代理了Struts的ActionServlet,从而可以通过Spring拦截HTTP请求,这是通过Spring的拦截器实现的,我们给出了一个用拦截器判断用户是否登录的例子。
  可以使用通配符简化Struts的配置,这也要求我们对页面名称和Action路径遵循一定的命名规范。这里同样演示了怎样使用Struts的模块化开发,针对中文乱码问题的处理等。
持久层实现,采用Hibernate,我们使用Spring对Hibernate的封装,简化了持久层的操作。

  在整合的过程中,通过将Action交给Spring托管,我们得以在web层使用Spring提供的优秀技术,IoC、AOP等,使得web层的功能更单一,结构更清晰,代码也变得更为简洁。在持久层的代码也同样的简洁。
  • 大小: 9.7 KB
  • 大小: 45.2 KB
  • 大小: 81.3 KB
  • 大小: 57.2 KB
  • 大小: 71.5 KB
  • 大小: 65.3 KB
8
1
分享到:
评论
6 楼 e421083458 2013-04-23  
人才啊! 
5 楼 wxq594808632 2009-04-02  
自从学了ssh之后一直没用。现在都快忘完了。赶紧复习下。。
4 楼 慕容轩 2008-11-04  
此贴要浮上来!
3 楼 ALLEN仔 2008-10-29  
不错,学习下
2 楼 heshencao 2008-10-29  
能把源码共享一下吗?
1 楼 avanry 2008-10-29  
高手,讲的非常细
不过现在都是struts2.0了

相关推荐

    spring hibernate struts ajax整合项目源代码

    整合的四大框架项目 spring hibernate struts ajax整合项目源代码 spring hibernate struts ajax整合项目源代码

    struts spring hibernate 登陆 SSH整合分页功能

    struts spring hibernate 登陆 SSH整合分页功能 SSH整合分页struts spring hibernate 登陆 SSH整合分页功能 SSH整合分页struts spring hibernate 登陆 SSH整合分页功能 SSH整合分页

    spring_struts_hibernate整合开发书籍

    《Spring+Struts+Hibernate整合开发》是一本深入讲解企业级Java应用开发的书籍,它主要聚焦于三大著名开源框架——Spring、Struts和Hibernate的集成与应用。这些框架是Java Web开发中的基石,广泛应用于各种复杂的...

    spring hibernate struts2 整合jar包

    标题中的"spring hibernate struts2 整合jar包"指的是一个用于构建Java Web应用程序的集成框架,这个框架集成了Spring、Hibernate和Struts2这三个关键的开源技术。Spring是全面的企业级应用开发框架,提供了依赖注入...

    Spring+Struts2+hibernate+Redis整合

    在IT行业中,SSH(Spring、Struts2、Hibernate)是一个经典的Java Web开发框架组合,而Redis则是一个高性能的键值存储系统,常用于缓存和数据持久化。将SSH与Redis整合,可以提升应用程序的性能和响应速度。下面将...

    Struts1+Spring2+Hibernate2整合详细例子

    整合Struts1、Spring2和Hibernate2的过程主要包括以下步骤: 1. **配置环境**:确保所有依赖库已添加到项目的类路径中,如struts-core.jar、spring-framework.jar和hibernate-core.jar等。 2. **配置Struts1**:...

    hibernate struts2 和spring的整合项目

    【标题】:“Hibernate、Struts2与Spring的整合项目” 【描述】:“这是一个将Hibernate、Struts2和Spring三大框架集成在一起的项目示例。它展示了如何在实际开发中有效地结合这三个强大的Java技术,实现数据持久层...

    最新版本的Struts2+Spring4+Hibernate4框架整合

    整合使用最新版本的三大框架(即Struts2、Spring4和Hibernate4),搭建项目架构原型。 项目架构原型:Struts2.3.16 + Spring4.1.1 + Hibernate4.3.6。 此外,还有:log4j、slf4j、junit4、ehcache等知识点。 项目...

    spring hibernate struts整合

    在Spring和Struts整合时,Spring可以作为Action的依赖注入容器,提供业务对象给Struts,从而减少代码耦合。 4. **整合过程**: - **配置Spring**:创建Spring的配置文件,如`applicationContext.xml`,定义Bean的...

    Spring+Struts+Hibernate比较详细的整合配置方案

    2. **Struts整合** - 配置Struts的`struts-config.xml`文件,声明Action和ActionForm,设置Action的Forward规则。 - 创建ActionForm类,如`LoginForm`,定义表单字段,与JSP页面中的表单元素对应。 - 创建Action...

    Struts Spring Hibernate整合实践

    1. **Spring 与 Struts 整合**: - **ActionSupport 方式**:将 Struts Action 类继承自 Spring 的 ActionSupport 类,但这样会导致 Struts Action 与 Spring 紧耦合,不利于后期扩展或更换框架。 - **...

    spring+hibernate+struts整合jar包

    在本整合中,Spring 2.5.6版本主要作为业务逻辑的管理和协调者,它可以通过配置文件管理Bean的生命周期,同时与Hibernate和Struts进行无缝集成。 Hibernate 3.2是一个流行的ORM(对象关系映射)工具,它消除了...

    spring hibernate struts2 整合

    在IT行业中,SSH(Struts2、Hibernate、Spring)是一个非常经典的Java Web开发框架整合,被誉为"企业级应用开发的黄金组合"。本教程将详细阐述如何将这三个组件结合在一起,构建一个完整的CRUD(创建、读取、更新、...

    spring hibernate struts 整合

    整合Spring、Hibernate和Struts,可以创建出高效、模块化且易于维护的企业级Web应用。这个过程通常被称为SSH整合,它是Java开发中的经典组合,尤其是在早期的Java EE项目中非常流行。 首先,Spring作为核心框架,...

    SSH整合源码(Struts+Spring+Hibernate整合用户注册例子)

    SSH整合,全称为Struts、Spring和Hibernate的集成,是一种常见的Java Web开发框架组合,用于构建高效、可维护的企业级应用程序。在这个例子中,我们看到的是一个基于SSH的用户注册功能的实现,使用的开发工具是...

    spring hibernate struts整合开发实例

    【Spring、Hibernate、Struts整合开发】 在Java应用开发中,Spring、Hibernate和Struts是三个非常重要的框架,它们各自负责不同的职责。Spring作为一个轻量级的容器,提供了依赖注入和面向切面编程(AOP)等功能;...

    struts+spring+hibernate三大框架整合

    Spring整合Struts主要有三种方式: 1. **使用Spring的ActionSupport**:Action类直接继承自Spring的ActionSupport,通过`super.getWebApplicationContext()`获取Spring上下文,然后通过`ApplicationContext.getBean...

    struts2 spring hibernate整合的简单登录代码

    Struts2、Spring和Hibernate是Java Web开发中的三大框架,它们各自负责不同的职责:Struts2作为MVC框架处理请求和展示,Spring提供依赖注入和事务管理,Hibernate则作为ORM框架处理数据库操作。将这三个框架整合在...

    struts2 spring hibernate 整合

    在Struts2.1.6、Spring2.5.6和Hibernate3.3.1的版本中,整合这三个框架时,开发者通常会使用全注解的方式来简化配置。全注解开发意味着在类和方法上使用注解来声明和配置组件,而不是编写XML配置文件。 首先,开发...

    集成Hibernate + Spring + Struts + Mybatis

    集成Hibernate + Spring + Struts + Mybatis,jdk 32的版本

Global site tag (gtag.js) - Google Analytics