使用struts+spring+hibernate 组装web应用
2004年9月16日  作者:Totodo  Matrix-与Java共舞    
图 3. 对象使用了 IoC。 A 包含了接受B,C的 setter方法 , 这同样达到了 由A创建B,C的目的。

建立我们的业务服务对象(Building Our Business Service Objects)


Business Object中的Setter方法接受的是接口,这样我们可以很松散的定义对象实现,然后注入。 在我们的案例中,我们将用一个business service object接收一个DAO,用它来控制domain objects的持久化。 由于在这个例子中使用了Hibernate,我们可以很方便的用其他持久框架实现 同时通知Spring 有新的DAO可以使用了。

在面向接口的编程中,你会明白 “注射依赖”模式是怎样松散耦合你的业务逻辑和持久机制的:)。



下面是一个接口business service object,DAO代码片段:


代码:

public interface IOrderService {

  public abstract Order saveNewOrder(Order order)

    throws OrderException,

           OrderMinimumAmountException;

 

  public abstract List findOrderByUser(

                                     String user)

                           throws OrderException;

 

  public abstract Order findOrderById(int id)

                           throws OrderException;

 

  public abstract void setOrderDAO(

                             IOrderDAO orderDAO);

}

 

注意到这段代码里有一个 setOrderDao(),它就是一个DAO Object设置方法(注射器)。 但这里并没有一个getOrderDao的方法,这不必要,因为你并不会在外部访问这个orderDao。这个DAO Objecte将被调用,和我们的persistence layer 通信。我们将用Spring把DAO Object 和 business service object搭配起来的。因为我们是面向接口编程的,所以并不需要将实现类紧密的耦合在一起。



接下去我们开始我们的DAO的实现类进行编码。 既然Spring已经有对Hibernate的支持,那这个例子就直接继承HibernateDaoSupport类了,这个类很有用,我们可以参考HibernateTemplate(它主要是针对HibernateDaoSupport的一个用法,译注:具体可以查看Srping 的API)。 下面是这个DAO接口代码:

代码:
public interface IOrderDAO {
  public abstract Order findOrderById(
                                    final int id);
 
  public abstract List findOrdersPlaceByUser(
                           final String placedBy);
  public abstract Order saveOrder(
                               final Order order);
}


我们仍然要给我们持久层组装很多关联的对象,这里包含了HibernateSessionFactory 和TransactionManager。 Spring 提供了一个 HibernateTransactionManager,他用线程捆绑了一个Hibernate Session,用它来支持transactions(请查看ThreadLocal) 。

下面是HibernateSessionFactory 和 HibernateTransactionManager:的配置:

代码:
<bean />
  </property>
  </bean>



可以看出:每个对象都可以在Spring 配置信息中用<bean>标签引用。在这里,mySessionFactory引用了HibernateSessionFactory,而myTransactionManager引用了HibernateTransactionManage。 注意代码中myTransactionManger Bean有个sessionFactory属性。 HibernateTransactionManager有个sessionFactory setter 和 getter方法,这是用来在Spring启动的时候实现“依赖注入” (dependency injection)的。 在sessionFactory 属性里 引用mySessionFactory。这两个对象在Spring容器初始化后就被组装了起来了。 这样的搭配让你从 单例(singleton objects)和工厂(factories)中解放了出来,降低了代码的维护代价。 mySessionFactory.的两个属性,分别是用来注入mappingResources 和 hibernatePropertes的。通常,如果你在Spring之外使用Hibernate,这样的设置应该放在hibernate.cfg.xml中的。 不管怎样,Spring提供了一个便捷的方式-----在Spring内部配置中并入了Hibernate的配置。 如果要得到更多的信息,可以查阅Spring API。





既然我们已经组装配置好了Service Beans,就需要把Business Service Object和 DAO也组装起来,并把这些对象配到一个事务管理器(transaction manager)里。



在Spring中的配置信息:
代码:

<!-- ORDER SERVICE -->
<bean />
  </property>
</bean>




图4 是我们对象搭建的一个提纲。 从中可以看出,每个对象都联系着Spring,并且能通过Spring注入到其他对象。把它与Spring的配置文件比较,观察他们之间的关系

使用struts+spring+hibernate 组装web应用

图 4. Spring就是这样基于配置文件,将各个Bean搭建在一起。

这个例子使用一个TransactionProxyFactoryBean,它定义了一个setTransactionManager()。 这对象很有用,他能很方便的处理你申明的事物还有Service Object。 你可以通过transactionAttributes属性来定义怎样处理。 想知道更多还是参考TransactionAttributeEditor吧。

TransactionProxyFactoryBean 还有个setter. 这会被我们 Business service object(orderTarget)引用, orderTarget定义了 业务服务层,并且它还有个属性,由setOrderDAO()引用。这个属性



Spring 和Bean 的还有一点要注意的: bean可以以用两种方式创造。 这些都在单例模式(Sington)和原型模式(propotype)中定义了。 默认的方式是singleton,这意味着共享的实例将被束缚。 而原形模式是在Spring用到bean的时候允许新建实例的。当每个用户需要得到他们自己Bean的Copy时,你应该仅使用prototype模式。(更多的请参考设计模式中的单例模式和原形模式)

提供一个服务定位器(Providing a Service Locator)
既然我们已经将我们的Serices和DAO搭配起来了。我们需要把我们的Service显示到其他层。 这个通常是在Struts或者Swing这层里编码。一个简单方法就是用 服务定位器返回给Spring context 。当然,可以通过直接调用Spring中的Bean来做。

