hongyunxp

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  2 随笔 :: 2 文章 :: 1 评论 :: 0 Trackbacks

2007年5月20日 #

 

摘要 面向方面编程(AOP)是面向对象编程(OOP)的一种扩展技术,能够很好的解决横切关注点问题以及相关的设计难题来实现松散耦合。Spring AOP 是 AOP 技术的一种实现。本文介绍了AOP 概念,然后详细讨论了如何利用Spring AOP 开发AOP 程序,最后展望了Spring AOP 的前景。

  关键词 AOP Spring AOP Java

  引言

  AOP(Aspected Oriented Programming)是一种新兴的编程技术。它可以解决OOP和过程化方法不能够很好解决的横切(crosscut)问题,如:事务、安全、日志等横切关注。当未来系统变得越来越复杂,横切关注点就成为一个打问题的时候,AOP就可以很轻松的解决横切关注点这个问题,使得AOP编程成为。Spring 是基于J2EE的轻量级开源开发框架,其中Spring AOP组件实现了面向方面编程。

  AOP 概述

  面向方面编程 (AOP) 提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。 面向对象将应用程序分解成各个层次的对象,而AOP将程序分解成各个方面或者说关注点 。这使得可以模块化诸如事务管理等这些横切多个对象的关注点。

  1、AOP 基本概念

  方面(Aspect): 一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

  连接点(Joinpoint): 程序执行过程中明确的点,如方法的调 用或特定的异常被抛出。

  通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类 型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架 包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。

  切入点(Pointcut): 指定一个通知将被引发的一系列连接点 的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。

  引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。

  目标对象(Target Object): 包含连接点的对象。也被称作 被通知或被代理对象。

  AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

  织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时 完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样, 在运行时完成织入。

  Spring AOP 介绍

  Spring的一个关键组件就是AOP框架。 Spring IoC容器(BeanFactory 和ApplicationContext)并不依赖于AOP, 这意味着如果你不需要使用,AOP可以不用,AOP完善了Spring IoC,使之成为一个有效的中间件解决方案。

  Spring AOP 是Spring 框架的重要组成部分,它实现了AOP联盟约定的接口。Spring AOP 是由纯Java开发完成的。Spring AOP 只实现了方法级别的连接点,在J2EE应用中,AOP拦截到方法级的操作已经足够。OOP倡导的是基于setter/getter 的方法访问,而非直接访问域,而Spring 有足够理由仅仅提供方法级的连接点。为了使控制反转(IoC)很方便的使用到非常健壮、灵活的企业服务,则需要Spring AOP 的实现。Spring AOP 在运行时才创建Advice 对象。Spring AOP的优点如下:

  ·允许开发者使用声明式企业服务,比如事务服务、安全性服务。

  ·开发者可以开发满足业务需求的自定义方面。

  ·开发Spring AOP Advice 很方便,可以借助代理类快速搭建Spring AOP 应用。

  使用Spring AOP松散耦合

  1、创建通知

  为实现AOP,开发者需要开发AOP 通知(Advice)。AOP 通知(Advice) 包含了方面(Aspect)的逻辑。当创建一个Advice 对象的时候,你就编写了实现横切(cross-cutting)功能。 Spring 的连接点是用方法拦截器实现的,这就意味着你编写的Spring AOP 通知将在方法调用的不同点组入进程序中。由于在调用一个方法时有几个不同的时间点,Spring 可以在不同的时间点组入进程序。Spring AOP中,提供了四种通知的接口: MethodBeforeAdvice 用于在目标方法调用前触发;AfterReturningAdvice 用于在目标方法调用后触发;ThrowsAdvice 用于在目标方法抛出异常时触发;MethodInterceptor 用于实现 Around 通知(Advice),在目方法执行的前后触发。

  如果要实现相应功能,则需要实现上述对应的接口。例如:实现Before 通知(Advice)需要实现方法 void before(Method method, Object[] args, Object target) ,实现 After 通知(Advice) 需要实现方法 void afterReturning (Method method, Object[] args, Object target)。

  2、在Spring 中定义切入点

  在不能明确调用方法的时候,通知就很不实用。切入点则可以决定特定的类,特定的方法是否匹配特定标准。如果某匹配,则通知将应用到此方法上。Spring 切入点允许用很灵活的方式将通知组织进我们的类中。Spring 中的切入点框架的核心是Pointcut接口,此接口允许我们定义组入通知中的类和方法。许多方面是通过一系列的通知和切入点组合来定义。

  在Spring中,一个advisor就是一个方面的完整的模块化表示。Spring提供了PointcutAdvisor接口把通知和切入点组合成一个对象。Spring中很多内建的切入点都有对应的PointcutAdvisor,这使得你可以很方便在一个地方管理切入点和通知。Spring中的切入点分为两类:静态和动态。因为静态切入点的性能要优于动态切入点,所以优先考虑使用。Spring 为我们提供创建静态切入点很实用的类StaticMethodMatherPointcut。在这个类中,我们只需要关心setMappedName和setMappedNams方法。你可以使用具体的类名,也可以使用通配符。如:设置mappedName属性为set* 则匹配所有的set方法。Spring还提供了另通过正则表达式来创建静态切入点的实用类RegexpMethodPointcut。通过使用Perl样式的正则表达式来定义你感兴趣的方法。当切入点需要运行时参数值来执行通知时,这时就需要使用动态切入点。Spring提供了一个内建的动态切入点:ControlFlowPointcut,此切入点匹配基于当前线程的调用堆栈。我们可以在只有在当前线程执行的执行时找到特定的类和特定的方法才返回true。使用动态切入点有很大的性能损耗。大多数的切入点可以静态确定,我们很少有机会创建动态切入点。为了增加可切入点的可重用性,Spring 提供了切入点上的集合操作——交集和合并。

  3、用ProxyFactoryBean创建AOP代理

  ProxyFactoryBean,和其他Spring的 FactoryBean实现一样,引入一个间接的层次。如果你定义一个名字为myfactory的ProxyFactoryBean, 引用myfactory的对象所看到的不是ProxyFactoryBean 实例本身,而是由实现ProxyFactoryBean的类的 getObject()方法所创建的对象。这个方法将创建一个包装了目标对象 的AOP代理。使用ProxyFactoryBean或者其他IoC可知的类来创建AOP代理的最重要的优点之一是IoC可以管理通知和切入点。这是一个非常的强大的功能,能够实现其他AOP框架很难实现的特定的方法。例如,一个通知本身可以引用应用对象(除了目标对象, 它在任何AOP框架中都可以引用应用对象),这完全得益于依赖注入所提供的可插入性。通常,我们不需要ProxyFactoryBean的全部功能,因为我们常常只对一个方面感兴趣: 例如,事务管理。当我们仅仅对一个特定的方面感兴趣时,我们可以使用许多便利的工厂来创建AOP代理,如:TransactionProxyFactoryBean。

  4、自动代理

  在应用较小时,只有很少类需要被通知的时,ProxyFactoryBean 可以很好的工作。当有许多类需要通知的时,显示的创建每个代理就显得很繁琐。幸运的是Spring提供了是使用自动通过容器来创建代理。这时,就只需要配置一个Bean来做繁琐的工作。Spring提供了两个类实现自动代理:BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。BeanNameAutoProxyCreator为匹配名字的Bean产生代理,它可以使用在将一个或者多个方面应用在命名相似的Bean中。自动代理框架假设代理将要暴露出什么接口。如果目标Bean没有实现任何接口,这时就会动态产生一个子类。而更强大的自动代理是DefaultAdvisorAutoProxyCreator,你所需要做的是在BeanFactory中包含它的配置。这个类的奇妙之处在于他使用实现了BeanPostProcessor接口。在Bean定义被加载倒Spring容器中后,DefaultAdvisorAutoProxyCreator将搜索上下文中的Advisor,最后它将Advisor应用到匹配Advisor切入点的Bean中。这个代理只对Advisor起作用,它需要通过Advisor来得到需要通知的Bean。元数据自动代理(MetaData AutoProxy)。元数据自动代理配置依赖于源代码属性而不是外部XML配置文件。这可以非常方便的使源代码和AOP元数据组织在同一个地方。元数据自动代理最常用的地方是用来声明事务。Spring提供了很强的框架来通过AOP框架来声明事务。这提供了在EJB使用声明式事务的相同功能。

  结论

  AOP 是面向对象编程的有力补充。通过方面就可以聚合在应用中行为形成可重用模块。

  通过程序可以实现怎样和在什么地方来调用这些行为。这可以减少代码重复,并使你更加关注业务逻辑。Spring 提供了AOP框架来实现调用方法时加入方面。在AOP框架中可以很方便的使用预定义的静态切入点来定义被调用的类和方法。我们需要通过Spring提供的代理类来产生代理对象,可以使用ProxyFactoryBean也可以使用自动代理。Spring AOP 的编程方式模块化了横向关注点的实现,提供了一个更好更快的软件开发方式。在软件结构日益扩大,结构日益复杂的今天,Spring AOP 将会发挥越来越重要的作用。
