<!----><!----><!----> <!---->
历时9 个多月的EJB3.1 系列文章终于要划上圆满的句号了。作为EJB3.1 专家组的代言人,Reza Rahman 继续与我们分享EJB3.1 的新特性。最难能可贵的是,本文最后他还就网友们的评论和建议整理出了一些大家都关心或感兴趣的话题,比如说“是否应该为Spring 框架量身定制一套EJB3.1 ?”
不过最终的规范还得几个月后才出来,所以,其中的变数也是未知的。不管怎么样,无论是否对EJB3.1 感冒,了解其特性还是很有必要的。
讨论
该系列文章预先给大家介绍了专家组们正在继续研究JavaEE 下一版本的规范——EJB3.1 所带来的改变。理想情况下,文章提到的这些变化,希望可以尽早的从你那里得到反馈从而使得专家组可以做出最好的选择。EJB3.0 已经将笨重的JavaEE 编程模型转变得简洁明了。EJB3.1 的目标就是在此基础上通过添加一系列必须的特性,使得EJB 走的更远。
前四篇文章中,我们已经谈过了:可选的Session Beans 接口,Singleton Beans ,增强的EJB Timer Service ,异步Session Bean 调用,EJB Lite 和WebBeans/EJB3.1 的整合。本文作为EJB3.1 系列的最后一篇,我会继续谈谈Session Bean 的全局JNDI 命名 方式和在Java SE 平台使用可嵌入式EJB3 容器 。不过请记住,这一切还没有最终敲定,JCP 成员还只是内部讨论的结果,所以你仍然可以向我们反馈任何信息。
标准的全局JNDI 命名
你也许已经知道了,所有的Session Beans 都是自动注册到JNDI 上的。无论是通过依赖注入还是原有的lookup 方式,客户端都可以从JNDI context 中取得bean 的引用。一个在容器的JNDI conext 中注册的bean 的命名通常作为“全局JNDI 的命名”。历来应用服务器供应商在给Session Bean 注册JNDI 名称的方式不尽相同。
那就意味着凡是依赖于具体供应商所提供的JNDI 命名方式后,移植就不方便了。对于那些使用了EJB3.0 依赖注入特性的项目来说这不是什么大问题,因为容器有责任去解析你代码中所依赖的@EJB 标注,从而获得JNDI 。那么问题就出在不是所有的场合下都能使用依赖注入。比如说,你想直接通过JNDI context 来直接获得non-managed( 非容器托管) 的对象 ( 比如说,JSP 页面或简单的单元测试) 。
好了,现在咱们需要一些代码来澄清我所表述的问题。下面是摘自EJB3 in Action 的一个关于Session Bean 的实例:
@Remote public interface PlaceBid { void addBid(Bid bid); } @Stateless(name="PlaceBid") public class PlaceBidBean implements PlaceBid { @PersistenceContext private EntityManager entityManager; public void addBid(Bid bid) { entityManager.persist(bid); } }
<!----><!---->
<!---->
假设在容器中,PlaceBidBean 的全局JNDI 命名为“chapter2/PlaceBid/remote ”。其中“chapter2 ”部分可作为JavaEE 的应用服务器名称;“PlaceBid ”部分可以采用@Stateless 标注的名称属性;最后的“remote ”部分可以PlaceBidBean 实现的是@Remote 接口。下面是某个Servlet 使用该Session Bean 的例子:
public class PlaceBidServlet extends HttpServlet { @EJB(beanName="PlaceBid") private PlaceBid placeBid; public void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... placeBid.addBid(bid); ... } }
<!----><!----> <!---->
容器自动解析@EJB 标注,得到它的beanName 属性值然后分别去与标有@ Remote
的PlaceBid 接口,以及标有@Stateless 的PlaceBidBean 实现类进行匹配。这一切在容器中使用依赖注入的方式是多么优雅,而接下来的单元测试却就不这么乐观了——因为它依赖于供应商指定的全局JNDI 命名。
public class PlaceBidClient { public static void main(String[] args) throws Exception { Context context = new InitialContext(); PlaceBid placeBid = (PlaceBid) context.lookup("chapter2/PlaceBid/remote"); // Non-portable placeBid.addBid(new Bid("rrahman", 10059, 200.50)); } }
<!----><!----> <!---->
所有的代码都是用供应商指定的全局JNDI 命名来实现,那当你的应用程序从一个应用服务器迁移到另一个服务器上的话,你会发现那就是噩梦的开始。如同许多JavaEE 开发商一样,开发时使用的是一种应用服务器(一般是开源的),布署时却用另外一种商业服务器时,就会变得很棘手。更糟糕的是大多数供应商的全局JNDI 命名方式还变化特别大,你得编写几种lookup 方式来复杂的移植性问题。为了进一步的说表明这个问题,假设我们建立了一个叫“action-bazaar ”的EAR 应用程序,它里面有一个叫“action-bazaar-ejb ”的EJB-JAR 包,包下有一个bean 叫PlaceBidBean 的Stateless session bean (通常情况下,如果没有设置@Stateless 的name 属性,bean 的name 默认就是该bean 的class name )。表1 显示了不同的应用服务器下,各自全局JNDI 命名的不同方式。
<!----><!----> <!---->
JBoss global JNDI name |
action-bazaar/PlaceBidBean/remote |
GlassFish global JNDI name |
PlaceBid |
WebSphere Community Edition global JNDI name |
action-bazaar-ejb/PlaceBidBean/PlaceBid |
Oracle Application Server (OC4J) global JNDI name |
PlaceBidBean |
<!----><!----> <!---->
表 1: 各个供应商指定的全局JNDI 命令方式
<!----><!----> <!---->
标准的全局JNDI 命名解决了移植性的问题。下面我就来说明这个JNDI 命名在容器中的是如何指定的。目前专家组的想法是尽可能的让Session Bean 的JNDI 命名清晰,并保证在跨多个应用服务器时还得保证唯一。于是可以考虑用“应用程序名+ 模块名+bean 名称+bean 的接口”来组成JNDI ,请看下面的例子:
java:global[/<application-name>]/<module-name>/<bean-name>#<interface-name>
<!----><!----> <!---->
正如你所看到,万一要布署的EJB 不打包成EAR ,而是以EJB-JAR 或WAR 形式发布的话,应用程序名称就应该是可选 项了。另一方面,模块名称又可以继承EJB-JAR 或WAR 包。现在我把上文的PlaceBidBean 按照标准全局命名就可以写成:java:global/action-bazaar/action-bazaar-ejb/PlaceBidBean#PlaceBid 。当然,你也许已经注意到这个例子的“PlaceBid ”接口只有一个实现类,所以接口名称 对于本例子来区分唯一性的问题不是很重要了。如果一个EJB 只有一个接口或压根就没有接口,这种现象是很普遍的,我们干脆可以不写接口名称。那么简写的全局命名就变成:java:global/action-bazaar/action-bazaar-ejb/PlaceBidBean 。
你觉得简写有好处吗?会不会将来出现什么问题?你觉得将全局JNDI 命名标准化这件事行不行的通?你对此有什么别的看法吗?
基于Java SE 的可嵌入式EJB3 容器
一提到EJB ,大多数人立马想到诸如WebLogic 或WebSphere 这样全方面的JAVA EE 应用程序服务器。现在我要说的是,你们错了。 已经有很多开源嵌入式EJB3 容器可以在非JAVA EE 环境中运行了(比如说进行单元测试)。这些嵌入式EJB3 容器包括OpenEJB( 由Apache Geronimo 和WebSphere 社区版本而来) 和嵌入式的Jboss 。甚至连GlassFish 最近都附加了embeddable container (嵌入式容器)的功能。
这些举措无疑都加速embeddable EJB 3 container 的标准化进程,从而让EJB3 也随心所欲的运行在JAVA SE 环境之上。最初embeddable container 面向的是单元测试,但现在还还可以用来做“离线”批处理,利用EJB3.1 的分布式事务处理特性,消息机制,计划调度,与桌面应用程序的异步通信,还可以将其嵌入在像Tomcat 这样流行的servlet 容器中。图1 概括了嵌入式EJB3.1 容器的平台架构(注意,EJB3.1 所依赖的JNDI 和RMI APIs 都囊括在JAVA SE 中了 )。
<!----><!----> <!---->
图1: EJB 3.1 Embeddable Containers Concept
<!----><!----> <!---->
从开发人员的角度来说,embeddable container 是透明的。下面的例子就是在使用embeddable container 后,代码应该如何编写:
public class PlaceBidClient { public static void main(String[] args) throws Exception { EJBContainer container = EJBContainerFactory.createEJBContainer(); Context context = container.getContext(); PlaceBid placeBid = (PlaceBid) context.lookup("java:global/action-bazaar/PlaceBid"); placeBid.addBid(new Bid("rrahman", 10059, 200.50)); container.close(); } }
<!----><!----> <!---->
当命令行应用程序main 运行时,这段使用了embeddable EJB3 container 的代码会与之共存在相同的JVM 进程中。EJBContainer.getContext() 方法提供了一个句柄 来取得嵌入式容器的JNDI context 。注意EJBContainer.close() 的方法不够严格,但出发点很好。虽然将来大多数embeddable container 有可能会支持所有EJB 特性,但目前的embeddable container 只要求可以支持EJB Lite (EJB 的一个子集 )就行了。
对于embeddable container 支持特性的不断完善,你是怎么想的?你还有没有一些我没有涉及到的地方?有一种与embeddable container 密切关的特性就是是否可以在那些非容器托管的测试类 上也提供EJB 的依赖注入支持。呵呵,我想EJB3.1 专家组搞定不了这个难题. 留给JAVA EE 6 的专家组去解决吧。你觉得这样有价值吗?如果单元测试可以这样写,你们觉得酷吗?
@RunWith(EmbeddableEJB3Runner.class) public class PlaceBidTest { @EJB private PlaceBid placeBid; @Test public void testAddBid() { placeBid.addBid(new Bid("rrahman", 10059, 200.50)); } }
其它精神食粮
Casey Stengel (著名的棒球教练 )说过“不要预言,尤其是预测未来。”其实预测未来真的很繁琐,但作为思想实验[ 注1] 的话还是有点用的,那么我们开始吧。最近我觉得有很多东都远远超过了EJB3.1/Java EE6 的范畴。我简要的将其概括如下(实际上,很多朋友已经问过我某些话题了):
<!---->l <!---->EJB3 和Spring 的整合:我坚信EJB3 和Spring 之间的整合的可能性非常大,就像现在的WebBeans+EJB3 或Seam+EJB3 的类似组合一样。现在EJB3 和Spring2.5 可以整合了,尤其在embeddable EJB3 container 上表现不俗。但是,我们会继续踏着BEA 的Pitchfork 项目所走的历程,着手开始在Spring 框架(或称之为应用程序平台)之上专门提供相应的EJB3 支持。
<!---->l <!---->JAX-RS 整合:可能你早就知道了,JAX-RS 是JAVA EE API 中的新成员,专注于基于REST 风格的web services 。鉴于EJB 对支持JAX-WS 的出色表现,我相信EJB 同样也会很完美的支持JAX-RS 。自从JBOSS JAX-RS 的实现RESTeasy 发布以来,就表明EJB3.1 也有机会打个翻身仗,这不Sun 的Jersey 也在构建进程。
<!---->l <!---->OSGi 交互能力:OSGi 被看作是EJB 和JAVA EE 交互的纽带。我们可以将可布署的JAVA EE 应用程序作为OSGi bundles ,从而证明上述观点是显而易见的。似乎像Sun ,IBM 和Jboss 这些巨头都不遗余力的往这块“砸钱”,直到它最终变成一个标准。
<!---->l <!---->EJB3 和RIA :不同于其它服务端组件开发模式,EJB 怀疑HTTP 。也许从Stateful Session Beans 的提出,就可以看出EJB 有能力在服务端管理状态,而无须依赖于任何客户端用户的HTTP sessions 。同样,EJB 在对远程客户端安全的透明传播和远程事务上下文的支持也是颇有建树。这些特性都使得EJB 绝对是RIA 应用程序的一个不错选择——更贴近于纯客户端的架构体系。像这些技术上的结合的事,可以通过健壮的EJB/RMI 来构建RIA 客户端,也可以通过扩展EJB 固有的远程特性,再利用HTTP 端口来实现一个高效的二进制传输协议来服务于RIA 客户端。我是希望我所描述的场景可以发生在像JavaFX 这样的RIA 技术上。
看到这里,你又有什么新的认识?我对是否应让native EJB3.1 支持Spring 框架(或平台)的反馈信息非常感兴趣。
你的帮助是必要的
首先,我要感谢所有对EJB3.1 系列文章发表评论和提出建立的朋友们。我深受鼓舞,并希望你们再接再厉。你可以直接通过JCP 的邮箱[email protected] 反馈给专家组,当然还可以随时给我抄送一份[email protected] .
我还非常渴望你能看看 EJB3.1 的公开草案 。虽然有一部分规范是有那么点枯燥,毕竟草案最初面向的还是EJB3.1 的实现者。当然,里面还有有一些鲜为人知的精彩部分。它们并不在我所写的EJB3.1 系列文章中,因为最终的规范 还有几个月才能出来。(不容易啊,终于提到“最终”两字了)
即使最终规范出来后,很多别的反馈方式,比如说许多开源JAVA EE 供应商都支持public issue trackers (公共问题跟踪系统)。这就是软件开发所流传的:最好的想法来源于用户!
最后,预祝我们所有的专家组在下一个EJB 规范版本好运。
好了,朋友们,我们要划上一个圆满的句号了………………….
参考资料
<!---->1. <!---->New Features in EJB 3.1, http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesinEJB3-1 .
<!---->2. <!---->New Features in EJB 3.1 - Part 2, http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesEJB31 .
<!---->3. <!---->New Features in EJB 3.1 - Part 3, http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesEJB31-3 .
<!---->4. <!---->New Features in EJB 3.1 – Part 4, http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesinEJB3-Part4 .
<!---->5. <!---->JSR 316: Java EE 6, http://jcp.org/en/jsr/detail?id=316 .
<!---->6. <!---->JSR 318: Enterprise JavaBeans 3.1, http://jcp.org/en/jsr/detail?id=318 .
<!---->7. <!---->EJB 3.1 Public Draft, http://jcp.org/aboutJava/communityprocess/pr/jsr318/index.html .
<!---->8. <!---->OpenEJB, http://openejb.apache.org .
<!---->9. <!---->EasyBeans, http://www.easybeans.net .
<!---->10. <!---->Embedded JBoss, http://wiki.jboss.org/wiki/EmbeddedJBoss .
<!---->11. <!---->Embedded GlassFish, https://embedded-glassfish.dev.java.net .
<!---->12. <!---->Spring Pitchfork, http://www.springsource.com/pitchfork .
<!---->13. <!---->JBoss RESTeasy EJB integration, http://wiki.jboss.org/wiki/RESTeasyEJBIntegration .
注1: 思想实验: 又称假想实验或理想实验,它不同于具体的实验,它不是一种实践活动,而只是一种思想中塑造的理想过程,是逻辑推理的一种方法和形式