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

  概念

  要本地或远程无状态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查找代码或者是手写 业务委托对象里面冗余思路方法除非它们可以带来实质性好处

  访问本地无状态Session Bean(SLSB)

  假设有个web控制器需要使用本地EJB组件我们遵循前人实战经验 于是使用了EJB业务思路方法接口(Business Methods Interface)模式这样 这个EJB组件本地接口就扩展了非EJB特定业务思路方法接口让我们假定这个 业务思路方法接口叫MyComponent

  public erface MyComponent {

  ...

  }

  (使用业务思路方法接口模式个主要原因就是为了保证本地接口和bean实现类 的间思路方法签名同步是自动另外个原因是它使得稍后我们改用基于POJO(简单Java对象) 服务实现更加容易只要这样改变是有利当然我们也需要实现 本地Home接口并提供个Bean实现类使其实现接口SessionBean和业务思路方法接口 MyComponent现在为了把我们Web层控制器和EJB实现链接起来我们唯要写 Java代码就是在控制器上公布个形参为MyComponentter思路方法这样就可以 把这个引用保存在控制器个例子变量中

private MyComponent myComponent;
public void MyComponent(MyComponent myComponent) {
this.myComponent = myComponent;
}
然后我们可以在控制器任意业务思路方法里面使用这个例子变量假设我们现在 从SpringApplicationContext或BeanFactory获得该控制器对象我们就可以在 同个上下文中配置个LocalStatelessSessionProxyFactoryBean 例子它将作为EJB组件代理对象这个代理对象配置和控制器属性 myComponent设置是使用个配置项完成如下所示:

<bean id="myComponent"
="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" = "com.mycom.myController">
<property name="myComponent"><ref bean="myComponent"/></property>
</bean>


  这些看似简单代码背后隐藏了很多复杂处理比如默默工作Spring AOP框架我们甚至不必知道这些概念样可以享用它结果Bean myComponent 定义中创建了个该EJB组件代理对象它实现了业务思路方法接口这个EJB组件 本地Home对象在启动时候就被放到了缓存Cache中所以只需要执行次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思路方法中被执行并且缓存Cache结果 这样就导致该EJB不能被绑定到目标位置解决方案就是不要预先例子化这个工厂对象 而允许它在第次用到时候再创建在XML容器中这是通过属性 lazy-init来控制

  尽管大部分Spring用户不会对这些感兴趣但那些对EJB进行AOP具体应用 用户则会想看看LocalSlsbInvokerInterceptor

  访问远程无状态Session Bean(SLSB)

  基本上访问远程EJB和访问本地EJB差别不大除了前者使用是 SimpleRemoteStatelessSessionProxyFactoryBean当然 无论是否使用Spring远程语义都相同不过对于使用场景和处理 来说另外台计算机上区别虚拟机中对象思路方法其处理有所区别

  和不使用Spring方式EJB客户端相比SpringEJB客户端有个额外 好处通常如果客户端代码随意在本地EJB和远程EJB的间来回切换就有 个问题这是远程接口思路方法需要声明其会抛出RemoteException 然后客户端代码必须处理这种异常但是本地接口思路方法却不需要这样 如果要把针对本地EJB代码改为访问远程EJB就需要修改客户端代码增加 对RemoteException处理反的就需要去掉这样 异常处理使用Spring 远程EJB代理我们就不再需要在业务思路方法接口和EJB 代码实现中声明会抛出RemoteException而是定义个 相似远程接口区别就是它抛出是RemoteAccessException 然后交给代理对象去动态协调这两个接口也就是说客户端代码不再需要和 RemoteException这个显式(checked)异常打交道实际运行中 所有抛出异常RemoteException都会被捕获并转换成个 隐式(non-checked)RemoteAccessException它是 RuntimeException个子类这样目标服务端就可以 在本地EJB或远程EJB(甚至POJO)的间随意地切换客户端不再需要关心甚至 根本不会觉察到这种切换当然这些都是可选我们并不阻止在业务接口中声明 异常RemoteExceptions

  使用Spring提供辅助类实现EJB组件

  Spring也提供了些辅助类来为EJB组件实现提供便利它们是为了倡导些 好实战经验比如把业务逻辑放在在EJB层的后POJO中实现只把事务隔离和 远程这些职责留给EJB

  要实现个无状态或有状态Session Bean或消息驱动Bean我们实现 可以继承分别继承AbstractStatelessSessionBean AbstractStatefulSessionBean和 AbstractMessageDrivenBean/AbstractJmsMessageDrivenBean

  考虑这个例子:我们把无状态Session Bean实现委托给普通Java服务对象 业务接口定义如下:

public erface MyComponent {
public void myMethod(...);
...
}
这是简单Java对象实现方式类:
public MyComponentImpl implements MyComponent {
public String myMethod(...) {
...
}
...
}


  最后是无状态Session Bean自身:

public 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 _disibledevent= (MyComponent) getBeanFactory.getBean(
ServicesConstants.CONTEXT_MYCOMP_ID);
}
// for business method, delegate to POJO service impl.
public String myMethod(...) {
_myComp.myMethod(...);
}
...
}
Spring为支持EJB而提供这些基类默认情况下会创建并载入个BeanFactory (这个例子里它是ApplicationContext子类)作为其生命周期部分 供EJB使用(比如像上面代码那样用来获取POJO服务对象)载入工作是通过 个策略对象完成它是BeanFactoryLocator子类 默认情况下实际使用BeanFactoryLocator实现类是 ContextJndiBeanFactoryLocator它根据个JNDI环境变量 来创建个ApplicationContext对象(这里是EJB类路径是 java:comp/env/ejb/BeanFactoryPath)如果需要改变 BeanFactory或ApplicationContext载入策略我们可以在子类中重定义了 SessionContext思路方法或具体EJB子类构造 BeanFactoryLocator思路方法来改变默认使用 BeanFactoryLocator实现类具体细节请参考JavaDoc



  如JavaDoc中所述有状态Session Bean在其生命周期中可能会被钝化并重新激活 如果是不可序列化BeanFactory或ApplicationContext由于它们不会被EJB容器保存 所以还需要手动在ejbPassivate和ejbActivate 这两个思路方法中分别unloadBeanFactory和loadBeanFactory 才能在钝化或激活时候卸载或载入

  有些情况下要载入ApplicationContext以使用EJB组件 ContextJndiBeanFactoryLocator默认实现基本上足够了 不过当ApplicationContext需要载入多个bean或这些bean化所需时间或内存 很多时候(例如HibernateSessionFactory化)就有可能出问题 每个EJB组件都有自己副本这种情况下用户会想重载 ContextJndiBeanFactoryLocator默认实现并使用其它 BeanFactoryLocator变体例如ContextSingleton 或者BeanFactoryLocator他们可以载入并共享个 BeanFactory或ApplicationContext来为多个EJB组件或其它客户端所公用这样做 相当简单只需要给EJB添加类似于如下代码: /**

* Override default BeanFactoryLocator implementation
*
* @see javax.ejb.SessionBean#SessionContext(javax.ejb.SessionContext)
*/
public void SessionContext(SessionContext sessionContext) {
super.SessionContext(sessionContext);
BeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance);
BeanFactoryLocatorKey(ServicesConstants.PRIMARY_CONTEXT_ID);
}




Tags:  ejb存取

延伸阅读

最新评论

发表评论