posted @ 2007-05-20 16:35 徐洪云 阅读(171) | 评论 (0)编辑 收藏

2007年5月14日 #

使用struts+spring+hibernate组装你的web应用架构

      其实,就算用Java建造一个不是很烦琐的web应用,也不是件轻松的事情。 在构架的一开始就有很多事情要考虑。 从高处看,摆在开发者面前有很多问题:要考虑是怎样建立用户接口?在哪里处理业务逻辑? 怎样持久化的数据。 而这三层构架中,每一层都有他们要仔细考虑的。 各个层该使用什么技术? 怎样的设计能松散耦合还能灵活改变? 怎样替换某个层而不影响整体构架?应用程序如何做各种级别的业务处理(比如事务处理)?

      构架一个Web应用需要弄明白好多问题。 幸运的是,已经有不少开发者已经遇到过这类问题,并且建立了处理这类问题的框架。 一个好框架具备以下几点: 减轻开发者处理复杂的问题的负担("不重复发明轮子"); 内部有良好的扩展; 并且有一个支持它的强大的用户团体。 好的构架一般有针对性的处理某一类问题,并且能将它做好(Do One Thing well)。 然而,你的程序中有几个层可能需要使用特定的框架,已经完成的UI(用户接口) 并不代表你也可以把你的业务逻辑和持久逻辑偶合到你的UI部分。 举个例子, 你不该在一个Controller(控制器)里面写JDBC代码作为你的业务逻辑, 这不是控制器应该提供的。 一个UI 控制器应该委派给其它给在UI范围之外的轻量级组件。 好的框架应该能指导代码如何分布。 更重要的是,框架能把开发者从编码中解放出来,使他们能专心于应用程序的逻辑(这对客户来说很重要)。

      这篇文章将讨论怎样结合几种著名的框架来使得你的应用程序做到松弛耦合。
