君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理

5.3.5  Facade Session Bean

数据实体bean建立完成后,下面主要是完成对这些数据模型新增、修改和查询等操作。因为实体bean有很多个,如果让客户端直接操作这些实体bean,如图5-11所示。 
1
图5-11  客户端直接操作实体Bean
从Servlet的一个方法中向EJB层发出了对多个实体bean的操作方法,因为EJB层是一个分布式的系统,在集群运行环境下,Web容器基本不与EJB容器位于同一台服务器上。因此Web层发出的每个方法都将会产生一次网络调用,由此引发严重的网络负担。如果两者距离较长,中间经过防火墙等网关,还会引起较长的处理过程,导致死锁等现象发生。
很显然,在客户端Servlet中直接调用实体Bean,将客户端和服务器端紧紧地耦合在一起。如果EJB层将来需要改变,那么必须修改客户端。没有松散的耦合性就无法剥离功能,也就不能实现充分的重用。同样,因为紧密的耦合联系在一起,调试或维护时就非常困难。
Facade模式可以在客户端和EJB之间建立一个统一接口,客户端只要直接和Facade类交互操作,通过Facade类再操作那些实体Bean,如图5-12所示。
1
图5-12  Facade
使用Facade模式克服了客户端直接访问EJB的缺点,减轻了网络负载,保证了事务处理的完整性,同时实现了Web层和EJB层的解耦。从而可以使得EJB层被多个客户端调用,达到了重用的目的。
EJB中的无状态Session Bean非常适合实现Facade功能。无状态Session Bean是对象池支持的功能类,使用起来方便而且快速。客户端调用无状态Session Bean时,容器只要从对象池中捡取一个事先生成的无状态Session Bean实例为之服务。
无状态Session Bean是使用最频繁的一种EJB,就如同在Web层使用JavaBeans一样频繁,大多数业务逻辑功能都可以通过SessionBean实现,Facade功能只是其最常用的一个用处。
Session Bean也可以通过JBuilder图形工具创建,然后对其Bean实现类进行手工代码输入,将业务逻辑写入Bean实现类中,以Facade为例,建立一个名叫CustomerManager的类担任Facade工作,代码如下:
public class CustomerManagerBean implements SessionBean {
  private final static Logger logger = Logger.getLogger(CustomerManagerBean.class);
  SessionContext sessionContext;
  CustomerHome chome;

  /**
   * 在ejbCreate()可以设置一些资源引用作为cache
   * 下次客户端再调用此bean时,容器从Pool中拉出这个bean
   * 本ejbCreate方法不会执行,但是CustomerHome home指向还是存在
   * @throws CreateException
   */
  public void ejbCreate() throws CreateException {
    try {
      //委托给ServiceLocator类进行CustomerHome定位
      ServiceLocator sl = new ServiceLocator();
      chome = (CustomerHome) sl.getLocalHome(JNDINames.CUSTOMER_HOME);
    } catch (Exception ex) {
      logger.error(ex);
      throw new CreateException(ex.getMessage());
    }
  }

  public void createCustomer(Customer customerDTO) {
    //调试信息
    logger.debug(" --->> enter session bean : createCustomer");
    try {
      logger.debug(" id=" + customerDTO.getId());
      CustomerLocal customerLocal = chome.create(customerDTO.getId());
      customerLocal.setFirstName(customerDTO.getFirstName());
      customerLocal.setLastName(customerDTO.getLastName());
      logger.debug(" id=" + customerDTO.getId() + " --->> customer created!");
    } catch (Exception ex) {
      logger.error(" --->> customer create error:" + ex);
    }
  }

  public Customer getCustomerById(Customer customerDTO) {
    try {
      CustomerLocal customerLocal = chome.findByPrimaryKey(customerDTO.getId());
      Return customerLocal.getCustomer();
    } catch (FinderException ex) {
      logger.warn(ex);
    } catch (Exception ex) {
      logger.error(ex);
    }
    return null;
  }

