EJB 3.1 新特性介绍(二)

Global JNDI names(统一的全局JNDI命名)

该特性已经渴望很久了,终于在EJB3.1 中得以实现。原来EJB的全局JNDI命名方式都是供应商各自的实现版本,在布署的时候有很多问题。同一个应用程序中的那些session beans在不同供应商的容器中很可能JNDI命名就不同,造成客户端的调用代码必须得调整修改。除此之外,支持EJB3的某些供应商将允许将本地业务接口配置在全局的JDNI中,而另一些供应商却将此特性排队在外,这也导致了兼容性问题。

规范定义了全局JNDI命名方式,采用统一的方式来获取注册的session beans。也就是说,我们终于可以使用兼容性的JNDI命名了。

每个兼容的全局JNDI命名都有如下语法规则:

java:global[/<app-name>]/<module-name>/<bean-name>[!<fully-qualified-interface-name>]

下面的表格是对不同的元素的解释:

名称

描述

必选

app-name

应用程序的名称。如果没有在application.xml中指定,则默认的名称就是EAR的打包名称。

module-name

模块的名称。如果没有在ejb-jar.xml中指定,则默认的名称就是bundle文件名

bean-name

Bean的名称。如果没有使用标注@Stateless,@Stateful,@Singleton或其它布署描述符,则默认的名称就是该session bean的类的完全限定名称。

Fully-qualified-interface-name

暴露接口的限定名称。如果是一个no-interface view,则它的值为应该bean类的完全限定名称。

如果一个bean只想对客户端暴露一个对外接口,那么容器不公必须保证该bean在JNDI命名中是可用的,而且必须采用以下格式:

java:global[/<app-name>]/<module-name>/<bean-name>

为了简化JDNI的使用,容器分别也提供了java:app 和 java:module 两种命名方式:

java:app[/<module-name>]/<bean-name>[!<fully-qualified-interface-name>]

java:module/<bean-name>[!<fully-qualified-interface-name>]

示例1:

Java代码 EJB 3.1 新特性介绍(二)
  1. packagecom.pt.xyz;
  2. @Singleton
  3. publicclassBeanA{(...)}

Bean A 可以通过下面合法的JNDI命名来取得这个可用的no-interfaceview:

  • java:global/myapp/mybeans/BeanA
  • ava:global/myapp/mybeans/BeanA!com.pt.xyz.BeanA
  • java:app/mybeans/BeanA
  • java:app/mybeans/BeanA!com.pt.xyz.BeanA
  • java:module/BeanA
  • java:module/BeanA!com.pt.xyz.BeanA

示例2:

将下列代码打包到mybeans.jar中,但不放在任何ear包中。同样,我们没有使用任何布署描述符:

Java代码 EJB 3.1 新特性介绍(二)
  1. packagecom.pt.xyz;
  2. @Stateless(name="MyBeanB")
  3. publicclassBeanBimplementsBLocal,BRemote{(...)}
  4. packagecom.pt.xyz;
  5. @Local
  6. publicinterfaceBLocal{(...)}
  7. packagecom.pt.abc;
  8. @Remote
  9. publicinterfaceBRemote{(...)}

Blocal接口可以通过以下JNDI命名获得:

  • java:global/mybeans/MyBeanB!com.pt.xyz.BLocal
  • java:app/MyBeanB!com.pt.xyz.BLocal
  • java:module/MyBeanB!com.pt.xyz.Blocal

BRemote接口可以通过以下JNDI命名获得:

  • java:global/mybeans/MyBeanB!com.pt.abc.BRemote
  • java:app/MyBeanB!com.pt.abc.BRemote
  • java:module/MyBeanB!com.pt.abc.BRemote

Timer-Service(调度服务)

有相当一部分企业应用程序或多或少的有“时间驱动(time-driven)”的需求。长久以来,EJB规范却一直忽略了这一点,于是开发人员*去采纳非标准的解决方案——Quartz或Flux。早在EJB2.1时就引入了Timer Service,容器提供Timer服务,允许EJBs在特定情况下使用timer回调从而实现任务调度。除外之外,任务调度还可以在事务上下文中完成。

虽然大家都知道Timer服务对某些应用是非常重要的环节,但EJB的专家组们考虑的非常有限,比如:

  • 所有的timer 必须编程式的创建。
  • 在定制调度任务时,缺乏灵活性
  • 不支持多个JVM的应用场合,也就是说不支持集群等。

