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代码   收藏代码
  1. package com.pt.xyz;  
  2.   
  3. @Singleton  
  4.   
  5. public class BeanA { (...) }  

 

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代码   收藏代码
  1. package com.pt.xyz;  
  2. @Stateless(name="MyBeanB")  
  3. public class BeanB implements BLocal, BRemote { (...) }  
  4.   
  5. package com.pt.xyz;  
  6. @Local  
  7. public interface BLocal { (...) }  
  8.   
  9.   
  10. package com.pt.abc;  
  11. @Remote  
  12. public interface BRemote { (...) }  

 

 

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代码   收藏代码
  1. //编程式的例子,timer在方法里创建,而哪个方法都可以执行调度  
  2. public String getHello(){  
  3. TimerService ts = sessionContext.getTimerService();    
  4. ts.createTimer(new Date(..), 10000, null);   
  5. }  
  6.   
  7. //声明式的话,在应用程序启动的时候,就必须被创建完成,因而只有使用@Stateless  
  8. 的方法才能执行调度。  
  9. @Stateless  
  10. public class TimerEJB {   
  11.     @Schedule(dayOfWeek="Mon")  
  12.     public void itIsMonday(Timer timer) { (...) }  
  13.   
  14.     @Schedule(dayOfMonth="Last")  
  15.     public void itIsEndOfMonth(Timer timer) { (...) }  
  16. }  

 

 

现在无论是声明式还是编程式都可以持久化(默认选项)或非持久化。非持久化的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 新特性介绍(二)_第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 新特性介绍(二)_第2张图片

 

 

这种简化的打包方式必须是用在简单的应用程序布署环境中,如果你有更多的需求,还是切换回传统的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代码   收藏代码
  1. @Singleton  
  2. @Startup  
  3. public class ByeEJB {   
  4.       
  5.     private Logger log;  
  6.       
  7.     @PostConstruct  
  8.     public void initByeEJB() {   
  9.         log.info("ByeEJB is being initialized...");   
  10.         (...)  
  11.     }  
  12.   
  13.     public String sayBye() {   
  14.         log.info("ByeEJB is saying bye...");   
  15.         return "Bye!";  
  16.     }  
  17.    
  18.     @PreDestroy  
  19.     public void destroyByeEJB() {   
  20.         log.info("ByeEJB is being destroyed...");   
  21.         (...)  
  22.     }  
  23. }  
  24.   
  25. public class Client {   
  26.   
  27.     private Logger log;  
  28.   
  29.     public static void main(String args[]) {   
  30.         log.info("Starting client...");  
  31.         EJBContainer ec = EJBContainer.createEJBContainer();  
  32.         log.info("Container created...");  
  33.         Context ctx = ec.getContext();  
  34.         //Gets the no-interface view  
  35.         ByeEJB byeEjb = ctx.lookup("java:global/bye/ByeEJB");  
  36.         String msg = byeEjb.sayBye();  
  37.         log.info("Got the following message: " + msg);  
  38.         ec.close();  
  39.         log.info("Finishing client...");  
  40.     }  
  41.   
  42. }  
 

 

输出结果:

 

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。

你可能感兴趣的:(ejb,3.1)