使用EJB 3.0简化企业级Java开发一
概述
Java企业级版本,或者说Java EE(以前叫J2EE),对于开发服务器端的应用来说是一个强大的但却又过于复杂的的平台。从它诞生之日起,过于复杂一直是对使用Java EE犹豫不决的一个重要因素。在JavaWorld的以前的一篇文章”简化之路”中,我指出了那些让Java EE应用变复杂的因素,其中很多都是与当前的EJB 2.1规范有关。
在过去的三年中,Java开放源代码社区,Java社区进程(JCP)以及主要的Java EE供应商,一直致力于让Java EE更简单。举例来说:新的设计范例,比如POJO服务,服务拦截器和依赖注入,已经可以在实际应用中用来简化Java EE的开发了。还有,新的工具和框架,比如Hibernate, AOP(aspect-oriented programming,面向方面编程),Struts,Xdoclet和Spring, 也已经被广泛用于同一目的。
简化不是功能的减少
简化一个编程模型并没有减少它的功能。简化只是把复杂的逻辑隐藏到了框架代码或可重用的组件中去了。根本上,它是把复杂的东西从需要应用开发者直接管理的地方转移到了大多数开发者看不到的地方。
上述的模板和工具让初学者更容易上手,同时也提高了有经验的Java开发者的生产力,现在它们正在被JCP合并到下一代的Java EE标准中(比如:EJB 3.0)。由Java开发人员Raghu Kodali最近所做的研究显示:将Java EE的示例程序RosterApp从EJB 2.1转到EJB 3.0可以减少百分之五十以上的代码。
Java注释是EJB3.0背后的关键,它将POJO服务,POJO持久化和依赖注入一起绑定为一个完整的企业级中间件解决方案。这篇文章中,我使用了一个示例应用:JBoss EJB 3.0 TrailBlazer,来演示使用注释开发轻量级的EJB 3.0 POJO应用。TrailBlazer的应用使用EJB 3.0中不同的工具和API重复实现了一个投资计算器。示例程序完全可以在JBoss 应用服务器4.0.3版本中运行,并且与最新的EJB 3.0标准完全兼容(完成时)。
让我们来开始体验一下注释驱动编程模型的好处吧。
EJB 3.0的注释驱动编程模型
从开发者的观点来看,EJB 3.0广泛地使用了Java 注释.注释有两个关键优势:它们取代了过多的XML配置文件并且消除了严格组件模型需求。
注释 vs XML
基于XML的布署描述和注释一起都可以用来在Java EE应用中配置服务的相关属性。它们的区别在于:XML文档是与代码分开处理的,特别是在运行时刻,而注释是与代码编译在一起的并被编译器检查的。对于开发者来说这就有了一些重要的含义,正如我下面所列出的:
o冗长:XML配置文件是出了名的冗长的。为了配置代码,XML文件必须复制许多信息:比如代码中类名字和方法名字。Java注释则不同,它是代码的一部分,不需要额外的引用就可以指明配置信息。
o强壮性:在XML文件中重复的代码信息引入了多处出错的可能。比如,如果你写错了方法的名字,那应用直到运行时刻才会出错垮掉。也就是说,XML配置文件的强壮性就不如注释,注释是被编译器检查的,并和其它代码一起被处理的。
o灵活性:既然XML文件是在代码之外被单独处理的,那也就是说基于XML的配置信息不是“硬编码”的,是可以以后修改的。部署的灵活性对系统管理员来说是非常非常重要的特性。
注释是简单易用的,已证明对大多数应用来说足够了。XML文件更复杂,但能被用来处理更高级的问题。EJB 3.0允许你通过注释来配置大多数的应用。EJB 3.0也支持用XML文件来覆盖默认的注释,及配置像数据库联接这样的外部资源。
除了替换和简化XML描述符,注释也允许我们废除困扰EJB 1.x, EJB 2.x的严格组件模型。
POJO vs 严格组件
EJB 组件是容器管理(container-managed)的对象。容器在运行时刻操作Bean的状态和行为。为了让行为发生,EJB 2.1规范定义了一个Bean必须遵守的严格的组件模型。每一个EJB类必须从某一种抽象类中继承,并为容器提供了回调的钩子。既然Java只支持单继承,严格组件模型就限制了开发者使用EJB组件创建一个复杂对象结构的能力。当把复杂的应用数据映射到实体 Bean中的时候,正如我们在第二部分中看到的,这会成为一个很大的问题。
在EJB 3.0中,所有的容器服务都可以通过使用注释的POJO应用来配置和交付。大多数情况下,并不需要特殊的组件类。让我们通过JBoss EJB 3.0 TrailBlazer示例看一下如何在EJB 3.0中使用注释。
开发藕合松散的服务对象
像Java EE这样的企业级中间件的一个最重要的好处是允许开发者使用藕合松散的组件来开发应用。这些组件仅仅通过他们自己发布的商业接口来藕合。因此这些组件的实现类可以在不改变应用其余部分的情况下改变自己的实现。这将会使应用更加强壮,更容易测试,更易移植。EJB 3.0使得在POJO中创建藕合松散的商业组件变得更简单了。
Session bean
在EJB 3.0应用中,藕合松散的服务组件的典型应用是Session Bean。一个Session Bean至少有一个接口(也就是:商业接口),其它应用组件通过它获得服务。下面的代码为我们的投资计算器服务提供了商业接口。它只有一个方法,根据给定的起始年龄,终止年龄,增长率,月存金额,计算出总投资额。
public interface Calculator { public double calculate (int start, int end, double growthrate, double saving);}
Session bean类简单地实现了商业接口。你必须通过使用Stateless或Stateful注释来告诉EJB 3.0容器这个POJO类是一个Session Bean。有状态(Stateful)的session bean在不同的服务请求间维护着客户的状态。相反地,对于无状态(Stateless)的session bean,每次的请求都是被随机挑选的session bean实例处理的。这些行为是与EJB 2.1规范中的有状态和无状态session bean的定义是一致的。EJB 3.0容器算出何时实例化Bean对象,并通过商业接口让其可用。下面是session bean实现类的代码:
@Statelesspublic class CalculatorBean implements Calculator { public double calculate (int start, int end, double growthrate, double saving) { double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1); return saving * 12. * (tmp - 1) / growthrate; }}
你也可以为一个session bean指明多个接口-一个为本地客户服务,一个为远程客户服务。只要使用@Local和@Remote注释来区分。下面的代码片断显示了同时实现了本地和远程接口的CalculatorBean。如果你没有@Local和@Remote注释,session bean接口默认为本地接口。
@Stateless@Local ({Calculator.class})@Remote ({RemoteCalculator.class})public class CalculatorBean implements Calculator, RemoteCalculator { public double calculate (int start, int end, double growthrate, double saving) { double tmp = Math.pow(1. + growthrate / 12., 12. * (end - start) + 1); return saving * 12. * (tmp - 1) / growthrate; } public String getServerInfo () { return "This is the JBoss EJB 3.0 TrailBlazer"; }}
Session bean用户通过JNDI得到bean的一个存根(Stub)对象。容器所提供的存根对象实现了session bean的商业接口。所有针对存根的调用都被引向了容器,由容器调用相应的实现类中的接口。对于有状态的的session bean,你必须自己在客户端缓存存根对象,这样在每次的后续调用时,容器才知道要提供相同的的bean实例。下面的片断显示如何调用session bean.在后面,你将会学到获取存根对象的更简单的方法。
InitialContext ctx = new InitialContext();cal = (Calculator) ctx.lookup(Calculator.class.getName());double res = cal.calculate(start, end, growthrate, saving);
Session bean生命周期的管理
为达到藕合松散的目的,应用把session bean实例的创建、缓存、销毁全部交给EJB 3.0容器(也就是,反向控制设计模式)。应用只和bean的商业接口打交道。
但如果应用需要对session对象更好的控制呢?比如说,应用可能需要在创建session bean的时候初始化数据库联接,而在销毁bean时关闭外部的联接。上述这些,你都可能通过在bean类中定义生命周期的回调方法来实现。这些方法将会被容器在生命周期的不同阶段调用(如:创建或销毁时)。通过使有下面所列的注释,EJB 3.0允许你将任何方法指定为回调方法。这不同于EJB 2.1,EJB 2.1中,所有的回调方法必须实现,即使这是空的。EJB 3.0中,bean可以有任意数量,任意名字的回调方法。
o@PostConstruct:当bean对象完成实例化后,使用了这个注释的方法会被立即调用。这个注释同时适用于有状态和无状态的session bean。
o@PreDestroy:使用这个注释的方法会在容器从它的对象池中销毁一个无用的或者过期的bean实例这前调用。同时适用于有状态和无状态的session bean.
o@PrePassivate:当一个有状态的session bean实例空闲过长的时间,容器将会钝化它,并把它的状态保存下来。使用这个注释的方法会在容器钝化bean实例之前调用。适用于有状态session bean。
o@PostActivate:当客户端再次使用已经被钝化的的有状态session bean时,新的实例被创建,状态被恢复。使用此注释的session bean会在bean的激活完成时调用。
o@Init:这个注释指定了有状态session bean初始化的方法。它区别于@PostConstruct注释在于:多个@Init注释方法可以同时存在于有状态session bean 中,但每个be