前言
Raza 同学终于又出 EJB3.1 文章了,真是姗姗来迟,我也是刚翻译出来,希望和大家分享 EJB3.1 的新特性。今天主要讲的是 WebBeans ,相信很多关于 EJB3.1 的人,一定会对它感兴趣的,今天我们谈到 WebBeans 两个方面:依赖注入和增强的拦截器模型。相信今天的文章会让你有所收获。
原文:http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesinEJB3-Part4
正文
WebBeans 和 EJB :如漆似胶
WebBeans 是当前 JavaEE6 日程安排中最激动人心的 JSRs 规范之一。支持集成 WebBeans 的主张非常有号召力。如果你用过 JSF 和 EJB3.0 来开发应用程序的话, JSF 的那层薄薄 backing bean 一定让你印象深刻。实际上,即使你可以用 @EJB annotation 很容易的将 EJB 注入到 backing bean 的话,那么 backing beans 还是在充当 glue-code 的角色。而 WebBeans 却允许你直接使用 EJB 作为 JSF 的 backing beans ,从而去掉了多余的 glue-code 。在第二小节的时候,我们共同来验证这样的美事吧。
除了更有效的整合 JSF 和 EJB 编程模型外, WebBeans 当然也少不了提供一系列很酷的特性,包括:
<!---->l <!---->更健壮的基于 annotation 的依赖注入
<!---->l <!---->越发简洁易用的 JavaEE 拦截器模型
<!---->l <!---->更加合理的对 Web 应用程序的组件上下文进行管理
这里的最后一点我不想多说,因为这里更侧重的是 JSF 而并非 EJB ,但剩下的精彩主题是会一一向大家介绍的。
WebBeans 很大程度上受 JBoss Seam 和 Google Guice 的启发。现在该 JSR 规范由 Gaving King 和 Bob Lee 所领导 ( 注:两位绝对是 Java 界响当当的牛人 ) 。我个人觉得,虽然东西到了 JSR 手里肯定会有自己的实现版本,但 Seam 的核心代码仍然是 WebBeans 的基石。
让 EJB 成为 JSF 的 Backing Beans
好吧,现在我们从 EJB3 in Action 拿一个例子进行重构,从总体上看看 WebBeans 究竟是什么。在 EJB3 in Action 中,最经典的 Session bean 例子自然还是那个“竞标”例子了 (adding a bid) 。 Session bean 使用了 JPA 将一个 Bid entity( 实体 ) 持久化到数据库。现在我们来看看在 WebBeans 的环境下, session bean 和 entity 变成什么样子了:
@Component @Stateless @Named("placeBid") public class PlaceBidBean { @PersistenceContext private EntityManager entityManager; @In private Bid bid; public void addBid() { entityManager.persist(bid); } } @Component @Entity @Named("bid") public class Bid { private Long bidId; private String bidder; private String item; private Double bidPrice; @Id @GeneratedValue public Long getBidId() { return bidId; } public void setBidId(Long bidId) { this.bidId = bidId; } public String getBidder() { return bidder; } public void setBidder(String bidder) { this.bidder = bidder; } public String getItem() { return item; } public void setItem(String item) { this.item = item; } public Double getBidPrice() { return bidPrice; } public void setBidPrice(Double bidPrice) { this.bidPrice = bidPrice; } }
在 stateless bean PlaceBidBean 和 Bid JPA entity 使用 @Component annotion ,这样就可以将它们同时注册到通过 WebBeans 容器里去。 @Named annotation 给组件分配的名称可以由 WebBeans 容器识别出来。而这些名称可以直接在 JSF 页面上使用。 ( 注:比如说上面的 @Named(“bid”) 相当于为 Bid 类起了一个别名,这样做的意义在于 JSF 可以用 bid 直接取值了,比如说 #{bid.bidId}, #{bid.bidder} 等,与 Struts2 需要写一大堆 set,get 方式相比,确实精简不少。 ) 在 bid 实例变量上的 @In annoation 是告诉该 session bean , bid 是注入进来的。我相信到这里,你应该很清楚在使用 WebBeans 组件后,相应的 JSP 页面代码会是怎么个情况了吧。图一,显示了一个实际竞价页面:
<html> ... <body> <f:view> ... <h:form> <table> <tr> <td>Bidder</td> <td><h:inputText value="#{bid.bidder}"/></td> </tr> <tr> <td>Item</td> <td><h:inputText value="#{bid.item}"/></td> </tr> <tr> <td>Bid Amount</td> <td><h:inputText value="#{bid.bidPrice}"/></td> </tr> </table> ... <h:commandButton type="submit" value="Add Bid" action="#{placeBid.addBid}"/> ... </h:form> ... </f:view> </body> </html>
正如你所看到,使用 EL 绑定在 JSF 页面上的 bidder , item 和 bid amount 字段都与 Bid entity 的属性是一致的。而“ bid ”和“ placeBid ”匹配的就是 @Named 所标注过的组件。当 WebBeans 第一次遇到符合 @Named 所标注的 Bid 实体时,它会在隐式的在后台创建一个 Bid 的实例,并将压入 request 的 context ,供页面使用。同时你还要注意到 PlaceBidBean.addBid 方法也已经作为一个 action listener 绑定在 JSP 页面的添加按钮上。当按钮点击时,触发表单提交, WebBeans 所绑定的表单值又会自动依次中填充回 Bid 实体的各个属性,这一切还得归功于 @In annoation 。与 Bid entity 不同的是, EJB 这一块会从 JNDI 去 look up PlaceBidBean ,然后将它放入 reqeust 的 context 中。当添加按钮触发后, addBid 方法就会被调用, EJB 马上就会使用 JPA entity manager 来保存注入的实体。除了 request , application, session context, WebBeans 来发明了“ conversation ” context 。关于这些 contexts 的更多细节,请参看 WebBeans 的草案。
单凭以上代码就吹捧 WebBeans 编程模型的确没有多大的说服力。 WebBeans 用一种方法将 JSF,EJB 和 JPA 整合起来,让人感觉这似乎是一个真正的 JavaEE 无缝开发平台。它的 out of box( 即拆即用 ) ,最小化 boilerplate code ,让你如同正在使用像 Ruby on Rails 这样的框架。你是不是这样看待的呢?你觉得还是这样很牵强?这样的模型有缺陷吗?
WebBeans 的依赖注入
绝大部分的企业应用程序组件无非就是: service 组件, DAOs 以及 domain model 组件。一般来说, JPA 是实现 domain model 组件的最佳选择;而 service 和 DAO 组件又是 EJB 的不二选择。因为 EJBs 默认具备事务和线程安全的特性。 ( 很多人其实错误的认为 EJBs 默认也是支持 remote 的,这不是真的。 ) 如果你要使用声明式的 security , remoting , web services , messaging , scheduling 或者 asynchronous processing 的话, EJB 其实显然是一个更具优势的组件。上述这些 services 我们已经完全以 annoation 的形式给封装好成 EJB 组件了。
尽管如此,在一个应用程序里,总是还有一些组件并根本不需要声明式 servies ,不需要事务的感知 (transaction-aware) ,也不需要显式的线程安全保证。就拿 utilities 或 helper 组件来说吧,它们确实没有必要。因为 EJB3.0 对那些非托管的组件,并不支持依赖注入,因此不把它们也搞成 EJB ,是没法直接使用依赖注入的。 WebBeans 的依赖注入很好的解决了这个问题——因为 @Component annoation 可以运用于任何 POJO 对象,不再局限于 EJBs 和 JPA 实体了。刚才的咱们不就用 @In annoation 将 Bid entity 注入到 PlaceBidBean 这个 stateless session bean 中去了吗?因此, WebBeans 同样可以将普通的组件注入到 EJBs 中去了 。
现在我们还是快速来看一个例子。假设用户输入的 bid amount 属性需要先通过一个 utility 类进行四舍五入到小数点两位,然后再保存,那么我们可以这么做:
@Component public class MathUtil { ... public static double round(double value, int decimalPlaces) { BigDecimal converter = new BigDecimal(Double.toString(value)); converter = converter.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP); return converter.doubleValue(); } ... } @Component @Stateless @Named("placeBid") public class PlaceBidBean { @PersistenceContext private EntityManager entityManager; @In private Bid bid; @In private MathUtil mathUtil; public void addBid() { bid.setBidPrice(mathUtil.round(bid.getBidPrice(), 2)); entityManager.persist(bid); } }
在这个例子里,当 EJB 需要时,一个新的 MathUtil 的实例会被创建,然后注入到相应的 EJB bean 中去。尽管普通的 WebBeans 组件无法直接使用 EJB 的声明式 services ,但他们可以使用自己的依赖注入,生命周期回调( life-cycle callbacks ,使用 @PostConstruct/ @PreDestroy 实现)以及 interceptoer 。
@In 和 @Component annotations 还只是冰山一角。 WebBeans 为 JavaEE 带来了一套完整的依赖注入特性,包括 Guice 风格的方法创建和 annoation 绑定。注意所有的 EJB 类型包括消息驱动 Bean 都可以使用 WebBeans 特性。
WebBeans 的 Interceptor Enhancements
相对于 EJB 来说, WebBeans 另一个主要的特性就是对现有 interceptor model( 拦截器模型 ) 的扩展。现在你可以直接在 EJBs 中使用 @Interceptor 和 @Interceptors annotations 了。这样的结构既优雅又直接,不过不是非常灵活。 WebBeans 在些基础上,通过使用 annoation, ,引入了更加灵活的 Interceptor 机制,却没有仍然没有增加什么复杂度。理解它的最好方法还是代码。现在假设我们想使用一个 auditing interceptor 来拦截 EJB 的 PlaceBidBean ,我们可以这么做:
@InterceptorBindingType @Target({TYPE, METHOD}) @Retention(RUNTIME) public @interface Audited {} @Audited @Interceptor public class AuditInterceptor { @AroundInvoke public Object audit(InvocationContext context) throws Exception { System.out.println("Entering: " + context.getMethod().getName()); System.out.println(" with args: " + context.getParameters()); return context.proceed(); } } @Component @Stateless @Named("placeBid") public class PlaceBidBean { @PersistenceContext private EntityManager entityManager; @In private Bid bid; @Audited public void addBid() { entityManager.persist(bid); } }
在 @Audited annotation 上面的那个 @InterceptorBindingType annotation 是用来声明它要绑定到一个 interceptor 上。与 EJB3.0 interceptor model 不同的是, WebBeans 通过使用 @Interceptor annotation 将一个 interceptor 绑定在一个或更多的 annotations 上。因此 @Audited 和 @Interceptor annotations 同样放在 AuditInterceptor 上面表示凡是标注了 @Audited 的组件或方法都会被拦截器拦截。因此,当 placeBid 方法被调用时, AuditInterceptor 先会触发 audit 方法的执行。
除了它的间接性与灵活性外,我觉得这个扩展真的让代码变得更加可读。你觉得呢?是不是 EJB 规范也应该采纳这个扩展,正式成为 JavaEE 整体中的一部分呢?
继续工作
由于 JavaOne 大会的原因,专家组进度慢了下来也是情有可原。但不管怎么说,还是要言归正传,回到正常的工作中来。还有很多有趣的主题正在激烈的讨论着,它们是:
<!---->l <!---->标准 JNDI 的映射机制同时由 JavaEE6 和 EJB3.1 两个专家组同时进行讨论。相信将来这一块应该没有多少水份。(笑:难道 JSR 专家组们自己觉得自己的东西都很有水份?)
<!---->l <!---->支持 JavaSE 环境也可以使用 EJB3.1 (比如说单元测试)的工作仍然在进行着。实际上,继 OpenEJB , EasyBeans 和 Embedded JBoss 的路线之后, Embedded GlassFish 的动作也强烈的表明了它的决心。
<!---->l <!---->一个很牛的扩展机制下在 JavaEE6 专家组中炸开了锅。而且好像它还是本次专家组的讨论最后一个 Big Thing 。如果通过审核,那么就会为 JavaEE 社区提供一个非标准的第三方基于 annoation 的声明式 services 组件。( BTW :究竟是什么,这么神秘, Raza 也真会“炒作”啊,呵呵)
你是怎么看待这些特性的?如果你觉得它们很重要,请大胆发表你的意见,并给专家组发 Email . EJB3.1 专家组的电子邮件 [email protected] WebBeans 专家组的电子邮件是 [email protected] 。也可以随时给我 [email protected] 发邮件。在此期间,我还会继续给你们介绍接下来专家组们正在讨论的特性,也许是本系列的最后一篇了。敬请期待。
参考