  public void ejbRemove() {  }
  public void ejbActivate() { }
  public void ejbPassivate() { }
  public void setSessionContext(SessionContext sessionContext) {
    this.sessionContext = sessionContext;
  }
}
这里省略了CustomerManagerBean的本地接口代码、Home代码以及ejb-jar.xml部署描述代码。这些部分的代码要比实体Bean简单得多,而且也是由JBuilder图形工具自动生成的。
在ejbCreate中使用了Service Locator模式,该模式主要是将一些网络间寻找定位的工作委托给专门的类来实现,具体见http://java.sun.com/blueprints/corej2eepatterns/Patterns /ServiceLocator.html。
在createCustomer方法中,输入参数使用Customer 实例,而不是下列语句:
createCustomer(String id, String firstName, String lastName)
这是一种将参数封装起来形成一个对象,在和EJB层作来回传送的方式为Transfer Object的模式。Transfer Object模式可以减轻EJB网络间的调用,将需要的参数打包一次性传送给对方,这类似于传输运送物品,当有很多东西需要运送时,往往是将这些物品一次性包装起来,通过交通工具一次性传送过去,更多细节将在下节讨论。
CustomerManager是一个Facade类,封装了对实体Bean的各种访问:新增、查询或修改等,而客户端只要统一调用CustomerManager就可以,这样显得思路清晰,有条理。
但是如果过度使用Facade模式还会引起混乱。因为有Facade这个统一接口,许多开发者为了省事,在很多实体Bean而且系统很复杂的情况下,将所有实体Bean的访问都封装在一个Facade类中,导致这个Facade类变得庞大,而且没有规则,失去了良好的重用性和可维护性。
其实现实世界有很多事情的处理办法可以在软件中借鉴。比如军队人很多,就分成军区、军、师直至班;学校人也很多,就分成年级、班级等。这些朴素的分类规则同样适合复杂的软件系统。依据具体应用将Facade分类,形成多个各自有特点的Facade群,例如用户资料管理合用一个Facade类;记账管理合用一个Facade类;进出仓库管理合用一个Facade类,具体如何分类依据对系统需求的认识和细化。

5.3.6  Transfer Object模式

客户端Web层向EJB层发出的调用无非是两个方面:数据和有关数据功能操作。前面章节已经通过Facade模式统一了有关数据功能操作的封装。在本节,将讨论数据本身的统一封装问题。
在实际项目中,客户端所要求数据项目也可能是多个的。例如,如果在客户端要显示一个客户的城市、街道等详细地址,那么是不是分别通过EJB查询数据库?如果每个数据项都分别操作一次EJB,那这种情况又类似于客户端直接操作实体Bean,带来的危害是增加了网络操作负担,降低了性能。
很显然,数据也需要打包争取通过网络一次性操作就能获得。例如将城市city、街道street组合成一个数据对象,在这个数据对象中包含一些字段属性和数值,这种专门用来方便网络传输实行临时打包的对象叫Transfer Object或Data Transfer Object。
当客户端从EJB中获取数据,EJB将一些数据数值封装构造成Transfer Object,通过它传输给客户端;在一般情况,也可以使用Transfer Object将客户端数据传输给EJB。
Transfer Object模式和Facade模式是EJB系统中最经常使用的两个模式,两者都有一个宗旨:提高分布式环境中网络计算性能,争取通过一次性网络调用完成一系列操作,因此这个两个模式在使用时显得特别灵活,特别是Transfer Object模式,会因为实际系统中错综复杂的数据而变得花样百出,如Domain Data Transfer Object 以及Custom Data Transfer Object,有可能需要专门的Transfer Object来生产符合各种需要的数据包装体。有兴趣者可参阅http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html。
构建Transfer Object基本出发点是为了满足客户端获取数据的需求,在大多数情况下,可以参考基本业务对象或实体Bean构建组合Transfer Object;在有些情况下,当客户端获取的数据可能涉及到不同的实体Bean,那么需要将从各个表中获取的字段数值临时构建成一个Transfer Object。
本项目中是直接将业务对象作为Transfer Object,如Customer、Subscription等。

5.4  Web与EJB接口框架

为了避免在大项目中Web层和EJB层过分耦合,提高Web层和EJB层的开发效率,加强J2EE系统的可维护性和可拓展性,需要在Web层和EJB层之间建立一个类似接口网关的框架系统,这样可以适应推广在各个J2EE项目中。

5.4.1  框架的设计