到EJB3.1版本时,有两种方式来创建timer:

  • 编程式:使用现有的TimerService接口,并且为了更加灵活的创建timer,对原有接口有了很大的改进和提高。
  • 声明式:使用annotation或布署描述符号来实现。采用这种方式的话,timer就以静态的形式定义在应用程序中,然后在应用程序启动时,自动创建。

@Schedule可用于自动创建一个timer,里面可以加入参数来限制调度时间。当一个方法被标注@Schedule后,到时间了就会自动被容器回调。如果采用编程式来创建timer,对一个bean来说,在哪个方法中调用timer都无所谓(原文没有给出编程式的例子,我在网上找了一个)。 如果是声明式的创建方式,只局限于被@Schedule标注过的方法才可以任务调度。在接下来的两个timer例子中,一个定义了每周一的午夜开始调度;另一个其是每个月的最后一天开始调度。注意看itIsMonday和itIsEndOfMonth上面的annotation:

Java代码 EJB 3.1 新特性介绍(二)
  1. //编程式的例子,timer在方法里创建,而哪个方法都可以执行调度
  2. publicStringgetHello(){
  3. TimerServicets=sessionContext.getTimerService();
  4. ts.createTimer(newDate(..),10000,null);
  5. }
  6. //声明式的话,在应用程序启动的时候,就必须被创建完成,因而只有使用@Stateless
  7. 的方法才能执行调度。
  8. @Stateless
  9. publicclassTimerEJB{
  10. @Schedule(dayOfWeek="Mon")
  11. publicvoiditIsMonday(Timertimer){(...)}
  12. @Schedule(dayOfMonth="Last")
  13. publicvoiditIsEndOfMonth(Timertimer){(...)}
  14. }

现在无论是声明式还是编程式都可以持久化(默认选项)或非持久化。非持久化的timer在应用程序关闭或容器宕机时,并不会存活下来。可以使用annotation的持久化属性来实现持久化需求。对于编程式的话,可以将TimerConfig对象作为参数传递给TimerService接口的createTimer方法。Timer接口提供了新的isPersistent方法,判断是否允许持久化。

每个被持久化的timer相当于一个单独的timer,在应用程序分布式布署时,也不用考虑JVM的数量。这对集群的应用影响非常重大。现在我们假设一下这个场景,如何让一个应用程序更新一个现有的timer。在EJB3.1之前,如果是布署在单个JVM的应用程序中,很容易做到,直接替换即可。但在在多个JVM的环境下,如果只是其中某个JVMtimer被创建或更新了,对其它JVM来说是不可见的,自然容易出很多奇怪的问题。这就意味着必须采用某种策略允许现有的所有timers对所有的JVM都是可见的。每次在出问题后,才知道是布署的应用程序不一致或其它低级错误造成的,这是一种非常差的实践方式。到EJB3.1时,开发人员再也不用关心布署时的跨JVM问题,这个工作留给容器去实现。

一个自动创建的非持久化timer在每次跨越JVM时,会在每个JVM中创建一个新的timer实例。

Timeout的回调方法有两个可选的重载函数:void <METHOD> (Timer timer) 和 void <METHOD> ()。

对于定时调度而言,有了很大的改进。表达式采用了模仿UNIX cron的日历语法格式。有8个主要属性可以按照下列的规则使用:

属性

属性值

示例

second

[0, 59]

second = "10"

minute

[0, 59]

minute = "30"

hour

[0, 23]

hour = "10"

dayOfMonth

- [1, 31] - day of the month

- Last - last day of the month

- -[1, 7] - number of days before end of month

- {"1st", "2nd", "3rd", "4th", "5th", ..., "Last"} {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}- identifies a single occurrence of a day of the month

dayOfMonth = "3"

dayOfMonth = "Last"

dayOfMonth = "-5"

dayOfMonth = "1st Tue"

month

- [1, 12] - month of the year

- {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}- month name

month = "7"

month = "Jan"

dayOfWeek

- [0, 7]- day of the week where both 0 and 7 refer to Sunday

- {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}- day's name

dayOfWeek = "5"

dayOfWeek = "Wed"

year

Four digit calendar year

year = "1978"

timezone

Id of the related timezone

timezone = "America/New_York"

每个属性值还有不同形式:

表达式类型

描述

示例

Single Value

限制属性只有一个值

dayOfWeek = "Wed"

Wild Card

