在接下来的两篇文章中,你将会学到使用ejb3.0中的pojo编程模型来开发更简单,更强壮的企业级java应用。本文将阐述如何使用ejb 3.0 注释来开发藕合松散的pojo应用及容器服务器如何管理pojo
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文档是与代码分开处理的,特别是在运行时刻,而注释是与代码编译在一起的并被编译器检查的。对于开发者来说这就有了一些重要的含义,正如我下面所列出的:
冗长: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代码块,在应用之间是重复的和杂乱的
ejb 3.0,对任何pojo,提供了一个简单的和优雅的方法来解藕服务对象和资源。使用@ejb注释,你可以将ejb存根对象注入到任何ejb 3.0容器管理的pojo中。如果注释用在一个属性变量上,容器将会在它被第一次访问之前赋值给它正确的值。下面的例了演示了怎样把calculatorbean无状态session bean的存根注入到calculatormdb mdb类中。
public class calculatormdb implements messagelistener {
@ejb calculator cal;
// use the cal variable
// ... ...
}
注释如果被用在javabean风格的setter方法上时,容器会在属性第一次使用之前,自动地用正确的参数调用bean的setter方法。下面的片断演示了这是如何做的:
public class calculatormdb implements messagelistener {
calculator cal;
@ejb
public void setcal (calculator cal) {
this.cal = cal;
}
// use the cal variable
// 使用cal变量
// ... ...
}
除@ejb注释之外,ejb 3.0也支持@resource注释来注入来自jndi的任何资源。下面的例子中,我演示了如何注入服务器端默入的timerservice和sessioncontext对象,也演示了如何注入来自jndi的命名数据库和jms资源。
@resource
timerservice tms;
@resource
sessioncontext ctx;
@resource (name="defaultds")
datasource mydb;
@resource (name="connectionfactory")
queueconnectionfactory factory;
@resource (name="queue/a")
queue queue;
此外,你也可以把一个容器管理的持久化管理器(也就是,entitymanager-类似于hibernate session对象)注入到ejb 3.0 pojo中。
把容器服务交给pojo
除了管理生命周期和访问藕合松散的服务对象外,ejb 3.0通过简单的注释也为pojo提供了运行时刻服务。
事务
最有用的容器服务可能就是事务管理服务,当应用出现失败或异常时,它保证了数据库的完整性。你可以简单地将为一个pojo方法申明它的事务属性。这样容器就可以在合适的上下文中运行这个方法。举例来说,下面的代码申明了容器在运行updateexchangerate()时必须创建一个新的事务。当这个方法退出时提交事务。实际上,所有在updateexchangerate()中被调用的方法都在此事务中运行,除非有特别申明。在updateexchangerate()中的数据库操作要么全部成功,要么全部失败。
@stateless
public class calculatorbean implements calculator {
// ... ...
@transactionattribute(transactionattributetype.required)
public void updateexchangerate (double newrate) throws exception {
// update the database in a loop.
// 在循环中更新数据库
// ... ...
// the operations in the loop must all be successful or
// the database is not updated at all.
// 循环中的操作必须全部成功或者根本不更新。
}
}
安全
容器也提供了安全服务来进行用户认证和根据用户规则来限制对pojo的访问。对每一个pojo来说,你可以通过使用@securitydomain注释为它指定一个安全域, 安全域告诉容器到哪里去找密码和用户角色列表。jboss中的other域表明文件是classpath中的users.propertes和roles.properties。这样,对每一个方法来说,我们可以使用一个安全限制注释来指定谁可以运行这个方法。比如,下面的例子,容器对所有试图调用addfund()的用户进行认证,只允许拥有adminuser角色的用户实际运行它。如果你没有登录或者没有以管理员的身份登录,一个安全意外将会抛出。
@stateless
@securitydomain("other")
public class calculatorbean implements calculator {
@rolesallowed({"adminuser"})
public void addfund (string name, double growthrate) {
// ... ...
}
@rolesallowed({"adminuser"})
public void addinvestor (string name, int start, int end) {
// ... ...
}
@permitall
public collection <fund> getfunds () {
// ... ...
}
// ... ...
@rolesallowed({"regularuser"})
public double calculate (int fundid, int investorid,
double saving) {
// ... ...
}
}
通用拦截器
事务和安全服务都可以被看作是容器管理的运行时刻拦截器。容器拦截了对ejb存根的调用,并在其上应用事务上下文或进行安全限制。
在ejb 3.0中,你可以自己写拦截器来扩展容器服务。使用@aroundinvoke注释,你可以将任意bean方法作为拦截器方法在任意bean方法之前和之后运行。下面的例子中,log()方法是一个拦截器,它计算和记录了其它bean方法的执行时间:
@stateful
public class calculatorbean implements calculator {
// bean methods that are to be intercepted by "log()"
// bean方法将被log()方法拦截
//
// ... ...
@aroundinvoke
public object log (invocationcontext ctx)
throws exception {
string classname = ctx.getbean().getclass().getname();
string methodname = ctx.getmethod().getname();
string target = classname + "." + methodname + "()";
long start = system.currenttimemillis();
system.out.println ("invoking " + target);
try {
return ctx.proceed();
} catch(exception e) {
throw e;
} finally {
system.out.println("exiting " + target);
cal.settrace(cal.gettrace() + "
" +
"exiting " + target);
long time = system.currenttimemillis() - start;
system.out.println("this method takes " +
time + "ms to execute");
}
}
}
下一步?
在第一部分中,我大致地讨论了ejb 3.0基于pojo的编程模型和如何在ejb 3.0中开发藕合松散的服务组件。在第二部分中,我会讨论ejb 3.0的另一个主要的概念:可管理的pojo持久性。
阅读关于 java ejb j2ee pojo struts spring 的全部文章
闽公网安备 35060202000074号