在本项目中,由于使用了CustomerManager代表实体Bean的统一接口,这样客户端可以直接调用CustomerManager了。在本项目中这个客户端位于Web容器中,是Strut的一个Action,以CustomerAction为例,它负责处理前台JSP提交的表单数据,经过简单封装,然后递交到EJB层进行“深加工”:
public class CustomerAction extends Action {
  public ActionForward execute(ActionMapping actionMapping,
                               ActionForm actionForm,
                               HttpServletRequest httpServletRequest,
                               HttpServletResponse httpServletResponse) {
    CustomerForm customerForm = (CustomerForm) actionForm;
    CustomerDTO customerDTO = new CustomerDTO();
    try {
      //将customerForm的信息复制到customerDTO中
      BeanUtils.copyProperties(customerDTO, customerForm);
      //对EJB的调用
      InitialContext ic = new InitialContext();
      Object o = ic.lookup("CustomerManagerLocal");
      CustomerManagerLocalHome cmhome = (CustomerManagerLocalHome) o;
      CustomerManagerLocal customerManager = cmhome.create();
      customerManager.createCustomer(customerDTO);
    } catch (Exception ex) {
      logger.error("error happened:" + ex);
      return (actionMapping.getInputForward());
    }
    return (actionMapping.findForward("createCustomOk"));
  }
}

 

在CustomerAction中实现了对EJB的调用,这种将EJB调用直接嵌入Action中有很多缺点:
加重网络负担。如果在CustomerAction一个方法体内分别调用多个不同的Facade类,就类似客户端直接调用实体Bean的问题,大大加重了网络传输负担。
可维护性差。如果在CustomerAction再增加editCustomer等其他方法,CustomerAction就变得复杂,导致维护性和拓展性降低。
与Web层过分耦合。Action属于Strut的一个部分,这样造成整个系统严重依赖Strut,如果将来有新的客户端(非HTML等),Strut框架不能使用的情况下,需要对系统Web层进行较大改动。
与EJB层过分耦合。由于在Action代码中直接指定了EJB调用代码。如CustomerAction中直接指定了EJB CustomerManager作为逻辑运算的进一步处理。但是在一个大型系统中,这种情况随时会发生改变。如其他Team由于开发了新的客户EJB组件,那么就需要直接修改CustomerAction代码。
针对这些缺点,首先采取分离措施,将EJB访问调离Action类。这样在前台表现层和EJB层之间形成了一个新的Proxy层。
那么,如何通过前台Action来透过Proxy触发相应的EJB Service? 
EJB Command模式可作为实现事件的触发机制。如Sun公司的J2EE源码PetStore在Web与EJB接口之间采用了EJB Command模式。下面是采取EJB Command模式的优点。
适合快速开发。编制Command具体实现类就能将整个流程运行起来,而且频繁修改的只是一个普通的Javabeans。
与表现层解耦合。如果在Web层中使用Strut框架,实现了与Strut的隔离。
与EJB层解耦。将事件Event与具体EJBAction触发关系放置在配置文件eventmappings.xml中,可以方便修改和定制,无需直接修改代码。
维护性强。由于事件Event和EJBAction是一对一关系,Command模式只有一个执行方法,因此,比较容易让其他Team理解,Web层和EJB层接口变得条理清晰。
这些优点都从PetStore源码中可以发现,以用户资料修改为例,CustomerEvent通过WebController触发了EJBControllerLocal,而EJBControllerLoca委托状态机StateMachine读取配置文件eventmappings.xml调用相应的Action类进行处理,CustomerEvent对应的Action类是CustomerEJBAction,CustomerEJBAction是一个普通的Javabean,即处理CustomerEvent是由EJB委托普通Javabean来实现真正的逻辑运算,如创建新的客户资料。
这里有一个明显的缺点,事务处理机制粗糙。因为创建新客户资料每一句代码是在Javabean中实现,而不是在正常的session bean实现,因此不能实现方法体内的事务机制。
还有一个缺点是,类似CustomerEJBAction这样的类不能在session bean中保存状态, 也就是说,无法实现有状态行为。
既然直接使用模式还有很多缺点,那么需要建立基于这些模式的一种框架,该框架能够克服直接使用这些模式导致的缺点。该框架的设计目标:
实现EJB Command的优点。
实现与Web层和EJB层的解耦。通过配置文件,将两者具体Service对应起来。
提供对有状态Session Bean的支持。
该框架的结构如图5-13所示。
1
图5-13  结构图

posted on 2013-10-06 23:02  刺猬的温驯  阅读(123)  评论(0)    收藏  举报