选择显示字体大小

用ejb 3.0简化企业java开发(上)

上篇:使用注释开发pojo服务

对开发服务器端应用程序而言,java企业版本即java ee(以前叫j2ee)是一个功能强大、但又过于复杂的平台。很早以来,过于复杂历来被认为是阻碍人们采用java ee的一个重要因素。

但在过去的三年,java开放源代码社区、java社区组织(jcp)以及主要的java ee开发商都在致力于简化java ee。譬如说,实际的应用程序使用新的设计范例来简化java ee的开发,譬如普通java对象(pojo)服务、服务拦截器和依赖注入。而诸多新的工具和框架也得到了广泛采用,用于同样的目的,譬如hibernate、面向方面编程(aop)、struts、xdoclet和spring

这些模式和工具让刚入门的开发人员更容易上手,同时提高了经验丰富的java开发人员的生产力,目前它们正在被jcp集成到下一代java ee标准(即ejb 3.0)当中。java开发人员raghu kodali最近开展的一项调查表明,把sun的java ee示例应用程序rosterapp从ejb 2.1移植到ejb 3.0可以减少50%以上的代码。

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配置文件,而且不需要严格的组件模型。

注释与xml

基于xml的部署描述符和注释都可以用来配置java ee应用程序中的服务相关属性。两者的区别在于:xml文件与代码分开处理(往往在运行时);而注释与代码一起编译,而且由编译器进行检查。这对开发人员产生了以下这些重要影响:

● 冗长性:xml配置文件以冗长出名。为了配置代码,xml文件必须从代码地方复制许多信息,譬如类名称和方法名称。另一方面,java注释却是代码的一部分,不需要另外引用代码,就可以指定配置信息。

● 健壮性:xml配置文件中的复制代码信息带来了多个潜在故障点。譬如说,如果拼错了xml文件中的方法名称,应用程序会在运行时出错。换句话说,xml配置文件不如注释来得健壮。注释可以由编译器来检查,同代码的其余部分一起处理。

● 灵活性:因为xml文件与代码分开处理,所以基于xml的配置信息不是“硬编码”的,以后可以改动。部署时间的灵活性对系统管理员来说是一项很好的特性。

注释使用简单,足以满足大多数应用程序的要求。xml文件比较复杂,可用来处理更高级的问题。ejb 3.0允许通过注释来配置大多数应用程序的设置。ejb 3.0还支持xml文件用于取消默认的注释值、配置外部资源(如数据库连接)。

pojo与严格组件

除了取代及简化xml描述符外,注释还可以让我们弃用曾困扰ejb 1.x和ejb 2.x的严格的组件模型。

ejb 组件是容器管理的对象。容器在运行时操纵bean实例的行为和内部状态。为了让这种行为出现,ejb 2.1规范定义了bean必须遵守的严格的组件模型。每个ejb类必须从为容器提供回调钩子(callback hook)的某个抽象类继承而来。因为java只支持单一继承,严格的组件模型就限制了开发人员使用ejb组件创建复杂对象结构的能力。读者会在本文下篇分看到,如果映射实体bean中复杂的应用程序数据,这更是个问题。

ejb 3.0中,所有容器服务都可以通过注释进行配置,并提供给应用程序里面的任何pojo。大多数情况下,不需要特殊的组件类。

开发松散耦合的服务对象

java ee等企业中间件的最重要的好处之一就是,让开发人员可以使用松散耦合的组件来开发应用程序。这些组件仅仅通过已发布的业务接口来进行耦合。因此,可在不改变应用程序其余部分的情况下,改变组件实现类。这样使应用程序更健壮、更容易测试,而且更容易移植。ejb 3.0简化了在pojo中构建松散耦合的业务组件。

会话bean

ejb 3.0应用程序中,松散耦合的服务组件通常作为会话bean来实现。会话bean要有一个接口(即业务接口),那样其他应用程序的组件就可以通过它使用其服务。下面的代码为我们的示例投资计算器服务提供了业务接口。根据投资者开始投资时及终止投资时的年龄、基金增长率及每月储蓄额,它只有一个方法来计算总的投资回报。

public interface calculator {

public double calculate (int start, int end, double growthrate, double saving); }

