正 文

J2EE中使用Spring AOP框架和EJB组件


www.7dspace.com  更新日期:2006-2-14 17:52:15  七度空间


  重构EJB组件以使用Spring的EJB类

  想像一个简单的股票报价EJB组件,它返回当前的股票交易价格,并允许设置新的交易价格。这个例子用于说明同时使用Spring Framework与J2EE服务的各个集成方面和最佳实践,而不是要展示如何编写股票管理应用程序。按照我们的要求,TradeManager业务接口应该就是下面这个样子:

public interface TradeManager {
  public static String ID = "tradeManager"; 

  public BigDecimal getPrice(String name); 

  public void setPrice(String name, BigDecimal price);

}

  在设计J2EE应用程序的过程中,通常使用远程无状态会话bean作为持久层中的外观和实体bean。下面的TradeManager1Impl说明了无状态会话bean中TradeManager接口的可能实现。注意,它使用了ServiceLocator来为本地的实体bean查找home接口。XDoclet注释用于为EJB描述符声明参数以及定义EJB组件的已公开方法。

/**
 * @ejb.bean
 *   name="org.javatx.spring.aop.TradeManager1"
 *   type="Stateless"
 *   view-type="both"
 *   transaction-type="Container"
 *
 * @ejb.transaction type="NotSupported"
 * 
 * @ejb.home
 *   remote-pattern="{0}Home"
 *   local-pattern="{0}LocalHome"
 *
 * @ejb.interface
 *   remote-pattern="{0}"
 *   local-pattern="{0}Local"
 */
public class TradeManager1Impl implements SessionBean, TradeManager {
  private SessionContext ctx; 

  private TradeLocalHome tradeHome; 

  /**
   * @ejb.interface-method view-type="both"
   */ 
  public BigDecimal getPrice(String symbol) {
    try {
      return tradeHome.findByPrimaryKey(symbol).getPrice();
    } catch(ObjectNotFoundException ex) {
      return null;
    } catch(FinderException ex) {
      throw new EJBException("Unable to find symbol", ex);
    }
  } 

  /**
   * @ejb.interface-method view-type="both"
   */ 
  public void setPrice(String symbol, BigDecimal price) {
    try {
      try {
        tradeHome.findByPrimaryKey(symbol).setPrice(price);
      } catch(ObjectNotFoundException ex) {
        tradeHome.create(symbol, price);
      }
    } catch(CreateException ex) {
      throw new EJBException("Unable to create symbol", ex);
    } catch(FinderException ex) {
      throw new EJBException("Unable to find symbol", ex);
    }
  } 

  public void ejbCreate() throws EJBException {
    tradeHome = ServiceLocator.getTradeLocalHome();
  } 

  public void ejbActivate() throws EJBException, RemoteException {
  } 

  public void ejbPassivate() throws EJBException, RemoteException {
  } 

  public void ejbRemove() throws EJBException, RemoteException {
  } 

  public void setSessionContext(SessionContext ctx) throws EJBException, RemoteException {
    this.ctx = ctx;
  }

}

  如果要在进行代码更改之后测试这样一个组件,那么在运行任何测试(通常是基于专用的容器内测试框架,比如Cactus或MockEJB)之前,必须要经过构建、启动容器和部署应用程序这整个周期。虽然在简单的用例中类的热部署可以节省重新部署的时间,但是当类模式变动(例如,添加域或方法,或者修改方法名)之后它就不行了。这个问题本身就是把所有逻辑转移到无格式Java对象中的最好理由。正如您在TradeManager1Impl代码中所看到的那样,大量的粘和代码把EJB中的所有内容组合在一起,而且您无法从围绕 JNDI访问和异常处理的复制工作中抽身。然而,Spring提供抽象的便利类,可以使用定制的EJB bean对它进行扩展,而无需直接实现J2EE接口。这些抽象的超类允许移除定制bean中的大多数粘和代码,而且提供用于获取Spring应用程序上下文的实例的方法。

  首先,需要把TradeManager1Impl中的所有逻辑都转移到新的无格式Java类中,这个新的类还实现了一个TradeManager接口。我们将把实体bean作为一种持久性机制,这不仅因为它超出了本文的讨论范围,还因为WebLogic Server提供了大量用于调优CMP bean性能的选项。在特定的用例中,这些bean可以提供非常好的性能。我们还将使用Spring IoC容器把TradeImpl实体bean的home接口注入到TradeDao的构造函数中,您将从下面的代码中看到这一点:

public class TradeDao implements TradeManager {
  private TradeLocalHome tradeHome;
  
  public TradeDao(TradeLocalHome tradeHome) {
    this.tradeHome = tradeHome;
  } 
 
  public BigDecimal getPrice(String symbol) {
    try {
      return tradeHome.findByPrimaryKey(symbol).getPrice();
    } catch(ObjectNotFoundException ex) {
      return null;
    } catch(FinderException ex) {
      throw new EJBException("Unable to find symbol", ex);
    }
  } 

  public void setPrice(String symbol, BigDecimal price) {
    try {
      try {
        tradeHome.findByPrimaryKey(symbol).setPrice(price);
      } catch(ObjectNotFoundException ex) {
        tradeHome.create(symbol, price);
      }
    } catch(CreateException ex) {
      throw new EJBException("Unable to create symbol", ex);
    } catch(FinderException ex) {
      throw new EJBException("Unable to find symbol", ex);
    }
  }

}

  现在,可以使用Spring的AbstractStatelessSessionBean抽象类重写TradeManager1Impl,该抽象类还可以帮助您获得上面所创建的TradeDao bean的一个Spring托管的实例:

/**
 * @ejb.home
 *   remote-pattern="TradeManager2Home"
 *   local-pattern="TradeManager2LocalHome"
 *   extends="javax.ejb.EJBHome"
 *   local-extends="javax.ejb.EJBLocalHome"
 *
 * @ejb.transaction type="NotSupported"
 * 
 * @ejb.interface
 *   remote-pattern="TradeManager2"
 *   local-pattern="TradeManager2Local"
 *   extends="javax.ejb.SessionBean"
 *   local-extends="javax.ejb.SessionBean, org.javatx.spring.aop.TradeManager"
 *
 * @ejb.env-entry
 *   name="BeanFactoryPath" 
 *   value="applicationContext.xml"
 */   
public class TradeManager2Impl extends AbstractStatelessSessionBean implements TradeManager {
  private TradeManager tradeManager; 

  public void setSessionContext(SessionContext sessionContext) {
     super.setSessionContext(sessionContext);
     // make sure there will be the only one Spring bean config
     setBeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance());
  }
  
  public void onEjbCreate() throws CreateException {
    tradeManager = (TradeManager) getBeanFactory().getBean(TradeManager.ID);
  } 
   
  /**
   * @ejb.interface-method view-type="both"
   */ 
  public BigDecimal getPrice(String symbol) {
    return tradeManager.getPrice(symbol);
  } 

  /**
   * @ejb.interface-method view-type="both"
   */ 
  public void setPrice(String symbol, BigDecimal price) {
    tradeManager.setPrice(symbol, price);
  }

}

  现在,EJB把所有调用都委托给在onEjbCreate()方法中从Spring获得的TradeManager实例,这个方法是在 AbstractEnterpriseBean中实现的,它处理所有查找和创建Spring应用程序上下文所需的工作。但是,必须在EJB部署描述符中为 EJB声明BeanFactoryPath env-entry,以便将配置文件和bean声明的位置告诉Spring。上面的例子使用了XDoclet注释来生成这些信息。

  此外还要注意,我们重写了setSessionContext()方法,以便告诉AbstractStatelessSessionBean跨所有EJB bean使用Sping应用程序上下文的单个实例。

  现在,可以在applicationContext.xml中声明一个tradeManager bean。基本上需要创建一个上面TradeDao的新实例,把从JNDI获得的TradeLocalHome实例传递给它的构造函数。下面给出了可能的定义:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "spring-beans.dtd">

<beans> 

  <bean id="tradeManager" class="org.javatx.spring.aop.TradeDao">
    <constructor-arg index="0">
      <bean class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
          <bean id="org.javatx.spring.aop.TradeLocalHome.JNDI_NAME"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
        </property>
        <property name="proxyInterface" value="org.javatx.spring.aop.TradeLocalHome"/>
      </bean>
    </constructor-arg>
  </bean>

</beans>

  在这里,我们使用了一个匿名定义的TradeLocalHome实例,这个实例是使用Spring的JndiObjectFactoryBean从JNDI获得的,然后把它作为一个构造函数参数注入到tradeManager 中。我们还使用了一个FieldRetrievingFactoryBean来避免硬编码TradeLocalHome的实际JNDI名称,而是从静态的域(在这个例子中为TradeLocalHome.JNDI_NAME)获取它。通常,使用JndiObjectFactoryBean时声明 proxyInterface属性是一个不错的主意,如上面的例子所示。

  还有另一种简单的方法可以访问会话bean。Spring提供一个LocalStatelessSessionProxyFactoryBean,它允许立刻获得一个会话bean而无需经过home接口。例如,下面的代码说明了如何使用通过Spring托管的另一个bean中的本地接口访问的MyComponentImpl会话bean:

<bean id="tradeManagerEjb" 
        class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName">
      <bean id="org.javatx.spring.aop.TradeManager2LocalHome.JNDI_NAME"
            class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
    </property>
    <property name="businessInterface" value="org.javatx.spring.aop.TradeManager"/>
</bean>

  这种方法的优点在于,可以很容易地从本地接口切换到远程接口,只要使用SimpleRemoteStatelessSessionProxyFactoryBean修改Spring上下文中的一处bean声明即可。例如:

<bean id="tradeManagerEjb" 
        class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
    <property name="jndiName">
      <bean id="org.javatx.spring.aop.TradeManager2Home.JNDI_NAME"
            class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
    </property>
    <property name="businessInterface" value="org.javatx.spring.aop.TradeManager"/>
    <property name="lookupHomeOnStartup" value="false"/>
  </bean>

  注意,lookupHomeOnStartup property被设置为false,以支持延迟初始化。

  下面,我总结一下到此为止所学习的内容:

    * 上面的重构已经为使用高级的Spring功能(也就是依赖性注入和AOP)奠定了基础。

    * 在没有修改客户端API的情况下,我把所有业务逻辑都移出外观会话bean,这就使得这个EJB不惧修改,而且易于测试。

    * 业务逻辑现在位于一个无格式Java对象中,只要该Java对象的依赖性不需要JNDI中的资源,就可以在容器外部对其进行测试,或者可以使用存根或模仿(mock)来代替这些依赖性。

    * 现在,可以代入不同的tradeManager实现,或者修改初始化参数和相关组件,而无需修改Java代码。

  至此,我们已经完成了所有准备步骤,可以开始解决对TradeManager服务的新需求了。

5页,页码:[1] [2] [3] [4] [5] 

上一篇:情人节个性礼品光盘制作全攻略
下一篇:Alexa排名能帮你赚钱么?
标题:J2EE中使用Spring AOP框架和EJB组件 作者:Eugene Kuleshov 来源:BEA
收藏此页】【打印】【关闭
站 内 搜 索
 

热 点 导 读
特 别 推 荐