技术开发 频道

Observer模式在J2EE中的实现

  【IT168 技术文章】

         引言:

  设计模式是经验的文档化。它是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。更通俗的来说,它是一个问题/解决方案对。一旦我们掌握了设计模式,就等于拥有了一支强有力的专家队伍。它甚至能够使面向对象的新手利用前人的经验找出职责明确的类和对象,从而获得优雅的解决方案。由于设计模式也是重构的目标,如果在设计的初期适当地引入设计模式,可以减少重构的工作量。

  但是,我们也不能陷入模式的陷阱,为了使用模式而去套模式,那样会陷入形式主义。我们在使用模式的时候,一定要注意模式的意图(intent),而不要过多的去关注模式的实现细节,因为这些实现细节在特定情况下,可能会发生一些改变。不要顽固地认为设计模式一书中的类图或实现代码就代表了模式本身。

  下面,我们来讨论一下为什么要在分布式、多层系统中使用Observer模式。

  多层体系结构(multi-tier architecture):

  三层体系结构是多层体系结构中最简单的一种,它一般包括:

  表示层(presentation)-窗口、报表-

  业务逻辑层(business logic)-管理业务过程的任务和规则。它又可以细分为领域对象层(代表领域概念)和服务层(提供数据库交互、安全性、打印报表)。

  存储层(storage)-持久化存储机制。如数据库服务器等。

  图一:三层体系结构

  而Java 2平台企业版(J2EE)是一种利用Java 2平台来简化诸多与多级企业解决方案的开发、部署和管理相关的复杂问题的体系结构。它是开放的、基于标准的平台,用以开发、部署和管理N层结构、面向Web的,以服务器为中心的企业级应用。

  为了支持领域对象的复用,并且使领域对象的接口变更所带来的影响最小化。我们将领域层(模型)和表示层(视图)相分离。

  采用模型-视图模式的意义在于:

  支持聚合度更高的模型定义,使模型的定义可以集中在领域过程的定义,而不是图形界面上。

  允许将模型和用户界面并行开发。

  使用户界面的需求变化对领域层所造成的影响最小化。

  允许建立与一个现有的领域层对象相连接的新视图,同时不影响领域层。

  允许一个模型同时有多个视图,例如使用SVG和表格。

  允许模型层独立于用户界面层执行。

  而这恰恰与Observer模式的意图相吻合。因此我们有必要跨层来实现Observer模式。

  其实,在应用中更多的是采用MVC框架来架构整个企业应用的。在MVC框架中,Model和View之间存在着依赖关系,是Observer模式的典型应用。当然MVC框架还包括其它模式如Composite模式和Strategy模式。在J2EE平台中,我们可以把Web Tier(包括Jsp和servelet和JavaBean)看作是表示层,EJB Tier看作是领域层。而controller可能跨距Web Tier和 EJB Tier。

  在Java类库中采用Java.util.Observable类和Java.util.Observer接口来实现Observer模式,它们在单个的Java VM.中运行的很好,但如果想在EJB中使用它们就会有一些问题。这正如我们引言中提到的,模式的具体实现在特定情况下,可能会发生一些改变。

  值传递还是远程引用传递?

  值传递:

  在Java RMI中要求所有的参数和返回类型是JAVA的基本类型或实现Java.io.Serilizable的对象。串行化对象通过值传递(又名拷贝传递),而不是引用传递,这意味着在某一层中串行化对象的更并不自动影响到其它的对象。

  远程引用传递:

  对于EJB对象而言,它由两个接口(home接口和remote接口)和一个类组成。容器会根据ejb规范来生成实现上面两个接口的类(我们分别称为xxxEJBHome对象和xxxEjbObject对象)。在较多的容器的实现方案中,xxxEJBHome对象使用了factory模式来创建xxxEjbObject对象;xxxEjbObject对象则采用proxy模式,作为xxxBean的代理类。在生成以上两个对象的同时,容器会从部署文件中读取关于安全、事务、持久性等服务并在xxxEjbObject对象和xxxEJBHome对象中添加以上服务的代码。而且xxxEJBHome对象和xxxEjbObject对象都是分布式对象,我们在此只讨论xxxEjbObject对象。所谓分布式对象,从本质上来讲,分为3个部分:object server、skeleton、stub。其中object server和skeleton位于服务器端,而stub位于客户端。Object server负责实现业务逻辑,skeleton负责marshal和unmarshal方法签名。

  图二:分布式对象

  显然,EJB的客户(调用EJB的对象)可以是任何对象,包括EJB和一般的Java类甚至是用任何语言写的corba客户端。

  从EJB的客户视角来看的话,我们只能看到一个home接口、一个remote接口(对于实体bean的话,还可以看见一个主键类,而bean类对客户是不可见的)。但我们从上面的论述,我们可以知道,对于remote接口中地方法调用,实际上是多态地调用XXX_Stub类。即XXX_Stub对象对客户具有可见性(但这种可见性是透明的,即客户不知道这种可见性的存在)。由于,XXX_Stub对象和Object Server实现了相同的接口,并且Object server真正实现了业务逻辑。所以,当在客户端调用XXX_Stub对象的方法时候,XXX_Stub对象通过socket通信机制将方法签名传给XXX_Skeleton对象,XXX_Skeleton对象在去委托Object Server完成业务处理逻辑。因此,Object Server本身发生了改变。我们称XXX_Stub对象是Object Server对象的远程引用,并认为当分布式对象作为参数传递的时候,是通过引用传递的(会产生副作用?D?D即改变参数对象),只不过这种引用是远程引用。可以参见本文的示范代码,了解远程引用。

  在J2EE中实现Observer模式的典型错误

  为了说明问题,我们从一个简单例子开始着手:假设一个或多个位于web tier层的、可以序列化(serilizable)的Java类,想要从位于EJB tier层的一个或多个实体bean中获取数据(它们可能是分布在不同EJB SERVER中)。可以把web tier层的Java类看成是一个分布式异构数据源的联合视图。显然,它们之间存在着依赖关系,而且这两个方面要相互独立(位于不同的tier),并且当一个对象改变的同时,不知道具体又多少个对象有待改变。因此,我们决定用OBSERVER模式来实现这个问题。把bean作为observer,把Entity Bean作为Subject。参照图三Observer设计模式

  图三:Observer设计模式

  这样,就会使起observer作用的Java类对起subject作用的entity bean中具有参数可见性,而且由于它们可以位于不同的机器上,所有我们要为Java类实现serilizable接口,从而使它可以在网络上传递。看上去似乎非常完美,但不幸的是:在运行的时候,我们发现客户端并不能自动更新。究其原因,我们发现,原来Java类是通过值传递的,即entity bean中有一个Java类的拷贝,它对拷贝进行修改,但不会影响原来的Java类。

  但如果我们在另外一种情况下,把entity bean作为observer,而使Java类作为subject,由于Java类拥有entity bean的远程引用,当subject产生事件的时候,会通知entity bean的远程引用作相应的改动。此时与Java类位于不同tier的entity bean的发生了真正的改动,从而实现了事件通知范型的observer模式。另外,为了使observer可以观察多个subject,entity bean要拥有observer对象,而这些observer对象(Java类)通过值传递被序列化到entity bean中,而且这种序列化是深度序列化。由于observer只是准备从subject中提取数据,而不是改变subject的数据,所有这时候的值传递是可行的。但是,由于EJB规范的要求,所有商务方法都要抛出java.rmi.RemoteException,所以单纯的从java类库提供的Observer接口继承是不行的。具体的细节可以参见ejb observer pattern文档(参考 EJBObserverPattern)。

  注意:文档中的附带程序在JB5.0+BAS4.5中会出现corba marshal的问题,如果你只是为了实现一个subject对应多个observer,而且是subject将所有的数据推给observer的话,可以将EJBObserver接口中的update(EJBObservable observable,Object args)改为update(EJBObservable observable,Object args),这样可以使程序在JB5.0+BAS4.5集成运行环境中通过。

  使用JMS在J2EE中实现Observer模式

  消息系统允许分开的未耦合的应用程序之间可靠地异步通信。它使消息产生者和使用者之间的关系是一种"松耦合"的关系。对于使用者,它不在乎谁产生了消息,产生者是否仍在网络上以及消息是什么时候产生的。这就允许建立动态的,可靠的和灵活的系统。整个的子系统能被修改而不会影响系统的其他部分。其实,领域层和表示层的分离的目的也就在于此。

  在消息系统中,通常有两种消息类型:1)发布/订阅(publish/subscribe)。发布/订阅消息系统支持一个事件驱动模型,消息产生者和使用者都参与消息的传递。产生者发布事件,而使用者订阅感兴趣的事件,并使用事件。产生者将消息和一个特定的主题(Topic)连在一起,消息系统根据使用者注册的兴趣,将消息传给使用者。2)点对点(Peer to peer)。在点对点的消息系统中,消息分发给一个单独的使用者。它维持一个"进入"消息队列。消息应用程序发送消息到一个特定的队列,而客户端从一个队列中得到消息。

  在消息系统中,我们可以通过JMS和消息驱动Bean(Message-Driven Bean),用来实现应用程序各个部件之间的异步消息传递。对于Observer模式而言,我们会优先地选用发布/订阅(publish/subscribe)模型。在编写事件的生产者和事件的消费者代码的时候,我们可以直接使用JMS API来编写Producer和Consumer,但是如果事件的消费者位于EJB Tier时,最好采用消息驱动Bean(Message- Driven Bean)。因为消息驱动Bean通常配置成是一个特别的主题(topic)或队列的客户端,作为消息的使用者。它大大地简化了创建一个JMS使用者,创建和配置一个JMS消息使用者这些功能都交由EJB容器来做了。开发人员只需简单地实现消息驱动Bean的接口,配置给EJB服务器,用来创建一个接收消息的商业逻辑部件。另外,当事件的消费者同时有多个,我们可以使用MDB Facade模式来实现Observer模式。

  图四:MDB facade模式

  MDB Facade模式实际上是Session Bean Facade模式的一个变种,因为MDB从本质上来说非常类似于一个无状态的Session Bean,只不过MDB没有remote接口和home接口。图四的MDB Facade模式也可以和Session Bean Facade模式结合起来使用,即一个MDB作为多个Session Bean的Facade,一个Session Bean作为多个Entity Bean的Facade。从而实现消息的传递。

  代码细节可以参照本文的示范代码,它在BEA weblogic中运行通过。

  总结

  我们使用模式的时候,一定要注意模式的意图(intent),而不要过多的去关注模式的实现细节,因为这些实现细节在特定情况下,可能会发生一些改变。在Java类库中采用Java.util.Observable类和Java.util.Observer接口来实现Observer模式,它们在单个的Java VM.中运行的很好,但如果想在EJB中使用它们就会有一些问题。本文主要讨论了在分布式、多层的J2EE平台中如何通过EJB Observer Pattern和JMS来实现Observer模式。

0
相关文章