如何建立你的架构,并且怎样让你的各个应用层保持一致。?如何整合框架以便让每个层在以一种松散偶合的方式彼此作用而不用管低层的技术细节?这对我们来说真是一种挑战。 这里讨论一个整合框架的策略( 使用3 种受欢迎的开源框架) :表示层我们用Struts; 业务层我们用Spring;而持久层则用Hibernate。 你也可以用其他FrameWork替换只要能得到同样的效果。 见图1 (框架组合示意图)



应用程序的分层

大部分的Web应用在职责上至少能被分成4层。 这四层是:presentation(描述),persistence(持久),business(业务)和domain model(域模块)。每个层在处理程序上都应该有一项明确的责任, 而不应该在功能上与其它层混合,并且每个层要与其它层分开的,但要给他们之间放一个通信接口。 我们就从介绍各个层开始,讨论一下这些层应该提供什么,不应该提供什么。



表示层(The Presentation Layer)

一般来讲,一个典型的Web应用的的末端应该是表示层。 很多Java发者也理解Struts所提供的。 象业务逻辑之类的被打包到org.apache.struts.Action., 因此,我们很赞成使用Struts这样的框架。



下面是Struts所负责的:

* 管理用户的请求,做出相应的响应。

* 提供一个Controller ,委派调用业务逻辑和其它上层处理。