会话bean类仅仅实现了业务接口。必须通过为其添加无状态或者有状态的注释,告诉ejb 3.0容器这个pojo类是会话bean。有状态的会话bean可以在几个不同的服务请求期间保持客户端状态。与之相反,无状态的会话bean的请求每次都是由随机的会话bean实例来处理。其行为与原来ejb 2.1中的有状态和无状态的会话bean的行为相一致。ejb 3.0容器计算出什么时候为bean对象创建实例,然后通过业务接口来提供。下面是会话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; }

}

还可以为一个会话bean指定多个接口-一个用于本地客户端,一个用于远程客户端。只要使用@local和@remote注释,就可以区别接口。下面的代码片断显示了calculatorbean会话bean同时实现了本地接口和远程接口。如果你没有@local和@remote注释,会话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"; }

}

会话bean用户通过java命令和目录接口(jndi)得到bean的存根对象。由容器提供的存根对象实现了会话bean的业务接口。针对存根对象的所有调用都被转向容器,并针对可管理的bean实例进行调用。至于无状态的会话bean,每次进行调用时,都能获得新的存根对象。至于有状态的会话bean,必须把存根对象缓存在客户端上,那样容器就知道以后每次调用时为你提供相同的的bean实例。下面的代码片断显示如何调用会话bean。这里介绍获得bean存根对象的一种更简单的方法。

initialcontext ctx = new initialcontext();

cal = (calculator) ctx.lookup(calculator.class.getname());

double res = cal.calculate(start, end, growthrate, saving);

会话bean的生命周期管理

为了实现松散耦合,应用程序把会话bean实例的创建、缓存、销毁全部交给ejb 3.0容器(即反向控制设计模式)。而应用程序只处理业务接口。

但如果应用程序需要对会话对象实行粒度更细的控制,该如何呢?譬如说,应用程序可能需要在容器创建会话bean时执行数据库初始化,或者在销毁bean时需要关闭外部连接。只要在bean类中实现生命周期回调方法,就能实现这些操作。这些方法由容器在bean生命周期的不同阶段(如bean创建和销毁)进行调用。在ejb 3.0中,可以指定任何bean方法作为回调,只要为其添加下列注释。不像ejb 2.1里面,所有的回调方法都必须加以实现,即便回调方法是空的;ejb 3.0 bean可以有好多回调方法,可以是任何方法名称。

● @postconstruct:bean实例创建后,容器立即调用添加了注释的方法。这个注释同时适用于有状态和无状态的会话bean。

● @predestroy:容器从对象池当中销毁闲置或者过期的bean实例之前,调用添加了注释的方法。这个注释同时适用于有状态和无状态的会话bean。

● @prepassivate:如果某个有状态的会话bean实例闲置时间过长,容器就会将它挂起(passivate),并把其状态保存在缓存当中。容器将bean实例挂起之前,调用由这个注释作以标记的方法。这个注释适用于有状态的会话bean。

● @postactivate:如果客户端再次使用已被挂起的的有状态的会话bean时,新的实例被创建,bean状态被恢复。如果被激活的bean实例准备就绪,就调用由该注释作以标记的方法。这个注释只适用于有状态的会话bean。

● @init:这个注释为有状态的会话bean指定了初始化方法。它有别于@postconstruct注释之处在于:在有状态的会话bean中,可以用@init对多个方法作以标记。不过,每个bean实例只能有一个@init方法被调用。ejb 3.0容器决定调用哪个@init方法,具体取决于bean是如何创建的。@postconstruct方法在@init方法之后被调用。

生命周期方法的另一个有用注释是@remove,对有状态的会话bean来说更是如此。应用程序通过存根对象调用使用@remove标注的方法时,容器就知道在该方法执行完毕后,把bean实例从对象池当中移走。下面是这些生命周期方法注释在calculatorbean中的一个示例:

@stateful