对于给定的属性,允许任意合法值

month = "*"

List

限制属性允许两个或两个以上的值,中间用逗号隔开

DayOfMonth = "3,10,23"

dayOfWeek = "Wed,Sun"

Range

限制属性在一个封闭的区间段内

year = "1978-1984"

Increments

定义一个 x/y 的表达式。限制属性在每 y 秒调度一次,并且在 x 时开始。

second = "*/10" - every 10 seconds

hour = "12/2"- every second hour starting at noon

再来多看一些示例吧:

每周二上午7:30开始调度:

@Schedule(hour = "7", minute ="30", dayOfWeek ="Tue")

每周从周一到周五的,7点,15点,20点开始调度:

@Schedule(hour = "7, 15, 20",dayOfWeek = "Mon-Fri")

每周日的每个小时调度一次:

@Schedule(hour = "*", dayOfWeek ="0")

Last Friday of December, at 12

每年12月的最后一个周五12时调用一次:

@Schedule(hour = "12", dayOfMonth= "Last Fri", month="Dec")

2009年每个月的最后三天的20点开始调用:

@Schedule(hour = "20", dayOfMonth= "-3", year="2009")

从下午三点开始,每个小时的第5分钟开始调用:

@Schedule(minute = "*/5", hour ="15/1")

TimerService接口对编程式也进行了增强,可以使用类似于cron的表达式。这些表达式可以看作是ScheduleExpression类的实例,并在创建timer时,作为参数传递进去。

EJB Lite

EJB必须按照规范去实现一系列API。从EJB3.1开始,这组API分成了两——最小配置和完整配置。最小配置就是EJB3.1 Lite,它的作为EJB3.1的子集基本足够应用程序的开发,没有必须实现整套API规范。这将带来很多好处:

提高了性能。削减大量API,为容器减了不少肥,自然变得更加轻快了,提供的服务也更加出色。

学习曲线降低。成为一名EJB开发人员可不是一件很简单的事,要求开发人员学习大量的知识。如果只是开发EJBLite应用程序,开发人员就的学习曲线明显轻松很多,提高了生产率。

降低了开销。常常听到人们抱怨高昂的EJB容器价格,以及一大堆根本用不上的API。一个应用程序几乎没有用啥statelessbean,也根本用不着很多EJBAPI或特性,却还要承担这笔可观的费用。现在有了EJB完事版和EJB Lite版,供应商可以采用不同的许可费用,应用程序可以按需付费了。

EJB 3.1 Lite包括下列特性:

  • 支持Stateless,Stateful和Singleton Session Beans;只支持local views 和 no-interface views;只支持同步调用(不提供异步调用)。
  • 支持Container-Managed Transactions 和 Bean-Managed Transactions 两种事务方式。
  • 支持声明式和编程式的安全特性。
  • 支持Interceptors。

  • 支持布署描述符号

简化的EJB打包机制

ejb-jar文件为enterprise beans的打包模块。在EJB3.1前,所有Beans都必须打包在该包下。那时考虑到的是所有JavaEE应用程序都由web 前端和 EJB后端组成,自然ear的目的就是分别将这两个模块 war 和 ejb-jar打包成一个整体。将结构分为前端和后端感觉上是一个不错的最佳实践,但其实于于简单的应用程序来说反而难以忍受。

EJB 3.1 新特性介绍(二)

EJB 3.1 允许企业enterprise beans打包到war包中去。这些类可以放在WEB-INF/classes目录下,或者打成jar包扔到WEB-INF/lib中去。一个war包最多只能包含一个ejb-jar.xml,该文件可放在WEB-INF/ejb.jar.xml下,也可放在WEB-INF/lib某个ejb-jar中的 META-INF/ejb-jar.xml下。

EJB 3.1 新特性介绍(二)

这种简化的打包方式必须是用在简单的应用程序布署环境中,如果你有更多的需求,还是切换回传统的ear包吧。

Embeddable EJB Containers(嵌入式的EJB容器)

传统意义上,EJB总是同一大堆笨重的Java EE容器联系在一起,很难使用:

  • 难于单元测试。
  • 一个单独的批处理程序从EJB中捞不到闺半点好处,除非某个JavaEE容器真难提供批处理服务。
  • EJB的桌面管理控制台复杂难用。

EJB3.1最具意义的特性之一就是提供了embeddable container。现在就连JavaSE客户端就可以在自己的JVM和classloader,实例化EJB容器。嵌入式的容器提供了一系列基本服务,允许客户端在享受EJB和同时,还不需要那些完整版的JavaEE容器。

