前言
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 专家组的电子邮件 jsr-318-comments@jcp.org WebBeans 专家组的电子邮件是 jsr-299-comments@jcp.org 。也可以随时给我 reza@rahmannet.net 发邮件。在此期间,我还会继续给你们介绍接下来专家组们正在讨论的特性,也许是本系列的最后一篇了。敬请期待。
参考
- New Features in EJB 3.1, http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesinEJB3-1 .
- New Features in EJB 3.1 - Part 2, http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesEJB31 .
- New Features in EJB 3.1 - Part 3, http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesEJB31-3 .
- JSR 316: Java EE 6, http://jcp.org/en/jsr/detail?id=316 .
- JSR 318: Enterprise JavaBeans 3.1, http://jcp.org/en/jsr/detail?id=318 .
- JSR 299: Web Beans, http://jcp.org/en/jsr/detail?id=299 .
- Seam, http://www.seamframework.org .
- Google Guice, http://code.google.com/p/google-guice/ .