public class calculatorbean implements calculator, serializable {

@postconstruct

public void initialize () {

//初始化历史记录,并从数据库中装入必要数据。 }

@predestroy

public void exit () {

// 若有必要,把历史记录保存至数据库中 }

@remove

public void stopsession () {

// 调用该方法以通知容器,移除该bean实例、终止会话。方法体可以是空的。}

}

消息驱动的bean

会话bean服务通过同步方法调用来提供。另一种重要的松散耦合的服务就是,由入站消息触发的异步服务,入站消息包括电子邮件或者java消息服务(jms)消息。ejb 3.0消息驱动的bean(mdb)是为了处理基于消息的服务请求而设计的组件。

mdb类必须实现消息监听器(messagelistener)接口。当容器检测到该bean的消息后,就调用onmessage()方法,并把入站消息作为调用参数传递。mdb会决定在onmessage()方法中如何处理消息。可以用注释来配置这个mdb监控哪些消息队列。mdb部署后,容器使用注释里面指定的配置信息。在下面的示例中,当容器检测到queue/mdb jms队列中的入站消息后,就会调用calculatorbean mdb。mdb会解析消息,并根据消息内容执行投资计算。

@messagedriven(activateconfig =

{

@activationconfigproperty(propertyname="destinationtype",

ropertyvalue="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来查询存根对象(用于会话bean)或者消息队列(用于mdb)。jndi查询是把客户端从实际实现的服务对象解除耦合的一个关键步骤。不过,基于字符串名的普通jndi查询并不方便。以下是几个原因:

● 客户端与服务端必须就基于字符串的名字达成一致。这不是由编译器或者任何部署时间检查所执行的契约。

● 已获取的服务对象在编译时不进行检查,可能会导致运行时出现数据类型转换错误(casting error)。

● 应用程序里面一再出现冗长的查询代码,该代码有自己的try-catch代码块。

ejb 3.0采用了一种简单、便利的方法,把解除耦合的服务对象和资源提供给任何pojo使用。你使用@ejb注释,就可以把ejb存根对象注入到ejb 3.0容器管理的任何pojo中。如果对某字段变量标以注释,容器会在第一次访问之前,为该变量赋予正确的值。下面的示例显示了如何把calculatorbean无状态会话bean的存根对象注入到calculatormdb mdb类中。

public class calculatormdb implements messagelistener {

@ejb calculator cal;

// 使用cal变量

// ... ... }

如果对某个属性的javabean风格的设置方法标以注释,属性第一次使用之前,容器会自动用正确的参数调用属性设置方法。下面的代码片断演示了工作过程:

public class calculatormdb implements messagelistener {

calculator cal;

 @ejb

 public void setcal (calculator cal) {

this.cal = cal; }

// 使用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;

此外,你还可以把容器管理的持久性管理器(即实体管理器——类似hibernate会话对象)注入到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 {

// 在循环中更新数据库

// ... ...

//循环中的操作必须全部成功,否则数据库根本不更新。 }

}

安全

容器还能提供验证用户身份的安全服务,并且可以根据用户角色,限制对可管理的pojo的访问。对每个pojo类而言,你可以使用@securitydomain注释指定安全域,它能告诉容器到哪里去找密码和用户角色列表。jboss里面的other域表明文件是类路径中的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 {

//被“log()”拦截的bean方法

// ... ...

@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简化企业java开发(下)

 


 


关键字 本文所属关键字

相关 与本文相关文章

分类 所有文章关键字导航

源码编程相关

Java   Asp   PHP   .Net   XML   C/C++   CGI   VB   Jsp   J2ee   J2se   J2me   EJB   Servlet   Tomcat   Resin   Struts   Weblogic   Eclipse   ANT   GUI   JMS   Web servise   IDEA   Webphere   Hibernate   Spring   Jboss   Applet   Swing   Socket   Javamail   Perl   Ajax   P2P   安全   模式   框架   测试   开源   游戏

SQL数据库相关

My-SQL   Ms-SQL   Access   DB2   Oracle   Sybase   SQLserver   索引   存储过程   加密   数据库   分页   视图  

手机无线相关

3G   Wap   CDMA   GRPS   GSM   IVR   彩信   短信   无线   增值业务

网页设计制作相关

HTML   CSS   网页配色   网页特效   Javascript   VBscript   Dreamweaver   Frontpage   JS   Web   网站设计

网站建设推广相关

建站经验   网站优化   网站排名   推广   Alexa

操作系统/服务器相关

Windows XP   Windows 2000   Windows 2003   Windows Me   Windows 9.x   Linux   UNIX   注册表   操作系统   服务器   应用服务器

图形图像多媒体相关

Photoshop   Fireworks   Flash   Coreldraw   Illustrator   Freehand   Photoimpact   多媒体   图形图像

标准 网站致力的规范

Valid CSS!

无不良内容,无不良广告,无恶意代码

Valid XHTML 1.0 Transitional

creativecommons