* 处理异常,抛给Struts Action

* 为显示提供一个模型

* UI验证。



以下条款,不该在Struts显示层的编码中经常出现。 它们与显示层无关的。

* 直接的与数据库通信,例如JDBC调用。

* 与你应用程序相关联的业务逻辑以及校验。

* 事物管理。

在表示层引入这些代码,则会带来高偶合和麻烦的维护。





持久层(The Persistence Layer)

典型的Web应用的另一个末端是持久层。这里通常是程序最容易失控的地方。开发者总是低估构建他们自己的持久框架的挑战性。系统内部的持续层不但需要大量调试时间,而且还经常缺少功能使之变得难以控制,这是持久层的通病。 还好有几个ORM开源框架很好的解决了这类问题。尤其是Hibernate。 Hibernate为java提供了OR持久化机制和查询服务, 它还给已经熟悉SQL和JDBC API 的Java开发者一个学习桥梁,他们学习起来很方便。 Hibernate的持久对象是基于POJO和Java collections。此外,使用Hibernate并不妨碍你正在使用的IDE。



请看下面的条目,你在持久层编码中需要了解的。

* 查询对象的相关信息的语句。 Hibernate通过一个OO查询语言(HQL)或者正则表达的API来完成查询。 HQL非常类似于SQL-- 只是把SQL里的table和columns用Object和它的fields代替。 你需要学习一些新的HQL语言; 不管怎样,他们容易理解而文档也做的很好。 HQL是一种对象查询的自然语言,花很小的代价就能学习它。

* 如何存储,更新,删除数据库记录。

* 象Hibernate这类的高级ORM框架支持大部分主流数据库,并且他们支持 Parent/child关系,事物处理,继承和多态。



业务层(The Business Layer)

一个典型Web应用的中间部分是业务层或者服务层。 从编码的视角来看,这层是最容易被忽视的一层。 而我们却往往在UI层或持久层周围看到这些业务处理的代码,这其实是不正确的,因为它导致了程序代码的紧密偶合,这样一来,随着时间推移这些代码很难维护。幸好,针对这一问题有好几种Frameworks存在。 最受欢迎的两个框架是Spring和PicoContainer。 这些为也被称为microcontainers,他们能让你很好的把对象搭配起来。 这两个框架都着手于‘依赖注射'(dependency injection)(还有我们知道的‘控制反转'Inversion of Control=IoC)这样的简单概念。 这篇文章将关注于Spring的注射(译注:通过一个给定参数的Setter方法来构造Bean,有所不同于Factory), Spring还提供了Setter Injection(type2),Constructor Injection(type3)等方式供我们选择。 Spring把程序中所涉及到包含业务逻辑和Dao的Objects——例如transaction management handler(事物管理控制)、Object Factoris(对象工厂)、service objects(服务组件)——都通过XML来配置联系起来。



后面我们会举个例子来揭示一下Spring 是怎样运用这些概念。

业务层所负责的如下:

* 处理应用程序的 业务逻辑和业务校验

* 管理事物

* 允许与其它层相互作用的接口

* 管理业务层级别的对象的依赖。

* 在显示层和持久层之间增加了一个灵活的机制,使得他们不直接的联系在一起。

* 通过揭示 从显示层到业务层之间的Context来得到business services。

