领域对象层( Domain Object Layer ) 因为这些对象将和所有层交互,这也许是一个开始编码的好地方。这个简单的领域模型将包括一个代表一份订单( order )的对象和一个代表一个订单项( line item for an order )的对象。订单( order )对象将和一组订单项( a collection of line item )对象有一对多( one-to-many )的关系。例子代码在领域层有两个简单的对象: com.meagle.bo.Order.java: 包括一份订单( oder )的概要信息; com.meagle.bo.OrderLineItem.java: 包括一份订单( order )的详细信息; 考虑一下为你的对象选择包名,它将反映你的应用程序是怎样分层的。例如:简单应用的领域对象( domain objects )可以放进com.meagle.bo包[译者注:bo-business object?]。更多专门的领域对象将放入在com.meagle.bo下面的子包里。业务逻辑在com.meagle.service包里开始打包,DAO对象放进com.meagle.service.dao.hibernate包。对于forms和actions的表现类( presentation classes )分别放入com.meagle.action 和 com.meagle.forms包。准确的包命名为你的类提供的功能提供一个清楚的区分,使当故障维护时更易于维护,和当给应用程序增加新的类或包时提供一致性。持久层配置( Persistence Layer Configuration ) 用Hibernate设置持久层涉及到几个步骤。第一步是进行配置持久化我们的领域业务对象( domain business objects )。因为我们用于领域对象( domain objects )持久化的Hibernate和POJOs一起工作( 此句原文:Since Hibernate works with POJOs we will use our domain objects for persistence. ),因此,订单和订单项对象包括的所有的字段的都需要提供getter和setter方法。订单对象将包括像ID、用户名、合计、和订单项这样一些字段的标准的JavaBean格式的setter和getter方法。订单项对象将同样的用JavaBean的格式为它的字段设置setter和getter方法。 Hibernate在XML文件里映射领域对象到关系数据库。订单和订单项对象将有两个映射文件来表达这种映射。有像XDoclet( http://xdoclet.sourceforge.net/ )这样的工具来帮助这种映射。Hibernate将映射领域对象到这些文件: Order.hbm.xml OrderLineItem.hbm.xml 你可以在WebContent/WEB-INF/classes/com/meagle/bo目录里找到这些生成的文件。配置Hibernate SessionFactory( http://www.hibernate.org/hib_docs/api/net/sf/hibernate/SessionFactory.html )使它知道是在和哪个数据库通信,使用哪个数据源或连接池,加载哪些持久对象。SessionFactory提供的Session( http://www.hibernate.org/hib_docs/api/net/sf/hibernate/Session.html )对象是Java对象和像选取、保存、更新、删除对象这样一些持久化功能间的翻译接口。我们将在后面的部分讨论Hibernate操作Session对象需要的SessionFactory配置。 业务层配置( Business Layer Configuration ) 既然我们已经有了领域对象( domain objects ),我们需要有业务服务对象来执行应用逻辑、执行向持久层的调用、获得从用户接口层( UI layer )的请求、处理事务、处理异常。为了将所有这些连接起来并且易于管理,我们将使用Spring框架的bean管理方面( bean management aspect )。Spring使用“控制反转”( IoC ),或者“setter依赖注入”来把这些对象连好,这些对象在一个外部的XML文件中被引用。“控制反转”是一个简单的概念,它允许对象接受其它的在一个高一些的层次被创建的对象。使用这种方法,你的对象从必须创建其它对象中解放出来并降低对象耦合。 这儿是个不使用IoC的对象创建它的从属对象( object creating its dependencies without IoC )的例子,这导致紧的对象耦合: 图2:没有使用IoC的对象组织。对象A创建对象B和C( http://www.onjava.com/onjava/2004/04/07/graphics/nonioc.gif )。 这儿是一个使用IoC的例子,它允许对象在一个高一些层次被创建和传进另外的对象,所以另外的对象能直接使用现成的对象?[译者注:另外的对象不必再亲自创建这些要使用的对象]( allows objects to be created at higher levels and passed into objects so that they can use the implementations directly ): 图3:对象使用IoC组织。对象A包含setter方法,它们接受到对象B和C的接口。这也可以用对象A里的接受对象B和C的构建器完成( http://www.onjava.com/onjava/2004/04/07/graphics/ioc.gif )。建立我们的业务服务对象( Building Our Business Service Objects ) 我们将在我们的业务对象中使用的setter方法接受的是接口,这些接口允许对象的松散定义的实现,这些对象将被设置或者注入。在我们这个例子里我们将使我们的业务服务对象接受一个DAO去控制我们的领域对象的持久化。当我们在这篇文章的例子中使用Hibernate( While the examples in this article use Hibernate ),我们可以容易的转换到一个不同的持久框架的实现,通知Spring使用新的实现的DAO对象。你能明白编程到接口和使用“依赖注入”模式是怎样宽松耦合你的业务逻辑和你的持久化机制的。 这儿是业务服务对象的接口,它是一个DAO对象依赖的桩。( Here is the interface for the business service object that is stubbed for a DAO object dependency: )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); } 注意上面的代码有一个为DAO对象准备的setter方法。这儿没有一个getOrderDAO方法因为它不是必要的,因为不太有从外面访问连着的OrderDAO对象的需要。DAO对象将被用来和我们的持久层沟通。我们将用Spring把业务服务对象和DAO对象连在一起。因为我们编码到接口,我们不会紧耦合实现。下一步是写我们的DAO实现对象。因为Spring有内建的对Hibernate的支持,这个例子DAO将继承HibernateDaoSupport( http://www.springframework.org/docs/api/org/springframework/orm/hibernate/support/HibernateDaoSupport.html )类,这使得我们容易取得一个到HibernateTemplate( http://www.springframework.org/docs/api/org/springframework/orm/hibernate/HibernateTemplate.html )类的引用,HibernateTemplate是一个帮助类,它能简化Hibernate Session的编码和处理HibernateExceptions。这儿是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配置文件里直接完成。Spring提供一个HibernateTransactionManager( http://www.springframework.org/docs/api/org/springframework/orm/hibernate/HibernateTransactionManager.html ),它将从工厂绑定一个Hibernate Session到一个线程来支持事务( 见ThreadLocal( http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ThreadLocal.html )获取更多的信息 )。这儿是HibernateSessionFactory和HibernateTransactionManager的Spring配置。 class="org.springframework.orm.hibernate.LocalSessionFactoryBean"> com/meagle/bo/Order.hbm.xml com/meagle/bo/OrderLineItem.hbm.xml net.sf.hibernate.dialect.MySQLDialect false C:/MyWebApps/.../WEB-INF/proxool.xml spring class="org.springframework.orm.hibernate.HibernateTransactionManager"> 每一个对象能被Spring配置里的一个标记引用。在这个例子里,bean “mySessionFactory”代表一个HibernateSessionFactory,bean “myTransactionManager”代表一个Hibernate transaction manager。注意transactionManger bean有一个叫作sessionFactory的属性元素。HibernateTransactionManager有一个为sessionFactory准备的setter和getter方法,它们是用来当Spring容器启动时的依赖注入。sessionFactory属性引用mySessionFactory bean。这两个对象现在当Spring容器初始化时将被连在一起。这种连接把你从为引用和创建这些对象而创建singleton对象和工厂中解放出来,这减少了你应用程序中的代码维护。mySessionFactory bean有两个属性元素,它们翻译成为mappingResources 和 hibernatePropertes准备的setter方法。通常,如果你在Spring之外使用Hibernate,这个配置将被保存在hibernate.cfg.xml文件中。不管怎样,Spring提供了一个便捷的方式--在Spring配置文件中合并Hibernate的配置。获得更多的信息查阅Spring API( http://www.springframework.org/docs/api/index.html )。既然我们已经配置了我们的容器服务beans和把它们连在了一起,我们需要把我们的业务服务对象和我们的DAO对象连在一起。然后,我们需要把这些对象连接到事务管理器。这是在Spring配置文件里的样子:class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">PROPAGATION_REQUIRED,readOnly,-OrderExceptionPROPAGATION_REQUIRED,-OrderException class="com.meagle.service.spring.OrderServiceSpringImpl"> class="com.meagle.service.dao.hibernate.OrderHibernateDAO"> 图4是我们已经连在一起的东西的一个概览。它展示了每个对象是怎样相关联的和怎样被Spring设置进其它对象中。把这幅图和示例应用中的Spring配置文件对比查看它们之间的关系。图4:这是Spring怎样将在这个配置的基础上装配beans( http://www.onjava.com/onjava/2004/04/07/graphics/spring_wiring.gif )。 这个例子使用一个TransactionProxyFactoryBean,它有一个为我们已经定义了的事务管理者准备的setter方法。这是一个有用的对象,它知道怎样处理声明的事务操作和你的服务对象。你可以通过transactionAttributes属性定义事务怎样被处理,transactionAttributes属性为方法名定义模式和它们怎样参与进一个事务。获得更多的关于在一个事务上配置隔离层和提交或回滚查阅TransactionAttributeEditor( http://www.springframework.org/docs/api/org/springframework/transaction/interceptor/TransactionAttributeEditor.html )。 TransactionProxyFactoryBean( http://www.springframework.org/docs/api/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.html )类也有一个为一个target准备的setter,target将是一个到我们的叫作orderTarget的业务服务对象的引用( a reference )。 orderTarget bean定义使用哪个业务服务对象并有一个指向setOrderDAO()的属性。orderDAO bean将居于这个属性中,orderDAO bean是我们的和持久层交流的DAO对象。 还有一个关于Spring和bean要注意的是bean能以两种模式工作。这两种模式被定义为singleton和prototype。一个bean默认的模式是singleton,意味着一个共享的bean的实例将被管理。这是用于无状态操作--像一个无状态会话bean将提供的那样。当bean由Spring提供时,prototype模式允许创建bean的新实例。你应当只有在每一个用户都需要他们自己的bean的拷贝时才使用prototype模式。提供一个服务定位器( Providing a Service Locator ) 既然我们已经把我们的服务和我们的DAO连起来了,我们需要把我们的服务暴露给其它层。通常是一个像使用Struts或Swing这样的用户接口层里的代码来使用这个服务。一个简单的处理方法是使用一个服务定位器模式的类从一个Spring上下文中返回资源。这也可以靠引用bean ID通过Spring来直接完成。 这儿是一个在Struts Action中怎样配置一个服务定位器的例子: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 Layer Configuration ) 示例应用的用户接口层使用Struts框架。这儿我们将讨论当为一个应用分层时和Struts相关的部分。让我们从在struts-config.xml文件里检查一个Action配置开始。 type="com.meagle.action.SaveOrderAction" name="OrderForm" scope="request" validate="true" input="/NewOrder.jsp"> Save New Order path="/NewOrder.jsp" scope="request" type="com.meagle.exception.OrderException"/> path="/NewOrder.jsp" scope="request" type="com.meagle.exception.OrderMinimumAmountException"/> SaveNewOrder Action被用来持久化一个用户从用户接口层提交的订单。这是一个典型的Struts Action;然而,注意这个action的异常配置。这些Exceptions为我们的业务服务对象也在Spring 配置文件(applicationContext-hibernate.xml)中配置了( 在transactionAttributes属性里 )。当这些异常被从业务层掷出我们能在我们的用户接口里恰当的处理它们。第一个异常,OrderException,当在持久层里保存订单对象失败时将被这个action使用。这将引起事务回滚和通过业务对象传递把异常传回给Struts层。OrderMinimumAmountException,在业务对象逻辑里的一个事务因为提交的订单达不到最小订单数量而失败也将被处理。然后,事务将回滚和这个异常能被用户接口层恰当的处理。 最后一个连接步骤是使我们的表现层和我们的业务层交互。这已经通过使用前面讨论的服务定位器来完成了。服务层充当一个到我们的业务逻辑和持久层的接口。这儿是 Struts中的SaveNewOrder Action可能怎样使用一个服务定位器调用一个业务方法: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( http://www.ibatis.com/ ),作为一个替代是显而易见的。或者你可能用不同于Struts的框架替代你的UI层。改变UI层的实现不会直接影响你的业务逻辑层或者你的持久层。替换你的持久层不会影响你的UI逻辑或业务服务层。集成一个web应用其实也不是一件烦琐的工作,靠解耦你的各应用层和用适当的框架组成它,它能变得更容易处理。Mark Eagle 是一位在MATRIX智囊团的高级软件工程师, Inc. in Atlanta, GA。
原文出自:http://blog.csdn.net/mshust2006/archive/2006/08/07/1033911.aspx
最近打算把SSH架构好好缕缕(以前断断续续学过和使用过一些),因为现在SSH(Struts +Spring+Hibernate)架构可以说是开发Web项目的一种轻量级的很好的解决方案,也比较成熟了,实际上在这三者(三个开源项目), Struts还是处理前端的运用,Hibernate处理持久化工资,而Spring像个联合剂,它通过其applicationContext.xml 配置文件,根据Ioc模式将前两者需要的类实例化并提供给需要的地方(就是提供bean),当然其还可以在配置文件中提供声明性事物处理,可以极大地节约代码(Hibernate可不可以了??)。下面来详细的说明一下:
我们先看看SS的结合,其结合点在于将Struts中Action的实例生成不再由Struts自己负责,而交于Spring容器去管理,需要修改的地方是首先在Struts的配置文件上用plug-in加上Spring的配置文件applicationContext.xml,然后在在action的配置地方的type属性统统设为org.springframework.web.struts.DelegatingActionProxy,这样Action的具体实现就有Spring去负责了,在Spring的配置文件中把相应的Action设为bean,交由Spring容器去处理。在这个地方有一个要注意的问题,就是为了使Struts的配置文件方便找到Spring的配置文件,我们把applicationContext.xml放在 WEB-INF下,而不是放在classpath下面了,这样如果在程序中要显示调用ApplicationContext对象,就不能直接实例化它了,我们可以通过web context来取得它,具体做法是在web.xml中可以加一个Listener或是Servlet的声明(两者取一就可),Web容器会自动加载 /WEB-INF/applicationContext.xml初始化ApplicationContext实例,配置完成之后,即可通过 WebApplicationContextUtils.getWebApplicationContext
方法在Web应用中获取ApplicationContext引用。
而对于Spring和Hibernate的结合点在于关于Hibernate的SessionFactory的配置交于了Spring,即我们可以不要hibernate.cfg.xml,而将sessionFactory的配置作为bean来交给Spring的配置文件,当然在这里SessionFactory的Class用org.springframework.orm.hibernate3.LocalSessionFactoryBean,关于数据库的登陆信息可以单独作为一个bean,如dataSource,然后在sessionFactory中引用。Spring对Hibernate提供大量的封装好的类,如HibernateDaoSupport,这样我们在使用是甚至不用先生成SessionFactory,再生成Session等等操作,Spring 都提供了封装;还有很重要的一点就是在事物维持上,用Spring的声明性事物会更简单容易些,也更灵活。
以上简要的分析了以下三者整合的一些结合点和具体的使用方法。
另外今天在看《深入简出Hibernate》 一书时,对于其前面提到的一些设计模式感觉上还是有不少共鸣的,如DAO模式,它可分成Domain Object和Data Accessor两种模式的结合,Domain Object实际上就是我们在实际项目业务中所提炼出的一些POJO,而对于Data Accessor是对POJO的操作,前者实现了业务数据的对象化封装,后者是实现了数据访问和业务逻辑的分离,都是很好的解耦方法。另外对于 Proxy,Decorator,Dynamic Proxy模式也分析的比较好,下次再就它们写些心得(Dynamic Proxy还没理解透!!)。
ps:我还是比较喜欢这套SSH系列的文章的。
原文:http://blog.csdn.net/mshust2006/archive/2006/12/03/1428049.aspx
在上篇系列中我简要的谈到了一下Hibernate的基本的实用方法,在这里我觉得对于一个技术(不论是开源项目或是行业中的某种标准技术)我们的认知是可以分为四个层次: 第一个层次是“知道,了解”,它的要求仅仅是我们听说过这个技术,知道它是干什么的或是看过几篇文章,可以跟人简单的侃一侃;第二个层次是“会用”,就是能够就这个技术熟练的写出"hello world",掌握了其基本的用法,能够在实际项目中使用;第三个层次是“精通”,就是对于这个技术很熟练了,知道其内部原理和优化使用,能够解决在实际项目中使用这种技术遇到的一些“灵异问题”,算是使用这个技术的大哪了;第四个层次是“融会贯通,创新”,即不但能熟练的使用这个技术,并且能理解这个技术的优势或新异的本质,能够将这个本质应用于其它的一些实现中,并不拘泥于这个技术中。由此可见,对于一个新技术的掌握,其层次是分的很开的,层次不同,当然其掌握就不一样,所需要花的时间也不一样,所以经常看有些书封面写的多少小时完全掌握一个什么技术,其实那都仅仅是个皮毛。 好了,言归正转,我们来谈谈Hibernate中的一些较深的内容。 先来看看实体映射,简单的实体映射就不用太多说了,一个表对应一个pojo利用xxx.hbm.xml配置文件,这里有一点要注意就是pojo的 bean文件中setter或是getter方法要制定成public的,这要可以提供Hibernate的性能。对于pojo的某个属性我们可以采用自定义的数据类型,即非基本数据类型,例如List,但其实这仅仅在于从数据库表中读出时可以自动专程List,而我们在写入时还是数据库表所支持的那些数据类型。对于数据库表我们知道其还有复合主键的概念,Hibernate同样支持。另外一个是对Blob,Clob这样的大字段类型的映射,Hibernate和Oracle配合起来有些特殊,和Sql server2000则没有。 以上是简单的实体映射,像Hibernate这样的ORM解决方案来说难点在于二维的关系型数据库表和面向对象结构(面向对象的特点有封装,继承,多态)的POJO的对应,简单的映射是没有反应出面向对象的特性的。 我们可以将一个表中的字段分别映射到多个 POJO中,这样在设计的粒度上更加清晰,实际上也是实现了对于传统关系型库表的面向对象的领域划分,在xxx.hbm.xml中使用结点component。在Hibernate3中对于POJO的属性提供了延迟加载,这样可以有效地提高我们读取部分表字段的性能。对于继承关系的多个POJO, Hibernate也提供了3种映射方法:一是父类和各子类都对应一个表(这在我的一个实际项目中可以采用这种方式来解决数据库表的union的问题,当然也可以用HQL查询语言来解决),在做查询时Hibernate会自动查询所有父类和子类对应的表,这种方法不好的地方在于改动父类的属性映射,各子类的映射文件也要改动;第二种方法是子类对应的表中只包含扩展的属性字段,这样的话xxx.hbm.xml只需要一个,在配置文件中利用joined- subclass结点来引入子类的控制属性的映射;最后一种方法是只要一个大表,将父类和各子类所以得属性字段都包括进来,配置文件也是一个,需要用到 discriminator节点来声明子类辨别标示的字段,这也就是说在数据库表中要多一个字段来做标示作用,另外用到subclass节点来配合 discriminator节点。 以上是实体映射方面,下面我们来谈谈另一个比较麻烦的问题--数据关联 所以我们要明白数据关联不等同于联合查询(就是指select中的join),我们在以前的面向sql语句的数据库编程中是没有数据关联这个概念的,以前我们对于多表之间的一条sql语句的操作只限于select,但有了数据关联,我们可以同时对多表进行增,删,改。这都是由于现在是有了ORM的技术,才有了数据关联这个概念,因为对象是很容易产生关联的,想象我们以前如果要往两个表中都插入一条记录,我们要写两条sql语句,然后batch执行,现在我们只须对一个对象进行save,通过xxx.hbm.xml的配置Hibernate会自动往另一个表中插入一条数据,当然其底层实现上还是有两条sql语句的,只不果我们在上层应用时不用写出来。 说了这么多,到底数据关联具体是怎么回事了? 数据关联可以看成是两个表之间的对应的某条或几条数据之间的关联(为什么是两个表之间,而不是多个表之间了,应为多个表之间的关联到具体的都可以拆成两两关联),那这些关联可分为一对一关联,包括主键关联,外键关联;一对多关联,包括单向一对多和双向一对多(也就是一对多和多对一);多对多关联。 具体的配置和特性我们下篇blog在讲。另外还有在下一篇还要谈到Hibernate的一些高级特性和性能优化方面的问题,这也可以算成是第三个层次了,呵呵。
搜索引擎很重要. 再给楼主推荐一个地方 美河学习在线.
我怎么找不到SSH系列(四++)的,我已经进入了
http://blog.csdn.net/mshust2006/archive/2006/08/07/1033911.aspx
再译:使用struts+spring+hibernate 组装web应用
原作者: Mark Eagle 04/07/2004(http://www.onjava.com/pub/a/onjava/2004/04/07/wiringwebapps.html )
译者:孟大兴 来自学习日记( http://www.learndiary.com ) 联系方式:[email protected]
[译者前言:这篇文章由totodo在2004-09-16已经翻译过( http://www.matrix.org.cn/resource/article/1034.html ),本译文借鉴了不少他的成果。希望各位朋友指出我译文中的不足,并能根据上面的联系方式及时反馈给我,我将第一时间内在Matrix我的blog中更新译文( http://blog.matrix.org.cn/page/littlebat?entry=%E5%86%8D%E8%AF%91_%E4%BD%BF%E7%94%A8struts_spring_hibernate_%E7%BB%84%E8%A3%85web%E5%BA%94%E7%94%A8 ),争取为广大不熟悉英文的朋友提供尽可能准确的译文。另外,如果你在运行本文章的例程时碰到问题:请参考:1、原作者的网站上的答疑( http://www.onjava.com/pub/a/onjava/2004/04/07/wiringwebapps.html?page=3 );2、我的试验文中的例程的日记( http://www.learndiary.com/disDiaryContentAction.do?goalID=1468 和 http://www.learndiary.com/disDiaryContentAction.do?goalID=1470 )] 其实,就算用Java建造一个不是很烦琐的web应用程序,也不是件轻松的事情。当为一个应用程序建造一个构架时有许多事情需要考虑。从高层来说,开发者需要考虑:怎样建立用户接口( user interfaces )?在哪里处理业务逻辑?和怎样持久化应用数据。这三层每一层都有它们各自的问题需要回答。 各个层次应该使用什么技术?怎样才能把应用程序设计得松耦合和能灵活改变?构架允许层的替换不会影响到其它层吗?应用程序怎样处理容器级的服务( container level services ),比如事务处理( transactions )? 当为你的web应用程序创建一个构架时,需要涉及到相当多的问题。幸运的是,已经有不少开发者已经遇到过这类重复发生的问题,并且建立了处理这类问题的框架。一个好框架具备以下几点:减轻开发者处理复杂的问题的负担( “不重复发明轮子” );内部定义为可扩展的;有一个强大的用户群支持。框架通常能够很好的解决一方面的问题。然而,你的应用程序有几个层可能都需要它们各自的框架。就如解决你的用户接口( UI )问题时你就不应该把事务逻辑和持久化逻辑掺杂进来。例如,你不应该在控制器( controller )里面写jdbc代码,使它包含有业务逻辑,这不是控制器应该提供的功能。它应该是轻量级的,代理来自用户接口( UI )外的调用请求给其它服务于这些请求的应用层。好的框架自然的形成代码如何分布的指导。更重要的是,框架减轻开发者从头开始写像持久层这样的代码的痛苦,使他们专注于对客户来说很重要的应用逻辑。 这篇文章将讨论怎样组合几个著名的框架去做到松耦合的目的,怎样建立你的构架,怎样让你的各个应用层保持一致。富于挑战的是:组合这些框架使得每一层都以一种松耦合的方式彼此沟通,而与底层的技术无关。这篇文章将使用3种流行的开源框架来讨论组合框架的策略。表现层我们将使用Struts( http://jakarta.apache.org/struts );业务层我们将使用Spring( http://www.springframework.org/ );持久层使用Hibrenate( http://www.hibernate.org/ ).你也可以在你的应用程序中替换这些框架中的任何一种而得到同样的效果。图1展示了当这些框架组合在一起时从高层看是什么样子。 图1用Struts, Spring, 和 Hibernate框架构建的概览 ( http://www.onjava.com/onjava/2004/04/07/graphics/wiring.gif )
应用程序的分层 ( Application Layering )
大多数不复杂的web应用都能被分成至少4个各负其责的层次。这些层次是:表现层( presentation )、持久层( persistence )、业务层( business )、领域模型层( domain model )。每层在应用程序中都有明确的责任,不应该和其它层混淆功能。每一应用层应该彼此独立但要给他们之间放一个通讯接口。让我们从审视各个层开始,讨论这些层应该提供什么和不应该提供什么。 表现层 (The Presentation Layer) 在一个典型的web应用的一端是表现层。很多Java开发者也理解Struts所提供的。然而,太常见的是,他们把像业务逻辑之类的耦合的代码放进了一个org.apache.struts.Action。所以,让我们在像Struts这样一个框架应该提供什么上取得一致意见。这儿是Struts负责的: 为用户管理请求和响应;
提供一个控制器( controller )代理调用业务逻辑和其它上层处理;
处理从其它层掷出给一个Struts Action的异常;
为显示提供一个模型;
执行用户接口( UI )验证。 这儿是一些经常用Struts编写的但是却不应该和Struts表现层相伴的项目:
直接和数据库通讯,比如JDBC调用;
业务逻辑和与你的应用程序相关的验证;
事务管理;
在表现层中引入这种代码将导致典型耦合( type coupling )和讨厌的维护。 持久层 ( The Persistence Layer )
在典型web应用的另一端是持久层。这通常是使事情迅速失控的地方。开发者低估了构建他们自己的持久层框架的挑战性。一般来说,机构内部自己写的持久层不仅需要大量的开发时间,而且还经常缺少功能和变得难以控制。有几个开源的“对象-关系映射”( ORM )框架非常解决问题。尤其是,Hibernate框架为java提供了"对象-关系持久化"( object-to-relational persistence )机制和查询服务。Hibernate对那些已经熟悉了SQL和JDBC API的Java开发者有一个适中的学习曲线。Hibernate持久对象是基于简单旧式Java对象( POJO )和Java集合( Java collections )。此外,使用Hibernate并不妨碍你正在使用的IDE。下面的列表包含了你该写在一个持久层框架里的代码类型: 查询相关的信息成为对象。Hibernate通过一种叫作HQL的面向对象( OO )的查询语言或者使用条件表达式API( expressive criteria API )来做这个事情。 HQL非常类似于SQL-- 只是把SQL里的table和columns用Object和它的fields代替。有一些新的专用的HQL语言成分要学;不过,它们容易理解而且文档做得好。HQL是一种使用来查询对象的自然语言,花很小的代价就能学习它。 保存、更新、删除储存在数据库中的信息。 像Hibernate这样的高级“对象-关系”映射( object-to-relational mapping )框架提供对大多数主流SQL数据库的支持,它们支持“父/子”( parent/child )关系、事务处理、继承和多态。 这儿是一些应该在持久层里被避免的项目: 业务逻辑应该在你的应用的一个高一些的层次里。持久层里仅仅允许数据存取操作。 你不应该把持久层逻辑( persistence logic )和你的表现层逻辑( presentation logic )搅在一起。避免像JSPs或基于servlet的类这些表现层组件里的逻辑和数据存取直接通讯。通过把持久层逻辑隔离进它自己的层,应用程序变得易于修改而不会影响在其它层的代码。例如:Hebernate能够被其它持久层框架或者API代替而不会修改在其它任何层的代码。 业务层( The Business Layer ) 在一个典型的web应用程序的中间的组件是业务层或服务层。从编码的视角来看,这个服务层是最容易被忽视的一层。不难在用户接口( UI )层或者持久层里找到散布在其中的这种类型的代码。这不是正确的地方,因为这导致了应用程序的紧耦合,这样一来,随着时间推移代码将很难维护。幸好,针对这一问题有好几种Frameworks存在。在这个领域两个最流行的框架是Spring和PicoContainer,它们叫作微容器( microcontainers ),你可以不费力不费神的把你的对象连在一起。所有这些框架都工作在一个简单的叫作“依赖注入”( dependency injection )( 也通称“控制反转”( inversion of control ) )的概念上。这篇文章将着眼于Spring的为指定的配置参数通过bean属性的setter注入( setter injection )的使用。Spring也提供了一个构建器注入( constructor injection )的复杂形式作为setter注入的一个替代。对象们被一个简单的XML文件连在一起,这个XML文件含有到像事务管理器( transaction management handler )、对象工厂( object factories )、包含业务逻辑的服务对象( service objects )、和数据存取对象( DAO )这些对象的引用( references )。 这篇文章的后面将用例子来把Spring使用这些概念的方法说得更清楚一些。业务层应该负责下面这些事情:
处理应用程序的业务逻辑和业务验证;
管理事务;
预留和其它层交互的接口;
管理业务层对象之间的依赖;
增加在表现层和持久层之间的灵活性,使它们互不直接通讯;
从表现层中提供一个上下文( context )给业务层获得业务服务( business services );
管理从业务逻辑到持久层的实现。领域模型层 ( The Domain Model Layer )
最后,因为我们讨论的是一个不是很复杂的、基于web的应用程序,我们需要一组能在不同的层之间移动的对象。领域对象层由那些代表现实世界中的业务对象的对象们组成,比如:一份订单( Order )、订单项( OrderLineItem )、产品( Product )等等。这个层让开发者停止建立和维护不必要的数据传输对象( 或者叫作DTOs ),来匹配他们的领域对象。例如,Hibernate允许你把数据库信息读进领域对象( domain objects )的一个对象图,这样你可以在连接断开的情况下把这些数据显示到UI层。那些对象也能被更新和送回到持久层并在数据库里更新。而且,你不必把对象转化成DTOs,因为DTOs在不同的应用层间移动,可能在转换中丢失。这个模型使得Java开发者自然地以一种面向对象的风格和对象打交道,没有附加的编码。 结合一个简单的例子
既然我们已经从一个高的层次上理解了这些组件, 现在就让我们开始实践吧。在这个例子中,我们还是将合并Struts、Spring、Hibernate框架。每一个这些框架在一篇文章中都有太多的细节覆盖到。这篇文章将用一个简单的例子代码展示怎样把它们结合在一起,而不是进入每个框架的许多细节。示例应用程序将示范一个请求怎样跨越每一层被服务的。这个示例应用程序的一个用户能保存一个订单到数据库中和查看一个在数据库中存在的订单。进一步的增强可以使用户更新或删除一个存在的订单。 你可以下载这个应用的源码( http://www.onjava.com/onjava/2004/04/07/examples/wiring.zip )。 因为领域对象( domain objects )将和每一层交互,我们将首先创建它们。这些对象将使我们定义什么应该被持久化,什么业务逻辑应该被提供,和哪种表现接口应该被设计。然后,我们将配置持久层和用Hibernate为我们的领域对象( domain objects )定义“对象-关系”映射( object-to-relational mappings )。然后,我们将定义和配置我们的业务对象( business objects )。在有了这些组件后,我们就能讨论用Spring把这些层连在一起。最后,我们将提供一个表现层( presentation layer ),它知道怎样和业务服务层( business service layer )交流和知道怎样处理从其它层产生的异常( exceptions )。
因为这些对象将和所有层交互,这也许是一个开始编码的好地方。这个简单的领域模型将包括一个代表一份订单( order )的对象和一个代表一个订单项( line item for an order )的对象。订单( order )对象将和一组订单项( a collection of line item )对象有一对多( one-to-many )的关系。例子代码在领域层有两个简单的对象:
com.meagle.bo.Order.java: 包括一份订单( oder )的概要信息;
com.meagle.bo.OrderLineItem.java: 包括一份订单( order )的详细信息;
考虑一下为你的对象选择包名,它将反映你的应用程序是怎样分层的。例如:简单应用的领域对象( domain objects )可以放进com.meagle.bo包[译者注:bo-business object?]。更多专门的领域对象将放入在com.meagle.bo下面的子包里。业务逻辑在com.meagle.service包里开始打包,DAO对象放进com.meagle.service.dao.hibernate包。对于forms和actions的表现类( presentation classes )分别放入com.meagle.action 和 com.meagle.forms包。准确的包命名为你的类提供的功能提供一个清楚的区分,使当故障维护时更易于维护,和当给应用程序增加新的类或包时提供一致性。持久层配置( Persistence Layer Configuration )
用Hibernate设置持久层涉及到几个步骤。第一步是进行配置持久化我们的领域业务对象( domain business objects )。因为我们用于领域对象( domain objects )持久化的Hibernate和POJOs一起工作( 此句原文:Since Hibernate works with POJOs we will use our domain objects for persistence. ),因此,订单和订单项对象包括的所有的字段的都需要提供getter和setter方法。订单对象将包括像ID、用户名、合计、和订单项这样一些字段的标准的JavaBean格式的setter和getter方法。订单项对象将同样的用JavaBean的格式为它的字段设置setter和getter方法。
Hibernate在XML文件里映射领域对象到关系数据库。订单和订单项对象将有两个映射文件来表达这种映射。有像XDoclet( http://xdoclet.sourceforge.net/ )这样的工具来帮助这种映射。Hibernate将映射领域对象到这些文件:
Order.hbm.xml
OrderLineItem.hbm.xml
你可以在WebContent/WEB-INF/classes/com/meagle/bo目录里找到这些生成的文件。配置Hibernate SessionFactory( http://www.hibernate.org/hib_docs/api/net/sf/hibernate/SessionFactory.html )使它知道是在和哪个数据库通信,使用哪个数据源或连接池,加载哪些持久对象。SessionFactory提供的Session( http://www.hibernate.org/hib_docs/api/net/sf/hibernate/Session.html )对象是Java对象和像选取、保存、更新、删除对象这样一些持久化功能间的翻译接口。我们将在后面的部分讨论Hibernate操作Session对象需要的SessionFactory配置。
业务层配置( Business Layer Configuration )
既然我们已经有了领域对象( domain objects ),我们需要有业务服务对象来执行应用逻辑、执行向持久层的调用、获得从用户接口层( UI layer )的请求、处理事务、处理异常。为了将所有这些连接起来并且易于管理,我们将使用Spring框架的bean管理方面( bean management aspect )。Spring使用“控制反转”( IoC ),或者“setter依赖注入”来把这些对象连好,这些对象在一个外部的XML文件中被引用。“控制反转”是一个简单的概念,它允许对象接受其它的在一个高一些的层次被创建的对象。使用这种方法,你的对象从必须创建其它对象中解放出来并降低对象耦合。 这儿是个不使用IoC的对象创建它的从属对象( object creating its dependencies without IoC )的例子,这导致紧的对象耦合:
图2:没有使用IoC的对象组织。对象A创建对象B和C( http://www.onjava.com/onjava/2004/04/07/graphics/nonioc.gif )。
这儿是一个使用IoC的例子,它允许对象在一个高一些层次被创建和传进另外的对象,所以另外的对象能直接使用现成的对象?[译者注:另外的对象不必再亲自创建这些要使用的对象]( allows objects to be created at higher levels and passed into objects so that they can use the implementations directly ):
图3:对象使用IoC组织。对象A包含setter方法,它们接受到对象B和C的接口。这也可以用对象A里的接受对象B和C的构建器完成( http://www.onjava.com/onjava/2004/04/07/graphics/ioc.gif )。建立我们的业务服务对象( Building Our Business Service Objects )
我们将在我们的业务对象中使用的setter方法接受的是接口,这些接口允许对象的松散定义的实现,这些对象将被设置或者注入。在我们这个例子里我们将使我们的业务服务对象接受一个DAO去控制我们的领域对象的持久化。当我们在这篇文章的例子中使用Hibernate( While the examples in this article use Hibernate ),我们可以容易的转换到一个不同的持久框架的实现,通知Spring使用新的实现的DAO对象。你能明白编程到接口和使用“依赖注入”模式是怎样宽松耦合你的业务逻辑和你的持久化机制的。
这儿是业务服务对象的接口,它是一个DAO对象依赖的桩。( Here is the interface for the business service object that is stubbed for a DAO object dependency: )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);
} 注意上面的代码有一个为DAO对象准备的setter方法。这儿没有一个getOrderDAO方法因为它不是必要的,因为不太有从外面访问连着的OrderDAO对象的需要。DAO对象将被用来和我们的持久层沟通。我们将用Spring把业务服务对象和DAO对象连在一起。因为我们编码到接口,我们不会紧耦合实现。下一步是写我们的DAO实现对象。因为Spring有内建的对Hibernate的支持,这个例子DAO将继承HibernateDaoSupport( http://www.springframework.org/docs/api/org/springframework/orm/hibernate/support/HibernateDaoSupport.html )类,这使得我们容易取得一个到HibernateTemplate( http://www.springframework.org/docs/api/org/springframework/orm/hibernate/HibernateTemplate.html )类的引用,HibernateTemplate是一个帮助类,它能简化Hibernate Session的编码和处理HibernateExceptions。这儿是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);
}
class="org.springframework.orm.hibernate.LocalSessionFactoryBean"> com/meagle/bo/Order.hbm.xml com/meagle/bo/OrderLineItem.hbm.xml net.sf.hibernate.dialect.MySQLDialect false C:/MyWebApps/.../WEB-INF/proxool.xml spring class="org.springframework.orm.hibernate.HibernateTransactionManager"> 每一个对象能被Spring配置里的一个标记引用。在这个例子里,bean “mySessionFactory”代表一个HibernateSessionFactory,bean “myTransactionManager”代表一个Hibernate transaction manager。注意transactionManger bean有一个叫作sessionFactory的属性元素。HibernateTransactionManager有一个为sessionFactory准备的setter和getter方法,它们是用来当Spring容器启动时的依赖注入。sessionFactory属性引用mySessionFactory bean。这两个对象现在当Spring容器初始化时将被连在一起。这种连接把你从为引用和创建这些对象而创建singleton对象和工厂中解放出来,这减少了你应用程序中的代码维护。mySessionFactory bean有两个属性元素,它们翻译成为mappingResources 和 hibernatePropertes准备的setter方法。通常,如果你在Spring之外使用Hibernate,这个配置将被保存在hibernate.cfg.xml文件中。不管怎样,Spring提供了一个便捷的方式--在Spring配置文件中合并Hibernate的配置。获得更多的信息查阅Spring API( http://www.springframework.org/docs/api/index.html )。既然我们已经配置了我们的容器服务beans和把它们连在了一起,我们需要把我们的业务服务对象和我们的DAO对象连在一起。然后,我们需要把这些对象连接到事务管理器。这是在Spring配置文件里的样子:class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">PROPAGATION_REQUIRED,readOnly,-OrderExceptionPROPAGATION_REQUIRED,-OrderException class="com.meagle.service.spring.OrderServiceSpringImpl"> class="com.meagle.service.dao.hibernate.OrderHibernateDAO"> 图4是我们已经连在一起的东西的一个概览。它展示了每个对象是怎样相关联的和怎样被Spring设置进其它对象中。把这幅图和示例应用中的Spring配置文件对比查看它们之间的关系。图4:这是Spring怎样将在这个配置的基础上装配beans( http://www.onjava.com/onjava/2004/04/07/graphics/spring_wiring.gif )。 这个例子使用一个TransactionProxyFactoryBean,它有一个为我们已经定义了的事务管理者准备的setter方法。这是一个有用的对象,它知道怎样处理声明的事务操作和你的服务对象。你可以通过transactionAttributes属性定义事务怎样被处理,transactionAttributes属性为方法名定义模式和它们怎样参与进一个事务。获得更多的关于在一个事务上配置隔离层和提交或回滚查阅TransactionAttributeEditor( http://www.springframework.org/docs/api/org/springframework/transaction/interceptor/TransactionAttributeEditor.html )。 TransactionProxyFactoryBean( http://www.springframework.org/docs/api/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.html )类也有一个为一个target准备的setter,target将是一个到我们的叫作orderTarget的业务服务对象的引用( a reference )。 orderTarget bean定义使用哪个业务服务对象并有一个指向setOrderDAO()的属性。orderDAO bean将居于这个属性中,orderDAO bean是我们的和持久层交流的DAO对象。 还有一个关于Spring和bean要注意的是bean能以两种模式工作。这两种模式被定义为singleton和prototype。一个bean默认的模式是singleton,意味着一个共享的bean的实例将被管理。这是用于无状态操作--像一个无状态会话bean将提供的那样。当bean由Spring提供时,prototype模式允许创建bean的新实例。你应当只有在每一个用户都需要他们自己的bean的拷贝时才使用prototype模式。提供一个服务定位器( Providing a Service Locator )
既然我们已经把我们的服务和我们的DAO连起来了,我们需要把我们的服务暴露给其它层。通常是一个像使用Struts或Swing这样的用户接口层里的代码来使用这个服务。一个简单的处理方法是使用一个服务定位器模式的类从一个Spring上下文中返回资源。这也可以靠引用bean ID通过Spring来直接完成。
这儿是一个在Struts Action中怎样配置一个服务定位器的例子: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 Layer Configuration )
示例应用的用户接口层使用Struts框架。这儿我们将讨论当为一个应用分层时和Struts相关的部分。让我们从在struts-config.xml文件里检查一个Action配置开始。
type="com.meagle.action.SaveOrderAction"
name="OrderForm"
scope="request"
validate="true"
input="/NewOrder.jsp">
Save New Order path="/NewOrder.jsp"
scope="request"
type="com.meagle.exception.OrderException"/> path="/NewOrder.jsp"
scope="request"
type="com.meagle.exception.OrderMinimumAmountException"/> SaveNewOrder Action被用来持久化一个用户从用户接口层提交的订单。这是一个典型的Struts Action;然而,注意这个action的异常配置。这些Exceptions为我们的业务服务对象也在Spring 配置文件(applicationContext-hibernate.xml)中配置了( 在transactionAttributes属性里 )。当这些异常被从业务层掷出我们能在我们的用户接口里恰当的处理它们。第一个异常,OrderException,当在持久层里保存订单对象失败时将被这个action使用。这将引起事务回滚和通过业务对象传递把异常传回给Struts层。OrderMinimumAmountException,在业务对象逻辑里的一个事务因为提交的订单达不到最小订单数量而失败也将被处理。然后,事务将回滚和这个异常能被用户接口层恰当的处理。 最后一个连接步骤是使我们的表现层和我们的业务层交互。这已经通过使用前面讨论的服务定位器来完成了。服务层充当一个到我们的业务逻辑和持久层的接口。这儿是 Struts中的SaveNewOrder Action可能怎样使用一个服务定位器调用一个业务方法: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( http://www.ibatis.com/ ),作为一个替代是显而易见的。或者你可能用不同于Struts的框架替代你的UI层。改变UI层的实现不会直接影响你的业务逻辑层或者你的持久层。替换你的持久层不会影响你的UI逻辑或业务服务层。集成一个web应用其实也不是一件烦琐的工作,靠解耦你的各应用层和用适当的框架组成它,它能变得更容易处理。Mark Eagle 是一位在MATRIX智囊团的高级软件工程师, Inc. in Atlanta, GA。