下面是一个Struts Actin 中的服务定位器的一个例子。
代码:

public abstract class BaseAction extends Action {
 
  private IOrderService orderService;
 
  public void setServlet(ActionServlet
                                 actionServlet) {
    super.setServlet(actionServlet);
    ServletContext servletContext =
               actionServlet.getServletContext();
 
    WebApplicationContext wac =
      WebApplicationContextUtils.
         getRequiredWebApplicationContext(
                                 servletContext);
 
      this.orderService = (IOrderService)
                     wac.getBean("orderService");
  }
 
  protected IOrderService getOrderService() {
    return orderService;
  }
}
 

UI 层配置 (UI Layer Configuration)

这个例子里UI层 使用了Struts framework. 这里我们要讲述一下在给程序分层的时候, 哪些是和Struts部分的。我们就从一个Struts-config.xml文件中的Action的配置信息开始吧。
代码:

struts-config.xml file.

<action path="/SaveNewOrder"
    type="com.meagle.action.SaveOrderAction"
    name="OrderForm"
    scope="request"
    validate="true"
    input="/NewOrder.jsp">
  <display-name>Save New Order</display-name>
  <exception key="error.order.save"
    path="/NewOrder.jsp"
    scope="request"
    type="com.meagle.exception.OrderException"/>
  <exception key="error.order.not.enough.money"
    path="/NewOrder.jsp"
    scope="request"
    type="com.
          meagle.
          exception.
          OrderMinimumAmountException"/>
  <forward name="success" path="/ViewOrder.jsp"/>
  <forward name="failure" path="/NewOrder.jsp"/>
</action>

SaveNewOrder 这个Action是用来持久化UI层里的表单提交过来Order的。这是Struts中一个很典型的Action; 注意观察这个Action中exception配置,这些Exceptions也在Spring 配置文件(applicationContext-hibernate.xml)中配置了(就在 business service object 的transactionAttributes属性里)。 当异常在业务层被被抛出时,我们可以控制他们,并适当的显示给UI层。

第一个异常,OrderException,在持久层保存order对象失败的时候被触发。这将导致事物回滚并且通过BO把异常回传到Struts这一层。

第二个异常,OrderMinimumAmountException也同第一个一样。





搭配整和的最后一步 通过是让你显示层和业务层相结合。这个已经被服务定位器(service locator)实现了(前面讨论过了), 这里服务层作为一个接口提供给我们的业务逻辑和持久层。



SaveNewOrder Action 在Struts中用一个服务定位器(service locator)来调用执行业务方法的。 方法代码如下:



代码:
public ActionForward execute(

  ActionMapping mapping,

  ActionForm form,

  javax.servlet.http.HttpServletRequest request,

  javax.servlet.http.HttpServletResponse response)

  throws java.lang.Exception {

 

  OrderForm oForm = (OrderForm) form;

 

  // Use the form to build an Order object that

  // can be saved in the persistence layer.

  // See the full source code in the sample app.

 

  // Obtain the wired business service object

  // from the service locator configuration

  // in BaseAction.

  // Delegate the save to the service layer and

  // further upstream to save the Order object.

  getOrderService().saveNewOrder(order);

 

  oForm.setOrder(order);

 

  ActionMessages messages = new ActionMessages();

  messages.add(

      ActionMessages.GLOBAL_MESSAGE,

            new ActionMessage(

      "message.order.saved.successfully"));

 

  saveMessages(request, messages);

 

  return mapping.findForward("success");

}


总结

这篇文章在技术和构架方面掩盖了很多低层的基础信息, 文章的主要的意图在于让你意识到如何给你应用程序分层。 分层可以“解耦”你的代码——允许新的组件被添加进来,而且让你的代码易于维护。 这里用到的技术只是专注于把“解偶”做好。 不管怎样,使用这样的构架可以让你用其他技术代替现在的层。 例如,你可能不使用Hibernate实现持久化。既然你在DAO中面向接口的编程的,所以你完全可以用iBATIS来代替。或者,你也可能想用Struts外的其他的技术或者框架替换现在的UI层(转换久层,实现层并不应该直接影响到你的业务逻辑和业务服务层)。 用适当的框架搭建你的Web应用,其实也不是一件烦琐的工作,更主要的是它“解耦”了你程序中的各个层。





后记:

看完这篇文章后,只是觉得很喜欢,于是就翻译了,当然同时也准备着挨大家扔来的鸡蛋:)。

这篇文章里并没有太多的技术细节,和详细的步骤。如果你从未使用过这些框架而在运行实例程序遇上困难的话,可以到CSDN论坛Java Open Source版发贴,我一定会详细解答的(啊哦,这不算做广告吧?),

文章是从一个构架的角度讲述了如何搭配现有的开源框架进行分层, 有太多的术语我都不知道怎么表达,而且可能有很多语句存在错误。如果影响了你的阅读,请你直接点原文地址,我同时也象你说声抱歉。



作者简介:Mark Eagle 高级软件工程师,亚特兰大。
翻 译:Totodo,软件工程师
参考资源:
参与论坛讨论:http://www.matrix.org.cn/forum.asp
更多技术文章:http://www.matrix.org.cn/article.asp
Matrix java门户:http://www.matrix.org.cn
原文地址:http://www.matrix.org.cn/article/1034.html
任何获得许可转载此文章,须在显著位置标明Matrix的原文地址,并做链接至原文页面,查看详细的版权说明

相关文章: