阅读提要 EJB 3.0规范的最终稿已经提交到JCP,如果不出意外EJB3.0将在2006年正式发布。本文作为探讨EJB 3.0公共草案三系列中的第一篇,将解释EJB 3.0和企业Bean组件的基本概念;另外,你还学习到怎样使用JBoss和Maven来开发基于EJB 3.0的企业级bean组件。
尽管EJB 3.0规范还没有正式发行,但是它已经在Java开发社群中引起广泛兴趣——无论是对其拥护者还是其竞争对手。所有人都承认迫切需要找到更有生产效率的软件开发方法;如今,他们的争论集中于在EJB 3.0。
尽管存在争论,但是EJB 3.0规范草稿的发行和在JBoss的初步支持意味着现在正是探索这种深具影响力的技术的时候。事实上,有关在生产系统中应用EJB 3.0的报告早已浮出水面。
本文是探讨EJB 3.0公共草案三系列中的第一篇。本文将向你介绍该草案中的一些专门概念并将同你一起用JBoss来实现这些具体的技术。另外,本文还涉及一些EJB 3.0和企业Bean组件的基本概念。
为此,你需要作如下准备:
·Java 2 SDK 1.5
·Maven 2.0
·JBoss应用程序服务器4.0.3
·JBoss AOP 1.3.4
一、EJB 3.0简介
企业级应用程序是这样一类应用程序-它们实现商业功能并包含大量的数据。这些应用程序一般不在孤立的环境中执行任务,取而代之的是,它们当中的每一段程序往往成为庞大的IT系统的一个小小的组成部分并且必须与人和另外的系统进行交互。简而言之,开发企业级应用程序是一件相当难的事情。在几乎所有的企业级应用程序中,为了在企业中成功地执行它们的功能,必须强调如性能,可伸缩性,并发性和安全性等一系列的问题。
作为对这些挑战的回应,为了更容易地开发分布式的面向对象的商业系统,在1998年3月,产生了EJB规格说明书。该规格说明书和实现它的应用程序服务器已经在很大程度上实现了这个目标。但是,在过去的几年中,EJB的不足以及随着更简化实现方式的出现,逐渐提出了很多问题-是否EJB能够为企业级应用程序的生产力开发提供最好的解决方案?
EJB经常被提及的不足包括如下:
·如果想利用提供的企业服务的组件就需要使用大量的EJB API。
·它是一种要么全有要么全无的提议。就算你想要使用一种EJB服务,你也必须使用所有伴随EJB的其它内容。
·EJB组件要求有相应的容器并且被证明是难于测试的。
·要求每个EJB实现若干内容(接口,类和描述符)。
·传统型EJB架构很少涉及面向对象并且引入了只有状态而没有行为的"假对象"。
关于EJB的一些流行选择有:
·面向方面编程(AOP)
·轻量级容器,如Spring,Pico Container和Hivemind
·对象关系映射(ORM)工具,如Hibernate,JDO和iBatis
·.NET服务组件
EJB 3.0规格说明书公共草案主要目的是使开发更为容易并使得平衡企业服务更为简单。这些改进在某种程度上得益于以上所列举内容的成功应用。上面工具的某些方面在EJB 3.0还留有它们的踪迹:
·一个POJO(普通Java对象)编程模型
·一个轻量级ORM持续性框架
·依赖性注入
·元数据注解
在EJB 3.0规格说明书中,其它一些重要组成包括:
·通过异常进行配置
·消除组件接口
·消除home接口
·检查异常使用的减少
本文假定你已经熟悉EJB 2.x并且会关注在3.0 规格说明书公共草案中所引入的新的变化。本文余下的部分将通过使用Maven和JBoss应用程序服务器来分析构建EJB 3.0应用程序的过程中的这些改进。
二、应用程序实例-一个在线音乐店
本文中所讨论的技术全部展现在所附的示例应用程序中-一家在线音乐店。这家店允许用户浏览和购买音乐,视频和音频书籍。图1显示出这个界面的一个屏幕快照;你可以下载本文最后有关的源代码。
图1.音乐店:该音乐店允许浏览的产品可以被添加到一个购物车上并最终实现购买。
这个示例程序展示了所有的三种类型的企业bean组件的使用,即无状态会话beans,有状态会话beans和消息驱动的beans。注意,在EJB 3.0规格说明书中,实体beans不再和会话beans及消息驱动的beans一样被当作企业bean组件。代之的是,它们被认为是持续性实体(这将在我的下一篇有关EJB 3.0持续性的文章中介绍)。图2中的类图是一个怎样用EJB实现该音乐店应用程序的高级展示。
图2.音乐店设计:在这个web应用程序中,请求是用一个Struts行为来处理的-这个行为使用两个EJB(一个无状态,一个有状态)来实现请求。在付款台,顾客的购物车经由JMS传递到一个以一个MDB形式实现的订单处理器。
本文相随的实例应用程序的设计和测试工具有:JBoss应用程序服务器4.0.3和JBoss AOP 1.3.4,并且使用Maven 2.0来构建这个应用程序。所附源代码为设置Jboss、使用Maven构建源文件和发布该应用程序均提供了指导。
三、无状态会话Beans
与EJB 3.0规格说明书中的其它一些企业bean组件一样,无状态会话beans是一些实现了一个商业接口的POJO。以我们的音乐店为例:该音乐店实现在用户浏览时对各种类型的音频和视频的查询功能。对这个音乐店来说,实现这一功能的一个简单的接口实现可以如下编写:
public interface IMusicStore{
public List<Genre> listGenres();
public Genre findGenreById(int id);
public Artist findArtistById(int id);
public Product findProductById(int id);
public void checkOut(IShoppingCart cart);
}
并且下面是这种情况的一个简单实现:
public class MusicStore implements IMusicStore{
public List<Genre> listGenres()
{ return MusicStoreDAO.getInstance().listGenres(); }
public Genre findGenreById(int id)
{ return MusicStoreDAO.getInstance().findGenreById(id); }
public Artist findArtistById(int id)
{ return MusicStoreDAO.getInstance().findArtistById(id); }
public Product findProductById(int id)
{ return MusicStoreDAO.getInstance().findProductById(id); }
public void checkOut(IShoppingCart cart)
{ ... }
}
在EJB 2.x中,如果你想要利用企业服务的优点,如声明性事务管理或对象分布,那么你必须修改该音乐店以实现javax.ejb.SessionBean接口以及在这个接口中定义的方法;另外,还要创建本地的和远程的bean和home接口。
在EJB 3.0中,一个类要被扩展为一个无状态会话bean必须满足下面两个基本要求:
1. 实现至少一个商业接口。
2. 被用@javax.ejb.Stateless注解所注解。
一个商业接口仅是一个普通的Java接口。其中,java.io.Serializable和java.io.Externalizable接口都被自动地忽略,因为这些都是在javax.ejb包中的接口。如果只有一个接口被指定,那么它就被假定为一个本地接口。这是在规格说明书的配置中唯一的一个例子。如果这个bean有多于一个的商业接口,那么每个接口必须被显式地定义为本地的或远程的-通过注解这个bean类或者通过注解这些接口本身。
因此,为了把MusicStore类做成为一个无状态的会话bean,仅需做一个改变。你必须用@Stateless注解来注解它。
@Stateless
public class MusicStore implements IMusicStore{ ...}
与EJB 3.0相关的另一个主要变化实际上是一个J2EE 1.5规格说明书中的变化-依赖性注入。J2EE 1.5规格说明书要求,J2EE应用程序客户、企业beans和web组件都可以存取一个JNDI命名环境。该命名环境可以被存取,或者通过显式的JNDI查找调用或通过指定容器将自动注入依赖性的注解。这意味着,现在你可以通过一个如下的简单注解声明一个对一个EJB或其它由容器管理的资源的依赖性:
import com.devx.musicstore.IMusicStore;
public abstract class AbstractStoreAction extends Action{
@EJB
protected IMusicStore store;
...
}
就是这样!该容器将把接口ImusicStore的一个实现注入到该行为中,而该行为可以使用这个存储对象,就象它是一个简单的POJO一样。
作者注:
在JBoss4.0.3 版本中还没有实现针对web组件的资源注入功能,尽管这种支持即将出现(http://jira.jboss.com/jira/browse/EJBTHREE-212)。该实例代码包含一个方面-用JBoss AOP写成,它实现了依赖性注入。
其实背后真正进行的是,这个注解指定把一个到类型IMusicStore的EJB参考注入到store字段中。该依赖性的目标并没有指定,因此必须由发布者来提供-就象使用传统型的资源参考一样。如果没有指定依赖性名字(如上面例子中所示),那么就假定是使用类的完全限制的命名。在上面的实例中,这将是java:comp/env/com.devx.musicstore.IMusicStore。
作者注 在缺省地使用EJB参考的名字时,在有关是否使用不限制的或完全限制的类命名的问题上在EJB 3.0和J2EE 1.5规格说明书存在一些歧义。EJB 3.0公共草案指定不限制的bean类命名,而J2EE 1.5草案显示了一个完全限制的命名示例。JBoss实现使用完全限制的命名。
作为选择,你可以如下方式来进一步提练这种依赖性的声明:
public abstract class AbstractStoreAction extends Action{
@EJB(
name = "ejb/musicStore",
beanName = "store1",
BusinessInterface = com.devx.musicstore.IMusicStore.class
)
protected IMusicStore store;
...
}
在此,对EJB参考的存取是通过在java:comp/env/ejb/musicStore的JNDI实现的。
四、远程接口
为了远程存取一个会话bean,你仅需要用如下的@Remote注解来对它进行注解即可:
@Stateless
@Remote
public class MusicStore implements IMusicStore
{ ...}
在上面的实例中,MusicStore bean仅仅实现了一个商业接口;既然这个类被指定为远程的,所以我们假定它是远程的。否则,如果MusicStore实现了两个商业接口,那么你将被要求指定哪个接口是本地和哪个接口是远程的。为此,你可以使用下面两种方法中的一种:在类上或在接口上。改变MusicStore类来使用一个远程接口的情况如下所示:
@Stateless
@Local( { IMusicStore.class } )
@Remote( { IMusicStoreRemote.class } )
public class MusicStore implements IMusicStore, IMusicStoreRemote
{ ...}
你可以把上面的方法与在一个接口上指定的方法进行比较:
@Remote
public interface IMusicStoreRemote
{ ...}
注意,远程接口并没有抛出RemoteExceptions异常。如果一个异常在协议级上出现,那么一个包装了RemoteException的EJBException异常将由容器抛出。作为一个RuntimeException异常,EJBException不需要在远程接口的方法签名或在它的实现bean类中声明。
五、有状态的会话beans
有状态的会话beans与无状态的会话beans有非常相似的需求。就象无状态的会话beans,它们必须至少有一个商业接口。然而,它们被代之用@Stateful注解所注解。因此在音乐店实例中,用户的购物车已经被建模为一个有状态的会话bean-既然它描述了用户和服务器之间的有状态的会话:
@Stateful
public class ShoppingCart implements IShoppingCart
{ ...}
如在EJB 2.x中一样,调用相同的到一个有状态的会话bean的参考也是由相同的EJB实例来处理的;这样以来,把这一参考存储到一个能够被客户不断读取的位置就显得极为重要。在音乐店情况下,这个参考存储在用户的HttpSession中。
六、消息驱动的Beans
消息驱动的Beans(MDB)还要实现一个商业接口并且被注解以指示它们的bean类型。然而,在这种情况中,该商业接口并不是来自于域而是来自于一个针对所用消息系统类型的适当的听者接口。在JMS的情况下,这是javax.jms.MessageListener。这个音乐店的订单处理器提供一实例:
@MessageDriven
public class OrderProcessor implements MessageListener{
public void onMessage(Message message){
ObjectMessage objectMessage = (ObjectMessage) message;
try{
Order order = (Order) objectMessage.getObject();
System.out.println("Products Ordered:");
for (Product p : order.getProducts())
{ System.out.println(p.getTitle()); }
}
catch (JMSException e) { e.printStackTrace(); }
}
}
就象在EJB 2.x一样,你可以给发布者提供关于如何配置MDB的其它信息。这个信息现在能够通过@MessageDriven注解的activationConfig元素来提供。例如,为了定义OrderProcessor仅在JMS消息到达订单队列时激活,你可以对类进行如下注解:
@MessageDriven(activateConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/orders")
})
public class OrderProcessor