embeddable container会扫描classpath从而找到EJB模块。有两种方式来限定是否为EJB模块:

  • 直接使用ejb-jar文件
  • 有一个目录包含META-INF/ejb-jar.xml文件,或至少有一个类使用了enterprise bean标注。

同样一个bean,无论是跑在embeddable container还是标准的Java EE容器,都没有什么区别,也不要求你的代码做任何修改。这一点绝对保证是透明的。

embeddable container原则上是应该至少实现EJB 3.1 Lite子集的。但仍然允许供应商去扩展EJB3.1其它更完整的功能。

EJBContainer类在embeddable container中扮演了一个非常核心的角色。它的static方法createEJBContainer用于实例化一个新的容器;而当close方法调用时,先遍历所有bean的PreDestroy回调方法,最后关闭容器。最后一项的要点是,getContext方法用于返回一个context,然后客户端可以通过这个context将布署在embeddable container中的session bean都给lookup出来使用。

Java代码 EJB 3.1 新特性介绍(二)
  1. @Singleton
  2. @Startup
  3. publicclassByeEJB{
  4. privateLoggerlog;
  5. @PostConstruct
  6. publicvoidinitByeEJB(){
  7. log.info("ByeEJBisbeinginitialized...");
  8. (...)
  9. }
  10. publicStringsayBye(){
  11. log.info("ByeEJBissayingbye...");
  12. return"Bye!";
  13. }
  14. @PreDestroy
  15. publicvoiddestroyByeEJB(){
  16. log.info("ByeEJBisbeingdestroyed...");
  17. (...)
  18. }
  19. }
  20. publicclassClient{
  21. privateLoggerlog;
  22. publicstaticvoidmain(Stringargs[]){
  23. log.info("Startingclient...");
  24. EJBContainerec=EJBContainer.createEJBContainer();
  25. log.info("Containercreated...");
  26. Contextctx=ec.getContext();
  27. //Getstheno-interfaceview
  28. ByeEJBbyeEjb=ctx.lookup("java:global/bye/ByeEJB");
  29. Stringmsg=byeEjb.sayBye();
  30. log.info("Gotthefollowingmessage:"+msg);
  31. ec.close();
  32. log.info("Finishingclient...");
  33. }
  34. }

输出结果:

Log output

Starting client...

ByeEJB is being initialized...

Container created...

ByeEJB is saying bye...

Got the following message: Bye!

ByeEJB is being destroyed...

Finishing client...

接下来的热点

除了上述的新的特性外,还有一些细微的改进。比如说简化现有的功能。下面列出的就是相关话题:

  • stateful 可以使用@ AfterBegin,@ BeforeCompletion,@ AfterCompletion标注来代替SessionSynchronization接口的实现。
  • 可以对stateful bean设定一个timeout,来指示应该stateful bean从容器删除之前的存活时间。@ StatefulTimeout就可实现这样的效果。
  • 对于stateless 和 stateful beans容器都有自己的并发调用机制。缺省情况下,允许并发访问stateful beans,具谁先谁后由容器自己决定。开发人员现在可以使用@ConcurrencyManagement(CONCURRENCY_NOT_ALLOWED)标注来指定stateful bean不支持并发访问。这样的话,同一个stateful bean每次只能处理一个客户端的请示。如果这个时候还有请求需要访问这个bean的时候,会就抛出ConcurrentAccessException异常。
  • @ AroundTimeout标注用于定义拦截方法(interceptor method)的超时时间。

结论

Java EE即将发布最终版,上面提到的绝大多数特性非常接近最终版本。2009必将是JavaEE火爆的一年。

EJB 3.1提供了更出色的架构,同时还为开发人员提供了更丰富的功能集,允许你去扩展现有设计和实现。这次发布的版本非常成熟完整,会使用Java服务端开发更加牢固。

由于技术总是在不断的自我完善,总难免会遗漏些更新的特性。计划是在下个版本推出下列特性:

  • 支持对某个bean的实例个数的控制,即可以指定它的最少个数和最大个数。
  • 提供应用程序的生命周期类,用于处理pre-start, post-start, pre-stop 和 post-stop四种应用程序状态。
  • 增强JMS,增加一些流行的消息系统特性——消息群组和消息订单等。
  • 支持 JAX-RS。