在过去一年左右的时间里,我们一直在深思我们希望在下一个EE平台版本中看到的新功能,并将我们的想法反馈给Sun,以纳入下一轮EE规范的JSR提案中。这些JSR应该很快就会公开,但我想要概述一下我认为重要的东西,以及为什么我认为它们很重要。这些条目中的许多都来自我们在Seam中的经验,其他的则是平台长期缺乏的东西。我的愿望清单相当长,所以我将分散在几篇文章中。首先,我将谈谈会话Bean。
基本的EJB组件模型 - 有状态的/无状态的会话Bean和消息驱动Bean - 几乎是完美的。它经受了时间的考验,作为一个覆盖了绝大多数用例的模型。然而,有时候这个模型过于限制,直到我们能处理这些情况,模型才真正完整。
并发会话Bean
首先,应该能够编写一个支持多个客户端并发访问的会话Bean。当然,这不应该成为默认的并发模型 - 我们尽可能希望引导人们实现应用程序,以便在并发线程和并发客户端之间不共享资源 - 但有时并发是不可避免的。
我的建议是允许无状态的或有状态的会话Bean有三种并发模式。
- 无并发,默认,并当前支持的行为:该Bean不支持并发客户端。如果两个请求同时到达,容器允许抛出并发访问异常。
- Bean管理并发:该Bean支持多个线程的并发访问,并负责管理对易变数据结构的访问。
- 容器管理并发:该Bean支持并发客户端,容器负责在进入Bean实现之前确保并发线程的序列化。
我们可以使用像@ConcurrencyManagement(CONTAINER)或@ConcurrencyManagement(BEAN)这样的注解来选择并发模型。例如
@Stateful @ConcurrencyManagement(CONTAINER) public class Counter { private int count; public void inc() { count== ; } @ReadOnly public int value() { return count; } }
在这个例子中,@ReadOnly 注解表示容器应使用读写锁定模型。
或者,你也可以选择“困难模式”
@Stateful @ConcurrencyManagement(BEAN) public class Counter { private int count; public synchronized void inc() { count== ; } public synchronized int value() { return count; } }
我们需要并发 Bean 的一大主要原因是为了支持在单个节点上所有线程之间共享状态的单例 Bean 的概念。单例 Bean 对于缓存或持有应用程序配置数据非常有用。(想想类似于 servlet 上下文属性,但适用于 EJB 容器。)实际上,引入单例 Bean 可以一箭双雕:单例 Bean 的 @PostConstruct 方法可以用作容器初始化钩子,就像今天在 Web 容器中使用启动 servlet 一样。同样,@PreDestroy 方法可以用来通知应用程序容器正在关闭。
轻量级异步性
我愿望清单上的第二项是轻量级异步性。目前,EJB 中有几种异步处理方法。第一种是 JMS。对于具有明确服务质量要求的情况,例如保证投递等,JMS 是完全合理的。然而,对于许多情况来说,它过于冗余。第二种方法是 EJB 计时服务。我实际上是计时服务的大粉丝,尽管我对大多数应用服务器中存在的可疑实现并不感冒。但如果计时服务要变得普遍有用,它将需要一些重大的增强。
首先,你应该可以有一个 Bean 有多个 @Timeout 方法。(哦!)
接下来,我们需要增强调度能力,我有一些想法,从支持业务日历来支持 cron 风格的模式,但目前还没有具体的方案。
最后,Seam 展示了如何在计时服务之上叠加异步方法调用协议。这是对 EJB3 的一个相对简单的补充(展示了 EJB3 模型的可扩展性),但我认为我们应该在规范中定义 @Asynchronous 方法。
有状态 Web 服务端点
我愿望清单上的第三项是有状态的 Web 服务端点。目前,只有无状态会话 Bean 可以作为 Web 服务端点。通过一些与 WS-Contexts 或 WS-Addressing(或任何合适的 WS-blahblah)的集成,我们将能够支持作为 Web 服务端点的作用的有状态会话 Bean。我还不知道这会是什么样子,但我们正在 Seam/WS 中试验一些可能相关的技术。
复制回调
第四项是 @PreReplicate 和 @PostReplicate 回调方法。目前,一些容器在复制时会重载 @PrePassivate 和 @PostActivate 回调,而另一些则不会。钝化与复制具有明显不同的语义,所以这并不合适。但确实需要某种回调。
可选业务接口
我愿望清单上的第五项是一个纯粹的使用便捷性相关的问题。目前,EJB 强制要求所有会话 Bean 都有某些 @Local 或 @Remote 接口。当会话 Bean 被理解存在于业务层,并且有一个定义良好的 API 位于业务逻辑和客户端之间时,这不是一个不合理的要求。但现在我们在所有地方使用会话 Bean - 甚至用于表示逻辑 - 显然,定义每个 Bean 的本地接口只是个麻烦。不幸的是,我们在编写规范的过程中意识到这一点太晚了,以至于无法在 EJB 3.0 中解决这个问题(我为此多次自责,我本应该知道得更好)。特别是在像 Seam 这样的环境中,Bean 的唯一客户端可能只是一个带有 EL 表达式的 JSF 页面,接口看起来完全多余!
接口应该是可选的,当它缺失时,Bean 类的公共方法应被视为会话 Bean 的业务方法。
简化的 JMS/JavaMail 发送
第六项,如果时间允许,我们本应该在3.0版本中完成,那就是简化JMS消息和JavaMail发送。目前,可以通过@Resource注入Topic或Queue,但当然我真正感兴趣的是QueueSender或TopicPublisher。这很容易解决——事实上,我们已经在Seam中解决了这个问题。
日志记录
另一个(较小的)易用性问题就是日志记录。我无法表达在Java 5中这有多糟糕。
static Log log = LogFactory.getLog(MySelf.class);
当然,它应该是这样的
@Logger Log log;
(就像在Seam中那样。)
EJB元注解
我最想要的愿望是支持将EJB注解用作元注解。通常,你会发现几个具有相同注解模式的EJB Bean。例如,你可能有一些带有以下注解的Seam组件
@Stateful @TransactionAttribute(MANDATORY) @Scope(CONVERSATION) @RolesAllowed(USER) @ConcurrencyManagement(CONTAINER) @Name("createOrder") public class CreateOrderBean implements CreateOrder { .... }
通过引入@AjaxConversation注解,你可以减少代码重复并提高代码的语义级别
@Stateful @TransactionAttribute(MANDATORY) @Scope(CONVERSATION) @RolesAllowed(USER) @ConcurrencyManagement(CONTAINER) public @interface AjaxConversation { .... }
而Bean类将看起来像这样
@AjaxConversation @Name("createOrder") public class CreateOrderBean implements CreateOrder { .... }
在EJB和Web Beans团队之间的协调下,我希望这能成为现实。
在下一部分,我将讨论我们可以在平台级别(打包、安全性、依赖注入等)进行改进的地方。