扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
在过去的三年中,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文档是与代码分开处理的,特别是在运行时刻,而注释是与代码编译在一起的并被编译器检查的。对于开发者来说这就有了一些重要的含义,正如我下面所列出的:
冗长:XML配置文件是出了名的冗长的。为了配置代码,XML文件必须复制许多信息:比如代码中类名字和方法名字。Java注释则不同,它是代码的一部分,不需要额外的引用就可以指明配置信息。
强壮性:在XML文件中重复的代码信息引入了多处出错的可能。比如,如果你写错了方法的名字,那应用直到运行时刻才会出错垮掉。也就是说,XML配置文件的强壮性就不如注释,注释是被编译器检查的,并和其它代码一起被处理的。
灵活性:既然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实现类的代码:
@Stateless
public 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可以有任意数量,任意名字的回调方法。
@PostConstruct:当bean对象完成实例化后,使用了这个注释的方法会被立即调用。这个注释同时适用于有状态和无状态的session bean。
@PreDestroy:使用这个注释的方法会在容器从它的对象池中销毁一个无用的或者过期的bean实例这前调用。同时适用于有状态和无状态的session bean.
@PrePassivate:当一个有状态的session bean实例空闲过长的时间,容器将会钝化它,并把它的状态保存下来。使用这个注释的方法会在容器钝化bean实例之前调用。适用于有状态session bean。
@PostActivate:当客户端再次使用已经被钝化的的有状态session bean时,新的实例被创建,状态被恢复。使用此注释的session bean会在bean的激活完成时调用。
@Init:这个注释指定了有状态session bean初始化的方法。它区别于@PostConstruct注释在于:多个@Init注释方法可以同时存在于有状态session bean 中,但每个bean实例只会有一个@Init注释的方法会被调用。这取决于bean是如何创建的(细节请看EJB 3.0规范)。@PostConstruct在@Init之后被调用。
另一个有用的生命周期方法注释是@Remove,特别是对于有状态session bean。当应用通过存根对象调用使用了@Remove注释的方法时,容器就知道在该方法执行完毕后,要把bean实例从对象池中移走。
@Stateful
public class CalculatorBean implements Calculator, Serializable {
// ... ...
@PostConstruct
public void initialize () {
// Initializes the history records and load
// necessary data from database etc.
// 初始化历史记录,并从数据库中装入必需的数据。
}
@PreDestroy
public void exit () {
// Save history records into database if necessary.
// 如有必要则将历史记录保存至数据库中
}
@Remove
public void stopSession () {
// Call to this method signals the container
// to remove this bean instance and terminates
// the session. The method body can be empty.
// 调用这个方法来通知容器将bean实例移除并中止session.
// 这个方法可以为空。
}
// ... ...
}
消息驱动bean
Session bean服务提供了同步调用的方法。另一个重要的藕合松散服务类型是一种通过进入的消息来触发的异步服务(比如:email或Java消息服务产生的消息)。EJB 3.0的消息驱动bean(MDB)是设计用来专门处理基于消息请求的组件。
一个MDB类必须实现MessageListener接口。当容器检测到bean守候的队列一条消息时,就调用onMessage()方法,将消息作为参数传入。MDB在OnMessage()中决定如何处理该消息。你可以用注释来配置MDB侦听哪一条队列。当MDB部署时,容器将会用到其中的注释信息。在下面的例子中,CalculatorBean MDB会在JMS队列queue/mdb有消息进入时调用。MDB解析消息,并根据消息内容计算投资。
@MessageDriven(activateConfig =
{
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="queue/mdb")
})
public class CalculatorBean implements MessageListener {
public void onMessage (Message msg) {
try {
TextMessage tmsg = (TextMessage) msg;
Timestamp sent =
new Timestamp(tmsg.getLongProperty("sent"));
StringTokenizer st =
new StringTokenizer(tmsg.getText(), ",");
int start = Integer.parseInt(st.nextToken());
int end = Integer.parseInt(st.nextToken());
double growthrate = Double.parseDouble(st.nextToken());
double saving = Double.parseDouble(st.nextToken());
double result =
calculate (start, end, growthrate, saving);
RecordManager.addRecord (sent, result);
} catch (Exception e) {
e.printStackTrace ();
}
}
// ... ...
}
依赖注入
在上一节中,你学到了如何开发藕合松散的服务组件。但是,为了存取那些服务对象,你需要通过服务器的JNDI来查找存根对象(session bean)或消息队列(MDB)。JNDI查找是把客户端与实际的服务端实现解藕的关键步骤。但是,直接使用一个字符串来进行JNDI查找并不优雅。有这样几个原因:
客户端与服务端必须有一致的基于字符串的名字。它没有在编译时得到认证或在布署时得到检查。
从JNDI返回的服务对象的类型没有在编译时进行检查,有可能在运行时出现转换(casting)错误。
冗长的查找代码,有着自己的try-catch代码块,在应用之间是重复的和杂乱的
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者