服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > JDK > 查看文档

java ejb容器的存取和实现

  作为轻量级的容器,spring常常被认为是ejb的替代品。我们也相信,对于很多 (不一定是绝大多数)应用和用例,相对于通过ejb容器来实现相同的功能而言, sping作为容器,加上它在事务,orm和jdbc存取这些领域中丰富的功能支持, spring的确是更好的选择。
  
  不过,需要特别注意的是,使用了spring并不是说我们就不能用ejb了, 实际上,spring大大简化了从中访问和实现ejb组件或只实现(ejb组件)其功能的复杂性。 另外,如果通过spring来访问ejb组件服务,以后就可以在本地ejb组件,远程ejb组件, 或者是pojo(简单java对象)这些变体之间透明地切换服务的实现,而不需要修改 客户端的代码。
  
  本章,我们来看看spring是如何帮助我们访问和实现ejb组件的。尤其是在访问 无状态session bean(slsbs)的时候,spring特别有用,现在我们就由此开始讨论。
  
  访问ejb 1.1. 概念要调用本地或远程无状态session bean上的方法,通常客户端的代码必须 进行jndi查找,得到(本地或远程的)ejb home对象,然后调用该对象的"create" 方法,才能得到实际的(本地或远程的)ejb对象。前后调用了不止一个ejb组件 上的方法。
  
  为了避免重复的底层调用,很多ejb应用使用了服务定位器(service locator) 和业务委托(bussiness delegate)模式,这样要比在客户端代码中到处进行jndi 查找更好些,不过它们的常见的实现都有明显的缺陷。例如:
  
  通常,若是依赖于服务定位器或业务代理单件来使用ejb,则很难对代码进 行测试。
  
  在仅使用了服务定位器模式而不使用业务委托模式的情况下,应用程序 代码仍然需要调用ejb home组件的create方法,还是要处理由此引入的异常。 导致代码仍然保留了与ejb api的耦合性以及ejb编程模型的复杂性。
  
  实现业务委托模式通常会导致大量的冗余代码,因为我们不得不编写 很多方法,而它们所做的仅仅是调用ejb组件的同名方法。
  
  spring采用的方法是允许创建并使用代理对象,一般是在spring的 applicationcontext或beanfactory里面进行配置,这样就和业务代理类似,只需要 少量的代码。我们不再需要另外编写额外的服务定位器或jndi查找的代码,或者是手写 的业务委托对象里面冗余的方法,除非它们可以带来实质性的好处。
  
  1.2. 访问本地的无状态session bean(slsb)
  假设有一个web控制器需要使用本地ejb组件。我们遵循前人的实践经验, 于是使用了ejb的业务方法接口(business methods interface)模式,这样, 这个ejb组件的本地接口就扩展了非ejb特定的业务方法接口。让我们假定这个 业务方法接口叫mycomponent.
  
  public interface mycomponent {……
  }(使用业务方法接口模式的一个主要原因就是为了保证本地接口和bean的实现类 之间方法签名的同步是自动的。另外一个原因是它使得稍后我们改用基于pojo(简单java对象) 的服务实现更加容易,只要这样的改变是有利的。当然,我们也需要实现 本地home接口,并提供一个bean实现类,使其实现接口sessionbean和业务方法接口 mycomponent.现在为了把我们web层的控制器和ejb的实现链接起来,我们唯一要写 的java代码就是在控制器上公布一个形参为mycomponent的setter方法。这样就可以 把这个引用保存在控制器的一个实例变量中。
  
  private mycomponent mycomponent;

public void setmycomponent(mycomponent mycomponent) {
    this.mycomponent = mycomponent;
}
  然后我们可以在控制器的任意业务方法里面使用这个实例变量。假设我们现在 从spring的applicationcontext或beanfactory获得该控制器对象,我们就可以在 同一个上下文中配置一个localstatelesssessionproxyfactorybean 的实例,它将作为ejb组件的代理对象。这个代理对象的配置和控制器的属性 mycomponent的设置是使用一个配置项完成的,如下所示:

<bean id="mycomponent"
      class="org.springframework.ejb.access.localstatelesssessionproxyfactorybean">
    <property name="jndiname"><value>mycomponent</value></property>
    <property name="businessinterface"><value>com.mycom.mycomponent</value></property>
</bean>

<bean id="mycontroller" class = "com.mycom.mycontroller">
    <property name="mycomponent"><ref bean="mycomponent"/></property>
</bean>
  这些看似简单的代码背后隐藏了很多复杂的处理,比如默默工作的spring aop框架,我们甚至不必知道这些概念,一样可以享用它的结果。bean mycomponent 的定义中创建了一个该ejb组件的代理对象,它实现了业务方法接口。这个ejb组件的 本地home对象在启动的时候就被放到了缓存中,所以只需要执行一次jndi查找即可。 每当ejb组件被调用的时候,这个代理对象就调用本地ejb组件的create方法,并调用 该ejb组件的相应的业务方法。
  
  在bean mycontroller的定义中,控制器类的属性 mycontroller的值被设置为上面代理对象。
  
  这样的ejb组件访问方式大大简化了应用程序代码:web层(或其他ejb客户端) 的代码不再依赖于ejb组件的使用。如果我们想把这个ejb的引用替换为一个pojo, 或者是模拟用的对象或其他测试组件,我们只需要简单地修改bean mycomponent 的定义中仅仅一行java代码,此外,我们也不再需要在应用程序中编写任何jndi查找 或其它ejb相关的代码。
  
  评测和实际应用中的经验表明,这种方式的性能负荷极小,(尽管其中 使用了反射方式以调用目标ejb组件的方法),通常的使用中我们几乎觉察不出。请记住 我们并不想频繁地调用ejb组件的底层方法,虽然如此,有些性能代价是与应用服务器 中ejb的基础框架相关的。
  
  关于jndi查找有一点需要注意。在bean容器中,这个类通常最好用作单件 (没理由使之成为原型)。不过,如果这个bean容器会预先实例化单件(类似xml applicationcontext的变体的行为),如果在ejb容器载入目标ejb前载入bean容器, 我们就可能会遇到问题。因为jndi查找会在该类的init方法中被执行并且缓存结果, 这样就导致该ejb不能被绑定到目标位置。解决方案就是不要预先实例化这个工厂对象, 而允许它在第一次用到的时候再创建,在xml容器中,这是通过属性 lazy-init来控制的。
  
  尽管大部分spring的用户不会对这些感兴趣,但那些对ejb进行aop的具体应用 的用户则会想看看localslsbinvokerinterceptor.
  
  1.3. 访问远程的无状态session bean(slsb)
  基本上访问远程ejb与访问本地ejb差别不大,除了前者使用的是 simpleremotestatelesssessionproxyfactorybean.当然, 无论是否使用spring,远程调用的语义都相同,不过,对于使用的场景和错误处理 来说,调用另外一台计算机上不同虚拟机中的对象的方法其处理有所不同。
  
  与不使用spring方式的ejb客户端相比,spring的ejb客户端有一个额外的 好处。通常如果客户端代码随意在本地ejb和远程ejb的调用之间来回切换,就有 一个问题。这是因为远程接口的方法需要声明其会抛出remoteexception ,然后客户端代码必须处理这种异常,但是本地接口的方法却不需要这样。 如果要把针对本地ejb的代码改为访问远程ejb,就需要修改客户端代码,增加 对remoteexception的处理,反之就需要去掉这样的 异常处理。使用spring 的远程ejb代理,我们就不再需要在业务方法接口和ejb的 代码实现中声明会抛出remoteexception,而是定义一个 相似的远程接口,唯一不同就是它抛出的是remoteaccessexception, 然后交给代理对象去动态的协调这两个接口。也就是说,客户端代码不再需要与 remoteexception这个显式(checked)异常打交道,实际运行中 所有抛出的异常remoteexception都会被捕获并转换成一个 隐式(non-checked)的remoteaccessexception,它是 runtimeexception的一个子类。这样目标服务端就可以 在本地ejb或远程ejb(甚至pojo)之间随意地切换,客户端不再需要关心甚至 根本不会觉察到这种切换。当然,这些都是可选的,我们并不阻止在业务接口中声明 异常remoteexceptions.
  
  2. 使用spring提供的辅助类实现ejb组件spring也提供了一些辅助类来为ejb组件的实现提供便利。它们是为了倡导一些 好的实践经验,比如把业务逻辑放在在ejb层之后的pojo中实现,只把事务隔离和 远程调用这些职责留给ejb.
  
  要实现一个无状态或有状态的session bean,或消息驱动bean,我们的实现 可以继承分别继承abstractstatelesssessionbean, abstractstatefulsessionbean,和 abstractmessagedrivenbean/abstractjmsmessagedrivenbean
  
  考虑这个例子:我们把无状态session bean的实现委托给普通的java服务对象。 业务接口的定义如下:
  
public interface mycomponent {
    public void mymethod(...);
    ...
}
  这是简单java对象实现方式的类:

public class mycomponentimpl implements mycomponent {
    public string mymethod(...) {
        ...
    }
    ...
}
  最后是无状态session bean自身:

public class mycomponentejb implements extends abstractstatelesssessionbean
        implements mycomponent {

    mycomponent _mycomp;

    /**
     * obtain our pojo service object from the beanfactory/applicationcontext
     * @see org.springframework.ejb.support.abstractstatelesssessionbean#onejbcreate()
     */
    protected void onejbcreate() throws createexception {
        _mycomp = (mycomponent) getbeanfactory().getbean(
            servicesconstants.context_mycomp_id);
    }

    // for business method, delegate to pojo service impl.
    public string mymethod(...) {
        return _mycomp.mymethod(...);
    }
    ...
}
  spring为支持ejb而提供的这些基类默认情况下会创建并载入一个beanfactory (这个例子里,它是applicationcontext的子类),作为其生命周期的一部分, 供ejb使用(比如像上面的代码那样用来获取pojo服务对象)。载入的工作是通过 一个策略对象完成的,它是beanfactorylocator的子类。 默认情况下,实际使用的beanfactorylocator的实现类是 contextjndibeanfactorylocator,它根据一个jndi环境变量 来创建一个applicationcontext对象(这里是ejb类,路径是 java:comp/env/ejb/beanfactorypath)。如果需要改变 beanfactory或applicationcontext的载入策略,我们可以在子类中重定义了的 setsessioncontext()方法或具体ejb子类的构造函数中调用 setbeanfactorylocator()方法来改变默认使用的 beanfactorylocator实现类。具体细节请参考javadoc.
  
  如javadoc中所述,有状态session bean在其生命周期中可能会被钝化并重新激活, 如果是不可序列化的beanfactory或applicationcontext,由于它们不会被ejb容器保存, 所以还需要手动在ejbpassivate和ejbactivate 这两个方法中分别调用unloadbeanfactory()和loadbeanfactory, 才能在钝化或激活的时候卸载或载入。
  
  有些情况下,要载入applicationcontext以使用ejb组件, contextjndibeanfactorylocator的默认实现基本上足够了, 不过,当applicationcontext需要载入多个bean,或这些bean初始化所需的时间或内存 很多的时候(例如hibernate的sessionfactory的初始化),就有可能出问题,因为 每个ejb组件都有自己的副本。这种情况下,用户会想重载 contextjndibeanfactorylocator的默认实现,并使用其它 beanfactorylocator的变体,例如contextsingleton 或者beanfactorylocator,他们可以载入并共享一个 beanfactory或applicationcontext来为多个ejb组件或其它客户端所公用。这样做 相当简单,只需要给ejb添加类似于如下的代码:
  
   /**
    * override default beanfactorylocator implementation
    * 
    * @see javax.ejb.sessionbean#setsessioncontext(javax.ejb.sessioncontext)
    */
   public void setsessioncontext(sessioncontext sessioncontext) {
       super.setsessioncontext(sessioncontext);
       setbeanfactorylocator(contextsingletonbeanfactorylocator.getinstance());
       setbeanfactorylocatorkey(servicesconstants.primary_context_id);
   }

扫描关注微信公众号