构建一个EJB组件的步骤如下: 1.编写组件接口(EJBObject远程接口,EJBLocalObject本地接口),Home接口,Enterprise Bean等。 2.编写部署描述文件。ejb-jar.xml等。 3.利用Jar工具将以上打包为Ejb-jar文件。如:jar cvfm HelloWorld.jar * 4.发布EJB。
EJB对象: 1.由容器生成的。 2.实现了远程接口或本地接口。 3.客户端通过EJB对象委托调用enterpriseBean的方法。
远程接口: 1.继承javax.ejb.EJBObject。 2.定义并公开enterpriseBean的方法。 3.客户端通过该接口与容器生成的EJB对象交互。 4.其中定义的方法都必须抛出java.rmi.RemoteException异常。这是必须的,因为它继了EJBObject,而EJBObject实现了Remote接口。 5.客户端通过远程接口调用enterpriseBean的效率是很底的。原因如下: (1)首先客户端需调用本地Stub。 (2)Stub将参数格式化为适合网络传输的格式。 (3)Stub与服务器端的Skeleton连接。 (4)Skeleton解释被Stub格式化了的参数。 (5)Skeleton调用EJB对象。 (6)EJB对象连接缓冲,事务处理,安全检查,生命周期服务等工作。 (7)调用enterpriseBean的业务方法。当enterpriseBean完成任务,返回Home时,将重得执行以上步骤。
本地接口: 1.继承javax.ejb.EJBLocalObject。 2.本地用户可以通过本地接口高性能的访问EJB对象。(免去执行远程接口中的第5项工作)
Home接口: 1.继承javax.ejb.EJBHome。 2.用来创建EJB对象。 3.其中有个Create方法返回容器实现了远程接口的EJB对象。并抛出两个异常。java.Rmi.RemoteException和javax.ejb.CreateException。
本地Home接口: 1.继承javax.ejb.EJBLocalHome。 2.同本地接口相同,它是本地客户端可以使用的高性能Home接口。 3.其中有个Create方法返回容器实现了本地接口的EJB对象。抛出javax.ejb.CreateException异常。
1.无状态会话Bean 从字面意思来理解,无状态会话Bean是没有能够标识它的目前状态的属性的Bean。例如: public class A { public A() {} public String hello() { return Hello 谁?; } }
public class Client { public Client() { A a = new A(); System.out.println(a.hello()); A b = new A(); System.out.println(b.hello()); } } 在Client中生成了两个A的实例,不管是对象a还是b,它们是没有状态的。对于Client来说a和b是没有差别的(但a != b)。所以同一个无状态会话Bean的实例都是相同的,可以被不同的客户端重复使用。
2.状态会话Bean 至于状态会话Bean,可以这样理解:它是有存储能力的。也就是说至少有一个属性来标识它目前的状态。例如: public class B { private String name; public B(String arg) { this.name = arg; } public String hello() { return Hello + this.name; } }
public class Client { public Client() { B a = new B(中国); System.out.println(a.hello()); B b = new B(世界); System.out.println(b.hello()); } }
3.调度池: 其实调度池的概念同数据连接池概念基本相同,都是为了提高系统性能而设计的。就像是饺子馆一样,饺子的做法可以有两种,一种是不管当前有没有客人,先将一些饺子下锅,等到客人来后可以马上捞出来给客人吃。还有一种是客人来后才将饺子下锅。这个例子显然不是十分恰当,但它的确能说明问题。调度池的概念就像是饺子的第一种做法一样,先将一些Bean实例放入池中,(也就是缓存起来,具体缓存多少取决于不同的容器)等到客户端调用时直接可以取来使用。它和饺子的区别还是很大的,要是饺子下锅后没有客人来吃可惨了 :)
4.无状态会话Bean实现调度池调度: 因为无状态会话Bean是不保存会话状态的,所以无论哪个客户端调用了某个无状态会话Bean,都没有差别,也就是说任何一个无状态会话Bean的实例都可以为客户端程序提供服务。而且无状态会话Bean同客户端的关系是1:n。也就是说,同一个会话Bean实例可以供不同客户端使用。因此无状态会话Bean可以被存储在调度池中,供不同客户端重用。
5.状态会话Bean实现调度池调度: 因为状态会话Bean是保持当前会话的状态的,所以实现起来远比无状态会话Bean困难。因为它要保存会话状态,但物理内存的空间是有限的,想要保存n多客户端当前会话状态是不可能的。EJB容器采取了同操作系统相同的做法,将状态会话Bean对话状态保存在硬盘或其它存储器中(钝化passivation)。当被钝化的Bean原先的客户端调用其方法时,被钝化的状态重新交还给Bean(激活activation)。也许容器把被钝化的状态交还给不同的Bean实例,那又有什么关系呢?只要这个Bean实例能跟原先钝化之前的那个Bean实例一模一样就行了。事实上它的确一模一样:)大多数容器采用最近最少被使用(Least Recently Used:LRU)的钝化策略。因为客户端是不可能一直在执行操作,他可能在看新闻或其它什么的。所以以这种方式钝化Bean是非常合理有效的。但有一个例外:正在进行事务处理的Bean一定要等到事务处理完毕后才能被钝化。大多数容器采用根据需要被激活(Just-in-time)的策略。客户有请求,ok没问题,激活那个被钝化了的Bean:)注:钝化和激活跟无状态会话Bean没关系。
6.对话状态遵从的规则: Bean遵从Java对象序列化规则(Java Object Serialization)。因为javax.ejb.EnterpriseBean接口扩展了java.io.Serializable这个接口,所以每个Enterprise Bean类都间接实现了这个接口。
7.状态会话Bean钝化时保存的内容如下: a.EJB对象引用。 b.Home对象引用。 d.EJB上下文的引用。 e.JNDI命名上下文。
8.激活和钝化的回调方法: 每个会话Bean都实现了SessionBean,所以每个会话Bean都有ejbPassivate()和ejbActivate()方法,EJB容器调用ejbPassivate()方法使Bean释放或处理占用的资源。调用ejbActivate()方法使Bean恢复在ejbPassivate()过程中释放的资源。
其实,在EJB中比较复杂的Bean不是会话Bean,不是消息驱动Bean,而是实体Bean。因为它涉及到对象序列化,O/R Mapping等一些持久化技术(还有一直没有流行起来的对象数据库)。
什么是实体Bean? 实体Bean是有着一组属性并且每个属性与数据库表中的每个字段一一对应并且公开get和set方法供外界访问。如图:
每一个属性对应数据库表中的一个字段,这样一个Bean实例就对应了表中的一条记录。这里要注意的是,并不是固定的一个实例对应一条记录。如表中有5条记录,不一定有5个实体Bean实例来对应,有可能3个,有可能8个。为什么呢?
1.因为对象的创建和删除是十分消耗系统资源的,所以不可以为成千上万条记录创建成千上万个对象。解决的办法是将被实例化的Bean重复再利用,这样就可以节省这样的开销。容器可以动态的分配实体Bean实例给不同的客户端的EJB对象使用,这样不但节省了容器不必要地实例化Bean的开销,而且节省了系统资源。
2.因为EJB规定只能有一个线程可以运行在一个Bean实例中,也就是说会话Bean,消息驱动Bean,实体Bean都是单线程的。所以为了使不同的客户端方便的访问相同的数据,容器将实例化相同实体Bean的多个实例供不同的客户端使用。问题来了,当使用这种方法时,怎样保证数据的同步呢?假如其中一个客户更改这条数据,如何反映到持有相同实体Bean的不同实例的客户端呢?为了达到实体Bean实例缓存的一致性,每个实体Bean都必须公开两个方法,ejbLoad()和ejbStore()。容器通过调用ejbLoad()方法从存储空间(数据库,文件等)中读取数据。调用ejbStore()方法保存数据到存储空间中。 实体Bean实例是跟数据库表中的数据一一对应的。可以把它就看成关系型数据在JAVA中的表现,因为那没有什么区别,改变它就改变了数据库中的数据。实体Bean是对应到数据库中的视图,对象和数据的同步是由EJB容器完成的(调用ejbLoad和ejbStore方法)。容器在实体Bean挂起前调用ejbStore方法,在实体Bean激活之后调用ejbLoad方法。
如何保持实体Bean? 1.可以手工完成持久化操作,也就是说,可以编写代码将内存中的字段转换成数据库中的字段。这样,就不得不处理一些如存储,导入和在实体Bean中查找数据的持久化方法。而且必须编写持久化API,如JDBC或SQL/J等。实体Bean可以通过JDBC执行SQL INSERT语句在数据库中插入数据。也可以通过SQL DELETE语句从数据库中删除数据。 2.可以让EJB容器完成持久化操作。容器会自动生成数据库访问代码,执行SQL语句。利用容器管理的持久,我们就可以不用编写JDBC代码了。解放了:)
实体Bean是怎样创建和删除的? 在EJB中,客户端是不能直接调用Bean的。它们通过调用EJB对象来执行操作。如下:
客户端在EJB对象或Home对象上调用remove方法删除实体Bean数据。ejbRemove()方法并不是将实体Bean从内存中删除,只是删除数据库中的数据。因为可以重复利用这个Bean实例操作不同的数据库数据。ejbRemove方法是每个实体Bean必须的方法,它不接收参数。如果客户端终止连接,并不会调用ejbRemove方法,因为一个实体Bean的存在时间比客户端会话时间要长。实体Bean是可以被查找的。利用定位器方法。
实体Bean的上下文 所有的企业Bean都有一个上下文(Context)对象来识别Bean所处的环境。这些上下文中包括EJB容器设置的环境信息。Bean可以访问这个上下文得到信息。实体Bean上下文中的getEJBLocalObject()方法可以得到当前客户端与实体Bean关联的EJB对象(容器产生的EJB对象)。getEJBObject()方法可以得到EJB本地对象。最重要的方法是getPrimaryKey(),它可以得到实体Bean实例的主键。主键可以唯一标识实体Bean实例,因为两个实体Bean数据库中的数据是不可能有相同的主键的。确定实例关联哪个数据库数据时就可以调用该方法。当实体Bean挂起一段时间被激活后,实体Bean实例必须对上下文对象执行一个getPrimaryKey()方法调用来确定它将要映射处理的数据库中的哪些数据。