欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【翻译】深入浅出 EJB3.1(下)

程序员文章站 2022-03-14 07:59:12
...

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

 

该特性已经渴望很久了,终于在 EJB 3.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:

 

package com.pt.xyz;

@Singleton

public class BeanA { (...) }

 

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

 

  • 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 开发人员可不是一件很简单的事,要求开发人员学习大量的知识。如果只是开发 EJB Lite 应用程序,开发人员就的学习曲线明显轻松很多,提高了生产率。

 

降低了开销。常常听到人们抱怨高昂的 EJB 容器价格,以及一大堆根本用不上的 API 。一个应用程序几乎没有用啥 stateless bean ,也根本用不着很多 EJB API 或特性,却还要承担这笔可观的费用。现在有了 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 打包成一个整体。将结构分为前端和后端感觉上是一个不错的最佳实践,但其实于于简单的应用程序来说反而难以忍受。

 

【翻译】深入浅出 EJB3.1(下)
            
    
    博客分类: 专家文章翻译 EJBBean编程企业应用JVM 

 

 

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 下。

 

【翻译】深入浅出 EJB3.1(下)
            
    
    博客分类: 专家文章翻译 EJBBean编程企业应用JVM 

 

 

这种简化的打包方式必须是用在简单的应用程序布署环境中,如果你有更多的需求,还是切换回传统的 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

 

 

我们应当庆幸专家为此而付出的极大努力,他们完成了一项高质量的工程。

 

 

 

关于作者

   

Paulo Moreira 是一个来自葡萄牙的职业软件工程师,目前供职于卢森堡的 Clearstream 银行。 Paulo Moreira 毕业于 葡萄牙米尼奥大学 ,拥有计算机科学和系统工程的硕士学位。他从 2001 年起,就一直从事于 java 服务端开发,涉及的领域主要有:通信,零售,软件和金融。