EJB 3.1 新特性介绍(一)

引言

 

 

Enterprise Java Beans(简称EJB)是Java Enterprise Edition(简称Java EE)平台上的服务端组件架构模型,目标极力于快速并简化分布式,事务处理,安全以及便携式的应用程序。

 

EJB在其2.*时代也叱诧风云过,由于能够解决许多企业应用程序的需求而被广泛采纳。但这只是EJB成功的表象,越来越多的质疑声开始抨击EJB的复杂。“缺乏好的持久层策略,又臭又长的布署描述符,能力有限的单元测试”等等这些常用却又不好用的技术导致了大量开发人员开始寻找新的“轮子”。

 

Sun的反应的确有些迟钝,但还是它花费大量精力来修订规范,使得EJB得到很大的改观。EJB3摈弃了许多现有的缺点,呈现给开发人员的解决方案在社区中大受好评。EJB又一次变成了切实可行的解决方案,并且现在已经有许多放弃它的团队,再次接收EJB。

 

虽然它成功了,但EJB3还没有当初预计的那么理想。反观EJB2.1,新规范要面对两个主要的挑战:

 

1.  为了改变EJB2.1现有的特性,(比如说需要强大的持久层框架来代替Entity Beans;支持使用Annotation来代替布署描述符;抛弃home interface等等。),需要进行大量的重建工作。

