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:
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包中。同样,我们没有使用任何布署描述符:
- package com.pt.xyz;
- @Stateless(name="MyBeanB")
- public class BeanB implements BLocal, BRemote { (...) }
- package com.pt.xyz;
- @Local
- public interface BLocal { (...) }
- package com.pt.abc;
- @Remote
- 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:
- //编程式的例子,timer在方法里创建,而哪个方法都可以执行调度
- public String getHello(){
- TimerService ts = sessionContext.getTimerService();
- ts.createTimer(new Date(..), 10000, null);
- }
- //声明式的话,在应用程序启动的时候,就必须被创建完成,因而只有使用@Stateless
- 的方法才能执行调度。
- @Stateless
- public class TimerEJB {
- @Schedule(dayOfWeek="Mon")
- public void itIsMonday(Timer timer) { (...) }
- @Schedule(dayOfMonth="Last")
- public void itIsEndOfMonth(Timer timer) { (...) }
- }
现在无论是声明式还是编程式都可以持久化(默认选项)或非持久化。非持久化的timer在应用程序关闭或容器宕机时,并不会存活下来。可以使用annotation的持久化属性来实现持久化需求。对于编程式的话,可以将TimerConfig对象作为参数传递给TimerService接口的createTimer方法。Timer接口提供了新的isPersistent方法,判断是否允许持久化。
每个被持久化的timer相当于一个单独的timer,在应用程序分布式布署时,也不用考虑JVM的数量。这对集群的应用影响非常重大。现在我们假设一下这个场景,如何让一个应用程序更新一个现有的timer。在EJB3.1之前,如果是布署在单个JVM的应用程序中,很容易做到,直接替换即可。但在在多个JVM的环境下,如果只是其中某个JVM的timer被创建或更新了,对其它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 允许企业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下。
这种简化的打包方式必须是用在简单的应用程序布署环境中,如果你有更多的需求,还是切换回传统的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出来使用。
- @Singleton
- @Startup
- public class ByeEJB {
- private Logger log;
- @PostConstruct
- public void initByeEJB() {
- log.info("ByeEJB is being initialized...");
- (...)
- }
- public String sayBye() {
- log.info("ByeEJB is saying bye...");
- return "Bye!";
- }
- @PreDestroy
- public void destroyByeEJB() {
- log.info("ByeEJB is being destroyed...");
- (...)
- }
- }
- public class Client {
- private Logger log;
- public static void main(String args[]) {
- log.info("Starting client...");
- EJBContainer ec = EJBContainer.createEJBContainer();
- log.info("Container created...");
- Context ctx = ec.getContext();
- //Gets the no-interface view
- ByeEJB byeEjb = ctx.lookup("java:global/bye/ByeEJB");
- String msg = byeEjb.sayBye();
- log.info("Got the following message: " + msg);
- ec.close();
- log.info("Finishing client...");
- }
- }
输出结果:
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。