* 管理程序的执行(从业务层到持久层)。





域模块层(The Domain Model Layer )
既然我们致力于的是一个不是很复杂的Web的应用, 我们需要一个对象集合,让它在不同层之间移动的。 域模块层由实际需求中的业务对象组成 比如, OrderLineItem , Product等等。 开发者在这层 不用管那些DTOs,仅关注domain object即可。 例如,Hibernate允许你将数据库中的信息存放入对象(domain objects),这样你可以在连接断开的情况下把这些数据显示到UI层。 而那些对象也可以返回给持续层,从而在数据库里更新。 而且,你不必把对象转化成DTOs(这可能似的它在不同层之间的在传输过程中丢失),这个模型使得Java开发者能很自然运用OO,而不需要附加的编码。

一个简单例子



既然我们已经从全局上理解这些组件。 现在就让我们开始实践吧。 我们还是用 Struts,Spring 和Hibernate。这三个框架已经被描述够多了,这里就不重复介绍了。 这篇文章举例指导你如何使用这三个框架整合开发, 并向你揭示 一个请求是如何贯穿于各个层的。(从用户的加入一个Order到数据库,显示;进而更新、删除)。



从这里可以下载到程序程序原代码(download

既然每个层是互相作用的,我们就先来创建domain objects。首先,我们要在这些Object中要确定那些是需要持久化的,哪些是提供给business logic,那些是显示接口的设计。 下一步,我们将配置我们的持久层并且定义好Hibernate的OR mappings。然后定义好Business Objects。有了这些组成部分之后,我们将 使用Spring把这些连接起来。 最后,我们提供给Spring一个持久层,从这个持久层里我们可以知道它是如何与业务逻辑层(business service layer)通信的,以及它是怎样处理其他层抛出的异常的。。



域对象层(Domain Object Layer)


这层是编码的着手点,我们的编码就从这层开始。 例子中Order 与OrderItem 是一个One—To—Many的关系。 下面就是Domain Object Layer的两个对象:



· com.meagle.bo.Order.java: 包含了一个Order的概要信息

· com.meagle.bo.OrderLineItem.java: 包含了Order的详细信息

好好考虑怎你的package命名,这反应出了你是怎样分层的。 例如 domain objects在程序中可能打包在com.meagle.bo内。 更详细一点将打包在com. meagle.bo的子目录下面。business logic应该从com.meagle.serice开始打包,而DAO 对象应该位于com.meagle.service.dao.hibernate。反应Forms和Actions的 持久对象(presentation classes) 应该分别放在 com.meagle.action和com.meagle.forms包。 准确的给包命名使得你的classes很好分割并且易于维护,并且在你添加新的classes时,能使得程序结构上保持上下一致。

持久层的配置(Persistence Layer Configuration)

建立Hibernate的持久层 需要好几个步骤。 第一步让我们把BO持久化。 既然Hibernate是通过POJO工作的, 因此Order和 OrderLineItem对象需要给所有的fileds 加上getter,setter方法。 Hibernate通过XML文件来映射(OR)对象,以下两个xml文件分别映射了Order 和OrderItem对象。(这里有个叫XDoclet工具可以自动生成你的XML影射文件)

- Order.hbm.xml
- OrderLineItem.hbm.xml

你可以在WebContent/WEB-INF/classes/com/meagle/bo目录下找到这些xml文件。Hibernate的 [urlhttp://www.hibernate.org/hib_docs/api/net/sf/hibernate/SessionFactory.html]SessionFactory [/url]是用来告诉程序 应该与哪个数据库通信,该使用哪个连接池或使用了DataSource, 应该加载哪些持久对象。而Session接口是用来完成Selecting,Saving,Delete和Updating这些操作。 后面的我们将讲述SessionFactory和Session是怎样设置的。

业务层的配置(Business Layer Configuration)

既然我们已经有了domain objects,接下来我们就要business service objects了,用他们来执行程序的logic,调用持久层,得到UI层的requests,处理transactions,并且控制exceptions。 为了将这些连接起来并且易于管理,我们将使用面向方面的 SpringFramework。 Spring 提供了 控制倒置(inversion of control 0==IoC)和注射依赖设置(setter dependency injection)这些方式(可供选择),用XML文件将对象连接起来。 IoC是一个简单概念(它允许一个对象在上层接受其他对象的创建),用IoC这种方式让你的对象从创建中释放了出来,降低了偶合度。




这里是一个没有使用IoC的对象创建的例子,它有很高偶合度。




图 2.没有使用 IoC. A 创建了 B 和 C

而这里是一个使用IoC的例子,这种方式允许对象在高层可以创建并进入另外一个对象,所以这样可以直接被执行。


图 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 id="mySessionFactory"
       class="org.springframework.orm.hibernate.
              LocalSessionFactoryBean">
  <property name="mappingResources">
    <list>
      <value>
        com/meagle/bo/Order.hbm.xml
      </value>
      <value>
        com/meagle/bo/OrderLineItem.hbm.xml
      </value>
    </list>
  </property>
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.dialect">
        net.sf.hibernate.dialect.MySQLDialect
      </prop>
      <prop key="hibernate.show_sql">
        false
      </prop>
      <prop key="hibernate.proxool.xml">
        C:/MyWebApps/.../WEB-INF/proxool.xml
      </prop>
      <prop key="hibernate.proxool.pool_alias">
          spring
      </prop>
    </props>
  </property>
</bean>
 
<!-- Transaction manager for a single Hibernate
SessionFactory (alternative to JTA) -->
<bean id="myTransactionManager"
         class="org.
                springframework.
                orm.
                hibernate.
                HibernateTransactionManager">
  <property name="sessionFactory">
    <ref local="mySessionFactory"/>
  </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 id="orderService"
  class="org.
         springframework.
         transaction.
         interceptor.
         TransactionProxyFactoryBean">
  <property name="transactionManager">
    <ref local="myTransactionManager"/>
  </property>
  <property name="target">
    <ref local="orderTarget"/>
  </property>
  <property name="transactionAttributes">
    <props>
      <prop key="find*">
     PROPAGATION_REQUIRED,readOnly,-OrderException
      </prop>
      <prop key="save*">
     PROPAGATION_REQUIRED,-OrderException
      </prop>
    </props>
  </property>
</bean>
 
<!-- ORDER TARGET PRIMARY BUSINESS OBJECT:
Hibernate implementation -->
<bean id="orderTarget"
         class="com.
                meagle.
                service.
                spring.
                OrderServiceSpringImpl">
  <property name="orderDAO">
    <ref local="orderDAO"/>
  </property>
</bean>
 
<!-- ORDER DAO OBJECT -->
<bean id="orderDAO"
         class="com.
                meagle.
                service.
                dao.
                hibernate.
                OrderHibernateDAO">
  <property name="sessionFactory">
    <ref local="mySessionFactory"/>
  </property>
</bean>




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



图 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,软件工程师





参考:

Struts:http://jakarta.apache.org/struts/index.html

Spring: http://www.springframework.org

Hibernate: http://www.hibernate.org

http://www.hibernate.org.cn

关于控制反转IOC和依赖注射:http://www.martinfowler.com/articles/injection.html

原文:

http://www.onjava.com/pub/a/onjava/2004/04/07/wiringwebapps.html?page=1
http://www.onjava.com/pub/a/onjava/2004/04/07/wiringwebapps.html?page=2
http://www.onjava.com/pub/a/onjava/2004/04/07/wiringwebapps.html?page=3
http://www.onjava.com/pub/a/onjava/2004/04/07/wiringwebapps.html?page=4
posted @ 2007-05-14 04:32 徐洪云 阅读(190) | 评论 (1)编辑 收藏

仅列出标题