2.  为了引入新的解决方案,需要加入原先规范中没有的新技术。(比如说支持Singletons;支持方法拦截;支持异步调用;改进并增强现有的Timer Service特性

 

对于EJB,优先考虑的就是全部重建。“我们只有先清空杯子里的水,才能接纳新的东西”。现在杯子已经清空了,这对我们来说是非常有利的,而且还可以没有包袱的大胆前进。

 

EJB3.1 又一次引入了一系列新的特性,倾向于挖掘技术的潜力。依我来看,EJB3.1绝对是一个重要的发布版本,它将那些长期让人渴望的特性带到开发者面前,更加能够满足最新的企业应用程序开发,同时对EJB再次被人们采纳将做出巨大的贡献。

 

近期,EJB 3.1提议最终草案已经发布了,现在我们已经非常接近最终发行版了。本文会贯穿大多数新的特性,对每一个新特性都有会有一定程度的介绍。

 

 

 

No-Interface View(非接口视图)

 

EJB3.1引入了no-interface view的概念——将一个bean的所有的public method通过一个Local View暴露出来。(具体访问可见参见本文后面的Global JNDI names)Session Beans并不强迫你再去实现任何接口。EJB容器提供一个指向no-interface view的引用实现,允许客户端调用该bean的任何public method,并且no-interface view也可以确保事务,安全以及拦截的行为与原先的用法一致。

 

通过non-interface view,所有bean的public method(当然也包括定义在其父类上的public method)都是可用的。一个客户端可以通过依赖注入或JNDI lookup得到该view的引用,用起来感觉就好像它是local或remote的view一样。

 

但与local和remote的view不同的是,localremoteview必须与其所实现的的业务接口同时存在,而no-interface view的引用则只是bean这个类本身。注意,no-interface view不再依赖于接口

 

下面的代码样例说明了一个servlet使用no-interface view是一件多么容易事啊。这个被引用的no-interface view叫作ByeEJB,其实就是一个普普通通的JavaBean。该EJB并未实现任何接口,也没有提供什么多余的布署描述符。最后但并不是最不重要的一点就是,这里EJB引用的获得使用了依赖注入进行简化。

 

 

Java代码   收藏代码
  1. ByeServlet  
  2.   
  3. (...)  
  4. @EJB  
  5. private ByeEJB byeEJB;  
  6.   
  7. public String sayBye() {  
  8.     StringBuilder sb = new StringBuilder();  
  9.     sb.append("<html><head>");  
  10.     sb.append("<title>ByeBye</title>");    
  11.     sb.append("</head><body>");  
  12.     sb.append("<h1>" + byeEJB.sayBye() + "</h1>");  
  13.     sb.append("</body></html>");  
  14.     return sb.toString();                         
  15. }   
  16. (...)  
  17.   
  18. ByeEJB  
  19. @Stateless  
  20. public class ByeEJB {  
  21.   
  22.     public String sayBye() {  
  23.         return "Bye!";  
  24.     }     
  25. }  

 

 

 

实际上如果引用类型为java类而不是接口的话,还是有些硬性的限制条件:

 

l         客户端永远无法使用new操作符来获得引用。(很明显如果是你自己new出来的,EJB容器自然无法托管)

 

l         除了public method外,如果其它方法如果出错,也会抛出EJBException异常。

 

l         一个指向该view的引用可以作为任何本地接口或其它no-interface view方法的参数进行传递或返回。

 

 

如果bean没有暴露任何local或remote view,则容器必须默认提供一个可用的no-interface view。如果bean提供了至少一个local或remote view,则窗口不会提供no-interface view(除非使用@LocalBean显式要求提供).

 

 

Bean和其父类的所有public method都会通过no-interface view被暴露出来。这也意味着任何public callback method也会暴露出来,因此在使用的时候要注意这一点。

 

本特性避免了接口的编写,简化了程序的开发(实际上并不是所有的类都需要接口)。也许在不久的将来还会加入remote no-interface view。

 

 

 

Singleton

 

 

大多数的应用程序都有过至少需要一个singleton bean(对每个应用程序来说,它意味着只需要初始化一次)的经历。许多供应商已经满足了这方面的需求,通过使用描述布署符来限定一个bean所允许的最大实例数量。供应商这种“各自为政”的方式破坏了JAVA到处宣扬的“一次编写,到处布署”的口号,因此迫切的再推出一套类似特性的规范来很有必要。EJB 3.1最终还是引入了singleton session beans。

 

现在主流的session beans有三类——stateless,stateful 和 singleton。Singleton session beans可通过使用Singleton Annotation来标注,然后每个应用程序会确保只实例化一次。Singleton session beans支持与客户端共享,当然也支持并发访问(后面有会具体谈到)

 

singleton bean的生命周期始于容器下列任意初始化阶段:

 

1.  直接实例化某个singleton

2.  通过依赖注入实例化某个singleton,这样其依赖的那些singleton也会被跟着实例化,如此递推下去。

3.  通过执行PostConstruct回调

 

缺省情况下,容器有义务决定singleton bean何时被创建(比如在spring中,默认是将所有的singleton在启动时就初始化),但是也允许开发人员使用Startup annotation在应用程序启动时,要求容器去对singleton进行初始化。此外,Startup annotation还允许你去定义singleton beans之间的依赖关系。当容器开始处理任何客户端发过来的请示时,所有标注有startup的singletons都必须初始化完成。

 

下面的代码样例大致演示了依赖是如何实现的。singleton A的没有使用@Startup也没有别的singleton依赖于它,于是A的实例化由容器来决定(说白了,此时A的实例化由具体EJB实现来决定,规范没有硬性规定)。singleton B在应用程序启动过程中被实例化,但必须早于singleton D和singleton E(很明显,没有B的实例,CD可能都无法正常初始化)。即使这个时候的B没有使用@Startup,但由于有其它的singletons依赖于它,它还是要先实例化的。singleton C由于使用了@Startup,它会在应用程序启动过程中被实例化,但必须比singleton E先完成。同理D也必须在E之前完成实例化。因此,E是应用程序中会最后一个被初始化。(注意,再重申一下A是否会初始化与供应商的实现有关,也许供应商的实现是预先加载,也许是延迟加载,但当你真正使用A的时候,肯定会保证被初始化了)

 

 

Java代码   收藏代码
  1. @Singleton  
  2. public class A { (...) }  
  3.   
  4. @Singleton  
  5. public class B { (...) }  
  6.   
  7. @Startup  
  8. public class C { (...) }  
  9.   
  10. @Startup(DependsOn="B")  
  11. @Singleton  
  12. public class D { (...) }  
  13.   
  14. @Startup(DependsOn=({"C", "D"})  
  15. @Singleton  
  16. public class E { (...) }  

 

 

 

有一点需要注意的是,如果一个bean依赖于多个bean的注入,那么这些被依赖的beans之间的初始化时机是不确定的。E依赖于C和D,并没有说C一定要在D之前被实例化,除非D本身也是依赖于C的。

 

singleton可以@Startup定义依赖于现存其它模块中的singletons。

 

当应用程序关闭时,容器有义务执行singletons的PreDestory回调,将所有的singletons全部销毁。这个时候,启动时候的依赖关系在销毁时变得有意义了,比如说A依赖于B,当A被销毁时,B还是存活的,刚好与初始化相反。

 

Singleton bean会维护服务端与客户端调用而产生的状态,但当应用程序关闭或容器挂掉时,该状态并不会保存下来。(大家一定想到这种情况如果使用序列化机制将状态保存下来,然后当程序再次启动时,再反序列化还原状态也是一种选择。不过,至于你singleton中装的是什么内容,是否需要被序列化,是否可以被序列化对容器来说还是未知数,所以干脆挂就挂吧。)为了处理服务端与客户端的并发调用问题,开发人员必须定义一个并发策略。规范中定义了两种方式:

 

l         CMC(Container-managed concurrency 容器托管的并发机制):顾名思义,由容器来管理该bean实例的并发调用。这也是EJB的默认策略。

 

l         BMC(Bean-managed concurrency  Bean托管的并发机制):容器此时并不会干涉该bean实例的并发,把并发的同步调用推回给开发人员。BMC允许使用合法的同步原语(如synchronized 和volatile关键字),来协调不同客户端不同线程对同一个singleton的并发访问。

 

(呵呵,这不正是声明式与编程式的又一次实践吗?)

 

大多数情况下,CMC肯定是首选。容器的管理并发问题时,还是使用“Lock (锁)”。每个方法都关联上一个read lock和write lock。Read lock表示应该方法可以尽可能的被多个线程并发调用,而write lock表示该方法在每次只能每一个线程访问。

 

缺省情况下,lock的属性值是write。当然你可以通过使用@Lock来修改默认属性值。@Lock可以用于类,接口和方法。如其它Annotation类似,@Lock也有继承性。在类级别使用@Lock,它的所有相关方法也会被应用上,除非你单独限制某一个具体的方法。

 

当某个方法持有 write lock时,容器只允许其中一个并发线程去调用该方法。其它线程并必须等待,直到该方法再次变得可用。客户端的等待也许是无限期的,这个时候可以使用@ AccessTimeout来指定一个最大等待时间。如果超时了,会抛出ConcurrentAccessTimeoutException异常。

 

下面的代码示例中演示了如果使用CMC。Singleton A明确指定为CMC(尽管这么没有必要,因为默认就是CMC,主要还是为了演示)。Singleton B并未定义任何并发策略,但按照规范,它还是属性CMC范畴,它的所有方法显示指定CMC使用 write lock方式。Singleton C与Singleton B几乎一模一样,只是使用的是read lock方式。Singleton D和Singleton C一样,但是D中的sayBye方法重新定义为 write lock。Singleton E总要是演示@AccessTimeout的使用,当有因等待E中某方法而被阻塞的客户端超过10秒时,就会招出ConcurrentAccessTimeoutException异常。

 

 

 

Java代码   收藏代码
  1. @Singleton  
  2. @ConcurrencyManagement(CONTAINER)   
  3. public class A { (...) }  
  4.   
  5. @Singleton  
  6. @Lock(WRITE)  
  7. public class B { (...) }  
  8.   
  9. @Singleton  
  10. @Lock(READ)  
  11. public class C { (...) }  
  12.   
  13. @Singleton  
  14. @Lock(READ)  
  15. public class D {   
  16. (...)  
  17. @Lock(WRITE)  
  18. public String sayBye() { (...) }  
  19. (...)  
  20.  }  
  21.   
  22. @Singleton  
  23. @AccessTimeout(10000)  
  24. public class E { (...) }  

 

 

如果是在集群环境下,当应用程序布署在不同的JVM上,则每个JVM都有该singleton的一个实例。

 

直到EJB 3,任何由EJB抛出系统异常都会导致实例被废弃和销毁。但这个原则并不适用于singleton beans——它们必须一直存活下来,至少应用程关闭时才销毁。因此任何在业务对像方法或回调抛出系统异常时,业务对象并不会被销毁。

 

与 stateless beans一样,singletons也可以暴露成web services。

 

 

 

Asynchronous Invocations(异步调用)

 

 

Session beans方法的异步调用是这些新特性中最重要的特性之一。它可以应用于所有类型的session beans。规范规定:在容器开始执行某个bean实例的调用之前,异步调用的控制权一定要返回给客户端。这又将session beans提高到了一个崭新的高度——使有潜在异步调用需求开发人员从session beans中获得更多好处,也允许客户端触发并行处理的流程。

 

通过使用@Asynchronous就可以将一个类或方法标记为异步调用。下面的示例演示了该annotation不同场合下的应用。Bean A将其所有方法标注为异步;SingletonB中,只有定义的flushBye方法才是异步的;对stateless C而言,所有的通过local interface Clocal接口调用的方法都是异步的;而通过Cremote接口调用却是同步的。因此,同一个方法还可能由于所引用的不同接口而表现出不同的行为。最后,bean D的flushBye肯定是异步的,于Dlocal是否是不是异步已经无关了。

 

 

Java代码   收藏代码
  1. @Stateless  
  2. @Asynchronous  
  3. public class A { (...) }  
  4.   
  5. @Singleton  
  6. public class B {   
  7. (...)   
  8.     @Asynchronous  
  9.     public void flushBye() { (...) }  
  10. (...)  
  11. }  
  12.   
  13. @Stateless  
  14. public class C implements CLocal, CRemote {   
  15.   
  16.     public void flushBye() { (...) }  
  17. }  
  18. @Local  
  19. @Asynchronous  
  20. public interface CLocal {   
  21.     public void flushBye();  
  22. }   
  23. @Remote  
  24. public interface CRemote {   
  25.     public void flushBye();  
  26. }   
  27.   
  28. @Stateless  
  29. public class D implements DLocal { (...) }  
  30.   
  31. @Local  
  32. public interface DLocal {  
  33. (...)   
  34.     @Asynchronous  
  35.     public void flushBye();  
  36. (...)  
  37. }  
 

 

 

注意异步方法调用的返回类型必须是void或Future<V>(其中V表示返回值的类型)。如果方法的返回值为void,则不允许声明任何应用程序异常。

 

Future接口在Java 5 中就被引入,提供四个主要方法:

 

  • cancel(booleanmayInterruptIfRunning):尝试取消异步方法的执行。如果某个bean的实例方法还未开始调用,容器会尝试取消这个调用。如果为参数为true:执行中的任务可以被interrupt;如果参数为false:允许这个任务执行完毕。标志位“mayInterruptIfRunning”用于控制目标bean是否对客户端是否可见,免得该异步调用被客户端不小心给取消了。
  • get:当方法调用完成时,返回结果。该get方法有两个重载版本,一个是调用后一直处于阻塞状态,直到方法调用完成;另一个则可以设置一个超时的参数。
  • isCancelled:指示该方法是否被取消。
  •  isDone:指示该方法是否执行完成。

 

 

 

规范要求容器提供AsyncResult<V>类作为Future<V>接口的实现,它可以将执行后的返回结构作为构造函数的参数,请看下面代码:

 

Java代码   收藏代码
  1. @Asynchronous  
  2. public Future<String> sayBye() {  
  3.     String bye = executeLongQuery();  
  4.     return new AsyncResult<String>(bye);  
  5. }  

 

 

 

Future<V>的返回类型因不同的客户端角度而异。因此,如果在一个标有@Asynchronous

接口中定义了方法m,那么请注意,只有这个接口中的方法允许返回类型为Future<V>,在其它别的非异步接口中的定义中不能带有Future<V>,只能返回普通的V(注意V在这里表示的是泛型)。请看下面示例:

 

 

Java代码   收藏代码
  1. @Stateless  
  2. public class ByeEJB implements ByeLocal, ByeRemote {   
  3.   
  4.     public String sayBye() { (...) }  
  5.   
  6. }  
  7.   
  8. @Local  
  9. @Asynchronous  
  10. public interface sayBye {   
  11.     public Future<String> flushBye();  
  12. }   
  13.   
  14. @Remote  
  15. public interface ByeRemote {   
  16.     public String sayBye();  
  17. }   

 

 

 

SessionContext接口有一个wasCancelCalled方法,用于判断客户端是否调用了Future和cancel方法。如果Future的cancel方法的mayInterruptIfRunning参数设置为true,那么wasCancelCalled自然也会返回true,也就是说异步调用被终止了。(也就是说SessionContext可以用于判断某个异步方法是否被取消,提高程序的健壮性)。请看代码示例:

 

Java代码   收藏代码
  1. @Resource  
  2. SessionContext ctx;  
  3.   
  4. @Asynchronous  
  5. public Future<String> sayBye() {  
  6.     String bye = executeFirstLongQuery();  
  7.     if (!ctx.wasCancelCalled()){  
  8.         bye += executeSecondLongQuery();  
  9.     }  
  10.     return new AsyncResult<String>(bye);  
  11. }  

 

 

(下面代码似乎有笔误,根本无法通过编译)

如果异步方法抛出了一个普通应用程序异常,则这个异常传播到客户端时必须为ExecutionException。原始的异常信息仍然可以通过调用getCause来获得的。

 

Java代码   收藏代码
  1. @Stateless  
  2. public class ByeEJB implements ByeLocal {   
  3.   
  4.     public String sayBye() throws MyException {   
  5.         throw new MyException();  
  6.     }  
  7.   
  8. }  
  9.   
  10. @Local  
  11. @Asynchronous  
  12. public interface ByeLocal {   
  13.     public Future<String> sayBye();  
  14. }   
  15.   
  16. //Client  
  17. @Stateless  
  18. public class ClientEJB {   
  19.   
  20.     @EJB  
  21.     ByeLocal byeEjb;  
  22.   
  23.     public void invokeSayBye() {   
  24.         try {  
  25.             Future<String> futStr = byeEjb.sayBye();  
  26.         } catch(ExecutionException ee) {  
  27.             String originalMsg = ee.getCause().getMessage();  
  28.             System.out.println("Original error message:" + originalMsg);  
  29.         }  
  30.     }  
  31.   
  32. }  

 

 

 

对于异步方法的执行,客户端的事务上下文并不会传播到。因此,当下列异步事务方法调用时,可以得到的结论分别为:

 

  • 如果方法m的事务属性定义为“REQUIRED”,那么它的表现形式将永远为“REQUIRES_NEW”。
  • 如果方法m的事务属性定义为“MANDATORY”,那么它的表现形式永远是抛出TransactionRequiredException异常。
  • 如果方法m的事务属性定义为“SUPPORTS”,那么它的表现形式永远是不会参与在事务上下文中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值