总 揽
      一、Server方组件结构
      EJB是一种Server方的组件结构,它可以非常简单的开发基于java的企业级的分布式对象应用。使用EJB可以开发出易升级的、可靠的、安全的应用程序,而不用独立开发复杂的分布式对象框架;EJB可以迅速开发服务方应用程序,快速建立基于java的服务方组件。EJB被设计用来实现企业中间件服务的可移植和可重用性。

      如果你对企业计算很陌生,这些概念对你很有用,EJB是个很复杂的主题,应该被充分的解释。在这一章我们讨论有关EJB的主要概念。首先,我们先讨论开发企业级软件是为了干什么?为什么说像EJB的预包装的分布式对象体系可以简化你的生活?在讨论中,你将会对服务端的组件结构有宏观的了解。


      服务端组件结构的需要
      我们必须首先了解开发者在建立和配置服务端环境下的组件时通常需要什么?同时,我们将解决围绕服务端开发所出现的问题,将看到建立一套像EJB标准体系结构的必要性。


      软件组件??一个软件组件是一段代码,它用来实现一系列定义好的接口。组件不是完整的
      应用程序??它们不能被独立运行。更贴切的说,它们是看作是许多大型问题分割成的小问题。
      软件组件的思想非常有用。公司可以买来定义好的可用来解决某一问题的模块,将它和其他组件一起编译用以解决大型问题。
      组件结构??为了使组件开发过程更加容易,需要为建立、管理、维持组件建立规范。
      开发组件的开发工具??建立组件时,应该允许开发者集中精力在组件的背后开发核心逻辑,使开发者不需要考虑太多的标准细节问题,从而快速开发应用程序。例如IDE:Symantec的Visual
      Cafe,IBM的VisualAge for Java,Inprise的Jbuilder 2,这些工具可以帮助你快速的建立和调试组件。
      管理配置好的组件容器??组件容器为你的组件运行提供了一个运行时环境。同时也提供了一套供大多数组件使用的通用服务。
      配置和维持组件工具??从组件提供商购买了组件后,还需要有一套工具来帮助配置和维持这些组件。

      Java:完美实现组件结构
      对于成功运用在解决商业问题的一个组件,无论是组件开发商还是使用组件的客户都必须遵守调用组件方法的语法和语义。开发商出版有关调用组件的规范,客户代码必须遵守它们。

      为了防止当开发商提供了一个新版本的组件,或更改了组件规范,客户方就必须重新编写或编译它们的代码,因此面向对象设计提出了一种新的设计方法:通过从组件的实现中分离出接口。

      组件接口
      组件逻辑
      为了使接口/实现分离这种方式变得更有效,开发者必须为组件接口写客户代码(这被称为基于接口的程序设计),通过这种分离,可以改变服务方逻辑而不用更改客户方代码。


      Java中的组件结构
      现在让我们了解什么是组件结构,看看在Java世界里存在那些组件结构。首先,你应该了解什么是JavaBeans,JavaBeans组件是小的应用程序块,可以使用JavaBean去集合成大型的组件,从而编译完整的应用程序。然而,你不能配置一个JavaBean,因为一个JavaBean不是一个完全的应用程序。JavaBean可以帮助你构建更大的可配置的软件。因为不需要配置,JavaBean不需要运行时环境,也不需要容器来对它进行实例化、破坏、提供其他服务的操作。应用程序本身是由许多JavaBean构成的。

      相比较,EJB标准定义了一个组件结构来配置组件,被称为企业级的Beans。企业级的Beans是比较大的、粗糙的被用来配置的应用程序组件。他们能被破坏,也能被用来和其他组件组合成更大的应用程序系统。可配置组件在容器内被配置,容器提供了对组件的运行时服务。例如实例化。

      企业级Beans和两种其他的Java组件十分相似:applets和servlets。Applets可以在Web页中配置,浏览器的Appletviewer为其提供了运行时的容器。Servlets可以在Web
      Server中被配置,Webserver的servlet
      engine为提供运行时的容器。企业级Beans可以在应用程序服务器中被配置,应用服务器为其提供了运行时的容器。
      它们三者之间真正的不同是每个组件类型可以延伸的域大小。
      Applets是轻便Java程序,它能够被任意的下载和运行。例如它可以从Web Server中下载到浏览器。
      Servlets是可以被用来延伸Web
      server功能的网络组件。它是面向请求/回答的,从许多客户端获得请求,再给它们发应答。这样使得它被广泛用于执行Web任务。
      Applets和servlets适用于客户方操作。而企业级Bean不扩展客户端操作,它是服务端组件,执行服务端操作;例如执行复杂运算、执行大量的商业传输。


      服务端所需
      一个完整的组件结构遵循以下方式:
      开发者写可重用组件
      提供商写组件容器:用以给组件提供运行时环境和服务
      提供商提供开发、配置和维持工具。
      这些方式保证了可重用性。

      多层结构
      服务方配置是用来支持用户同时执行并发、安全、可靠、有效的操作的软件。
      服务方配置一般被分成多层。每层实现不同的功能,每层有一个或多个组件。注意:层是个抽象的概念,在物理上它并不存在。下面有个分层的例子:
      代表层:这些容器组件处理用户接口和用户交互。例如,代表层是一个单独的应用程序,可以用VB来写。基于Web配置的代表层可以使用java
      servlets,Java server pages或java applets。
      业务逻辑层:是用来解决业务问题的容器组件的集合。这些组件常被用来处理高执行度的工作,它们常用安全性的语言来写,例如:Java、C。
      数据层:被业务逻辑层用来保持状态的持久性。数据层的中心是一个或多个数据库。
      分层的优点是尽量隔离各层。

      两层结构
      通常,大多数配置是两层结构。将业务逻辑层和另外两层的一个合并:可能和代表层合并。可能和数据层合并。
      代表层和业务逻辑层合并
      如把代表层和业务逻辑层这一层作为客户端,数据层作为服务端,则形成fat客户端,而thin服务端的情况。在两层结构中,客户端应用程序通过数据库桥API与数据层相连。例如:ODBC、JDBC。

      这样的两层结构有以下特征:
      配置代价非常高。数据库驱动必须在每一个客户层上安装和配置。
      数据库驱动交换代价高。转接一个数据库驱动,需要在各个客户端重新安装客户端驱动。
      数据库类型转型代价高。
      业务逻辑移植代价高。
      数据库连接代价高。
      网络性能发挥低。
      将业务逻辑层部分并入数据层
      形成客户端thin,而服务端fat的情况。

      N层结构
      其中将代表层、业务逻辑层、数据层各自为一层。
      特点:
      配置代价低。
      数据库交换代价低。
      业务逻辑移植代价低。
      可以和防火墙结合配置安全部分。
      资源可以被重用。
      每层都有较大的灵活性

      J2EE技术:
      J2EE是中间件服务套件,对于服务端应用的开发者来说,使开发变得更容易。它包含:
      EJB:它定义了怎样去写服务方组件、提供了一个在组件和管理它们的应用服务器之间的标准。EJB是J2EE的核心。
      RMI和RMI-IIOP:RMI??远程过程调用;RMI-IIOP是RMI的扩展,它可以使用IIOP协议,可以被CORBA整合使用。
      JNDI:JNDI用来在网络上区分组件和其他资源的位置。
      JDBC:是联系数据库的桥梁

      推出EJB1.0后的几个月,第一个基于EJB的应用服务BEA’s WebLogic就诞生了。


      二、EJB总揽

      EJB采用divide-and-conquer的方式来实现服务方的计算。事实上,EJB标准定义了6个不同的部分协同工作。每个部分都被作为EJB配置成功的关键。在这儿,我们分别讨论它们的作用。


      Who’s Who in EJB
      在EJB的世界里,业务解决方案被分为四个阶段发展:
      1、
      业务逻辑模块化。ERP、金融、电子商务提供商将产品模块化,集成在可重用EJB组件中,这样就意味着任何有EJB和J2EE知识的人都可以使用这些组件。
      2、 业务组件需要运行在分布式的、企业级配置的多层环境中。这样就需要不同的中间件,推动了中间件的发展。
      3、 应用服务器和可重用组件捆绑。
      4、 完整的系统配置。

      EJB有好的可移植性,它被分为完全不同的6个部分:

      6个部分之间的关系

      The relationship between EJB servers and EJB containers.
      EJB规范定义了完成一个基于EJB组件的分布式应用所需要的六个角色,这六个角色可以由不同的厂商来担当,也可以某个厂商担当多个角色。这六个角色是:
      Enterprise Bean Provider EJB组件开发者,负责编写EJB组件,EJB组件开发者是应用领域的专家。
      Application Assembler 应用组合者,负责将各种EJB组合成一个完整的应用系统。
      Deployer 部署者,负责将包含EJB组件的ejb-jar文件部署到应用服务器中。
      EJB Server Provider EJB服务器提供者,负责实现一些底层的系统服务,如交易管理等。EJB服务器提供者是系统领域的专家。
      EJB Container Provider
      EJB容器提供者,负责提供EJB组件的运行环境,EJB容器提供者和EJB服务器提供者一般是由相同的厂商担当,提供的产品叫应用服务器。
      System Administrator 系统管理员负责为EJB服务器和容器提供一个企业级的计算和网络环境。

      EJB配置的物理部分分为以下几部分:
      EJB Container
      是装载Enterprise Beans及Enterprise
      Beans生存的空间,是Beans在运行时相互联接的接口,Container必须实现与Beans之间的约定,Continer提供者也应该提供配置工具以便能方便地配置Beans,使其适合各种运行环境。

      EJB Server
      主要处理复杂的底层任务,如分布式对象、分布式事务处理的管理、系统OS级的访问、网络、数据库访问等。EJB Server与EJB
      Container之间的合约在EJB 1.0 规范中有详细说明。大多EJB Server提供者也是EJB Container提供者。
      开发Beans
      一、会话Beans介绍
      按功能可把EJB分为两类:Session Beans 与 Entity Beans。
      企业级Bean类
      为了使bean可以在任一容器中工作,bean必须被附在接口中。在EJB中,在enterprise bean
      class中提供了企业级bean组件的实现。这是个简单的遵循接口的java类。
      一个enterprise bean class 包含对组件的实现细节。会话bean的实现不同于实体bean的实现,
      一个Session Beans针对单一的客户完成一次连接或会话,其生存直到客户完成连接与会话,或系统意外中止。当一个新的客户从EJB
      Server访到一个Session Beans时,那么EJB Container创建一个新的Session
      Beans实例,其运行直到会话结束,Session Beans必须实现接口javax.ejb.SessionBean。
      Entity Beans实现接口javax.ejb.EntityBean,其描述了特定数据源中的数据,能长时间存在于EJB
      Container中,不会随系统的意外中止而消失,并且可以让多个客户同时访问。
      EJB规范定义了许多bean类能够实现的标准接口。定义了所有的bean类必须有的方法。容器调用这些方法用来管理bean。
      所有bean类(无论是会话bean还是实体bean)必须实现的最基本的接口是javax.ejb.EnterpriseBean接口。
      public interface javax.ejb.EnterpriseBean extends java.io.Serializable
      {
      }
      值得注意的是:它继承了java.io.Serializable。
      所有的会话bean必须实现javax.ejb.SessionBean,
      所有的实体bean必须实现javax.ejb.EntityBean.
      EJB对象
      当客户想使用enterprise bean
      class的一个实例时,客户不必直接在实际的实例上调用方法,调用过程被EJB容器截取,bean实例被容器中对象所代表。
      1、Enterprise bean
      class不能通过网络直接被调用,我们知道EJB容器可以操纵网络,因此它通过容器将bean包装成可在网络上使用的对象。
      2、通过截取请求,容器可以自动执行许多必要的管理工作。
      3、EJB容器可以跟踪哪个方法被调用,在系统管理者的用户接口上显示其用法等等。
      因此,EJB容器可以看作间接的存在于客户代码和bean之间的层。这个间接的层使用单独的网络对象来表示自己,这个对象称为EJB对象。
      EJB对象作为容器物理的部分;所有的EJB对象都有针对容器特殊要求的代码。因此,容器提供商提供专门工具,用来自动为EJB对象产生类文件。
      远程接口
      我们前面了解到,bean客户调用EJB对象上的方法来代替调用bean,为了执行它,EJB对象必须复制bean类中的每个业务方法。但是,怎样才能使自动产生的EJB对象知道复制了哪个方法呢?这就用到了bean提供者写的一个特殊的接口,这个接口复制所有的与bean类相关联的业务逻辑方法。这个接口被称为远程接口。

      这个接口必须遵循EJB规范的定义,所有的远程接口必须从sun公司提供的通用接口继承而来,即javax.ejb.EJBObject。

      EJB对象
      public interface javax.ejb.EJBObject
      extends java.rmi.Remote
      {
      public abstract javax.ejb.EJBHome getEJBHome()
      throws java.rmi.RemoteException;

      public abstract java.lang.Object getPrimaryKey()
      throws java.rmi.RemoteException;

      public abstract void remove()
      throws java.rmi.RemoteException,
      javax.ejb.RemoveException;

      public abstract javax.ejb.Handle getHandle()
      throws java.rmi.RemoteException;

      public abstract boolean isIdentical(javax.ejb.EJBObject)
      throws java.rmi.RemoteException;
      }
      以上是对于所有EJB对象必须拥有的方法,你不需实现这些方法,这些方法的实现,生成EJB对象时由容器自动生成。

      客户端代码通过调用javax.ejb.EJBObject的方法来和bean协同工作。

      Java RMI和EJB对象
      你应当注意到:java.ejb.EJBObject继承了Java.rmi.Remote。Java.rmi.Remote接口是java远程方法调用(RMI)的一部分,任一个实现java.rmi.Remote的对象都是rmote对象,它可以被另外的java虚拟机所调用。

      被容器提供的EJB对象实现了远程接口,同时也间接实现了java.rmi.Remote,这样也就意味着你的EJB对象是完全符合网络需要的,可以被网络上的其他java虚拟机调用。当然,EJB接口也必须遵守EJB规范。

      EJB远程接口必须遵守java的RMI远程接口规范。例如:错误处理,二者相同。
      远程接口同样也必须遵守java RMI参数传递规范。不是什么都可以通过VM方法调用来在网络上传递,传递的参数必须符合RMI类型。
      EJB也继承了RMI的优点,对于RMI,你正在调用的远程对象的物理地址是不可见的。这个特点同样也适用于EJB。客户代码不必关心正使用的EJB对象是在邻近的计算机上还是从internat传递来的。这样,EJB对象可以和客户端处在同一个java
      VM中。
      EJB保证了本地分布式组件的透明度。这种透明对于多层配置来说是非常必要的。客户端代码是非常容易移植的,不受限于特殊的多层配置。EJB容器可以以最佳化方式在本地执行。


      Home对象
      我们看到,客户端代码处理EJB对象,而从不直接操作beans。那么,客户端如何得到EJB对象的参考呢?
      客户端不直接将EJB对象实例化。因为EJB对象可以存在于不同的机器中。同样的,EJB使本地透明化,因此客户端不知道它的确切所在。
      客户端代码通过EJB对象工厂得到EJB对象的参考。EJB规范里称这种工厂为home对象。它主要起一下作用:
      建立EJB对象。
      找到已经存在的EJB对象。
      删除EJB对象。
      在一些细节方面,EJB对象工厂同EJB对象的特征相同。

      Home接口
      Home接口简单的定义了建立、删除和寻找EJB对象的方法。容器的home对象实现了home接口。
      通常,EJB定义了所有home接口必须支持的许多方法,这些必须的方法被定义在javax.ejb.EJBHome接口上,home接口必须继承Java.ejb.EJBHome接口。

      public interface javax.ejb.EJBHome
      extends java.rmi.Remote
      {
      public abstract EJBMetaData getEJBMetaData()
      throws java.rmi.RemoteException;
      public abstract void remove(Handle handle)
      throws java.rmi.RemoteException
      javax.ejb.RemoveException;
      public abstract void remove(Object primaryKey)
      throws java.rmi.RemoteException,
      javax.ejb.RemoveException;
      }
      javax.ejb.EJBHome接口
      注意javax.ejb.EJBHome继承了java.rmi.Remote,这意味着home接口同样也支持RMI远程对象,传递的参数和RMI也相同。

 

      Home对象

      所有home对象所需的方法
      配置描述符
      配置描述符允许EJB容器向企业级的bean组件提供隐含的中间件服务。隐含的中间件服务是bean可以获得不必将任何中间件API解码,可以自动获得服务的一种服务。


      Bean的特殊属性
      最后,你还需要有一个基于java的bean的属性文件。Bean在运行时读这些属性,这些属性在使用bean函数时会被用到。

      Ejb-jar文件
      一旦生成bean的类、home接口、远程接口、配置描述符和bean的属性,我们就可以把它们打包成一个实体。这个实体称作Ejb-jar文件。这是个压缩文件。


      建立Ejb-jar文件

      什么是会话bean
      一个Session Beans针对单一的客户完成一次连接或会话,其生存直到客户完成连接与会话,或系统意外中止。Session
      Beans必须实现接口javax.ejb.SessionBean。

      会话bean的生存期
      会话bean和实体bean的主要不同是它们的生存期的长短。会话bean的生存期短。与客户的会话时间相当。在与客户连接端开时,EJB容器会破坏会话bean。

      相反,实体bean可以存活相当长的时间,实体bean是永久存取的一部分,例如:数据库。
      会话bean不能保存永久的存储数据,但是,它可以进行数据库操作。
      所有的会话bean都需要管理callback方法,容器定时的调用它,用来对bean的重要事件发出警告。这个方法仅能被容器调用。

      Conversational versus Nonconversational Session Beans

      如何写会话Bean
      写会话bean的类,必须实现javax.ejb.SessionBean接口
      public interface javax.ejb.SessionBean
      extends javax.ejb.EnterpriseBean
      {
      public abstract void setSessionContext(SessionContext ctx)
      throws java.rmi.RemoteException;

      public abstract void ejbPassivate()
      throws java.rmi.RemoteException;

      public abstract void ejbActivate()
      throws java.rmi.RemoteException;

      public abstract void ejbRemove()
      throws java.rmi.RemoteException;
      }
      会话bean和实体bean都继承了javax.ejb.EnterpriseBean接口
      让我们详细看看接口中的各种方法:
      setSessionContext(SessionContext ctx)
      容器调用这个方法来通过会话上下文与bean连接。Bean可以通过会话上下文向容器查询当前事物的状态和当前的安全状态等。
      import javax.ejb.*;
      public class MyBean implements SessionBean {
      private SessionContext ctx;
      public void setSessionContext(SessionContext ctx) {
      this.ctx = ctx;
      }
      ...
      }

      ejbCreate(…)
      用来初始化你的会话bean,可以定义多个不同参数的ejbCreate()方法来用不同的方法初始化bean。
      import javax.ejb.*;
      public class MyBean implements SessionBean {
      private int memberVariable;
      public void ejbCreate(int initialValue) {
      this.memberVariable = initialValue;
      }
      ...
      }
      ejbCreate()方法是容器可以调用的callback方法,客户端代码不能调用它,因为客户端不能直接处理beans??他们必须通过容器,但是客户端必须采用某种方法向ejbCreate方法传递参数,客户端提供初始化参数。Home接口是客户端用来初始化调用的接口工厂。你必须在home接口中复制每一个ejbCreate()方法,例如:如果在bean类中你有下面的ejbCreate方法

      public void ejbCreate(int i) throws ...
      那么你必须在你的home接口里有下面的create()方法
      public void create(int i) throws ...
      客户端调用home接口中的create()方法,将参数传递给ejbCreate()。
      EjbPassivate()
      如果出现太多的实例bean,EJB容器可以将它们中的一些钝化,将它们写到临时的存出空间例如数据库或文件系统。容器释放它们所申请的空间。
      import javax.ejb.*;
      public class MyBean implements SessionBean {
      public void ejbPassivate() {
      <close socket connections, etc...>
      }
      ...
      }
      ejbActivate()
      当客户需要使用被钝化的bean时,容器将被钝化的bean重新导入内存,激活它们。
      Bean又被导致内存,这时需要重新得到bean所需要的资源。
      import javax.ejb.*;
      public class MyBean implements SessionBean {
      public void ejbActivate() {
      <open socket connections, etc...>
      }
      ...
      }
      ejbRemove()
      当容器将会话bean实例remove掉时,调用此方法。所有的bean都有这种方法,它没有参数,它将释放所有以分配的资源。
      import javax.ejb.*;
      public class MyBean implements SessionBean {
      public void ejbRemove() {
      <prepare for destruction>
      }
      ...
      }
      容器可以在任何时候调用ejbRemove()方法,但如果遇到异常,则有可能禁止容器调用此方法。

      业务方法
      应该定义一些解决业务问题的方法:例如:
      import javax.ejb.*;
      public class MyBean implements SessionBean {
      public int add(int i, int j) {
      return (i + j);
      }
      ...
      }
      因为客户端要调用这些方法,因此,你必须在bean的远程接口中列出这些方法。

      如何调用会话beans
      我们现在来看客户方,客户方要通过使用一个或多个bean解决一些现实的问题。
      有两种不同的客户方:
      Java RMI-based clients:这种客户方通过使用JNDI定位对象,使用JTA控制事务。
      CORBA客户方:客户方也可以使用CORBA标准来写。CORBA客户方使用CORBA名字服务(COS
      Naming)在网络上定位对象,使用CORBA的OTS控制事务。
      无论是用CORBA还是RMI,典型的客户端代码都必须实现:
      1、 定位Home接口
      2、 使用Home接口建立EJB对象
      3、在EJB对象上调用业务方法
      4、清除EJB对象
      定位Home接口
      客户端使用JNDL定位Home对象。
      J2EE中JNDL的作用
      J2EE的目标之一是使应用程序实现“write once,run
      anywhere”。任何的运行在企业级配置的java代码在多层结构中应该是不受约束的。因此必须实现定位的透明化。
      J2EE通过使用JNDL来实现定位的透明化。已有目录服务的产品如Netscape的Directory Server,微软的Active
      Directory,IBM的Lotus Notes。
      通常我们使用目录服务存储用户名、密码、机器位置、打印机位置等等。J2EE扩展目录服务存储资源的本地信息,这些资源也可以是Home对象、企业级bean的环境属性、数据库驱动、信息服务驱动和其他资源。使用目录服务,在些应用程序代码时可以不必关心确切的机器名字和机器地址,这样保证了代码的可移植性。无论资源在何处,都不需要重新编译代码。

      JNDL通过为本地用户、机器、网络、对象和服务提供一个标准接口向企业级配置中增加值。
      为了在J2EE中定位资源,必须实现以下两步:
      1、 用配置描述符中的“绰号”关联资源,J2EE将向资源绑定绰号。
      2、 资源的客户端使用JNDL中的绰号定位资源。

      怎样使用JNDL定位Home对象
      客户端不必关心Home对象在网络的什么地方。JNDL为Home对象提供绰号来定位Home对象。通过绰号可以得到Home对象的参考。
      具体点,客户端代码必须执行以下几步来通过JNDL得到参考。
      1、 建立环境。必须配置将要使用目录服务,包括为验证身份所需的用户名、密码。
      2、 建立初始的上下文。初始上下文是连接目录结构的本地出发点。通过初始上下文得到设定的环境属性。
      3、 得到Home对象。执行lookup()操作,返回一个RMI远程对象。

      得到Home对象的参考
      /*
      * Get System properties for JNDI initialization
      */
      Properties props = System.getProperties();
      /*
      * Form an initial context
      */
      Context ctx = new InitialContext(props);
      /*
      * Get a reference to the home object - the
      * factory for EJB objects
      */
      MyHome home = (MyHome) ctx.lookup("MyHome");
      建立EJB对象
      得到Home对象以后,可以将Home对象作为建立EJB对象的工厂。调用create()方法建立EJB对象。
      MyRemoteInterface ejbObject = home.create();
      无参数是因为无状态beans不需要初始参数。
      调用方法
      客户端有了EJB对象以后就可以通过它来调用方法。当客户端在EJB对象上调用方法时,EJB对象必须选择一个bean实例来应答。EJB对象可以建立一个新的实例或是重用已经存在的实例。

      ejbObject.add();
      破坏EJB对象
      调用EJB或Home对象上的remove()方法来破坏EJB对象。
      ejbObject.remove();
      无状态会话bean基础
      无状态会话bean是可以模仿业务过程的组件,它可以在单独的方法调用中被执行。Stateless Session
      Bean不能够维持一个调用客户的状态,在一个方法调用中,Stateless Session Bean
      可以维持调用客户的状态,当方法执行完,状态不会被保持。在调用完成后,Stateless Session
      Bean被立即释放到缓冲池中,所以Stateless Session Bean具有很好的伸缩性,可以支持大量用户的调用。
      无状态会话beans的特点
      没有对话状态
      无状态会话bean可以拥有内部状态,它们的状态不能为特殊的客户端定制。这意味着所有的无状态bean对于客户端是无差别的,客户端也不能分离它们。客户端必须将所有的必需的客户端数据作为业务逻辑方法的参数传给无状态bean,无状态bean可以从外部资源(例如数据库)获得所需的数据。

      初始化无状态bean只有一种方法
      我们知道会话bean的初始化调用ejbCreate()方法,因为无状态会话bean不能够在方法调用之间保留状态,因此它也不能在客户端给ejbCreate()调用传递数据以后保留状态。调用不带参数的ejbCreate()或create()。

      容器可以聚集和重用无状态会话Bean
      构建“Hello,World!”远程接口
      package com.wiley.compBooks.roman.session.helloworld;
      import javax.ejb.*;
      import java.rmi.RemoteException;
      import java.rmi.Remote;
      /**
      * This is the HelloBean remote interface.
      *
      * This interface is what clients operate on when
      * they interact with EJB objects. The container
      * vendor will implement this interface; the
      * implemented object is the EJB object, which
      * delegates invocations to the actual bean.
      */
      public interface Hello extends EJBObject {
      /**
      * The one method - hello - returns a greeting to the client.
      */
      public String hello() throws java.rmi.RemoteException;
      }
      Source 4.1 Hello.java.
      Hello接口继承了EJBObject接口,EJBObject继承Remote接口,因此hello可以抛出rmi异常。
      下面建立bean,实现业务方法:hello()。
      他实现了javax.ejb.SessionBean接口
      package com.wiley.compBooks.roman.session.helloworld;
      import javax.ejb.*;
      /**
      * Demonstration stateless session bean.
      */
      public class HelloBean implements SessionBean {
      //
      // EJB-required methods
      //
      public void ejbCreate() {
      System.out.println("ejbCreate()");
      }
      public void ejbRemove() {
      System.out.println("ejbRemove()");
      }
      public void ejbActivate() {
      System.out.println("ejbActivate()");
      }
      public void ejbPassivate() {
      System.out.println("ejbPassivate()");
      }
      public void setSessionContext(SessionContext ctx) {
      System.out.println("setSessionContext()");
      }
      //
      // Business methods
      //
      public String hello() {
      System.out.println("hello()");
      return "Hello, World!";
      }
      }
      Source 4.2 HelloBean.java
      注意:不需要实现自己的远程接口,初始化方法不带参数。破坏bean时,使用比较简单的ejbRemove()方法。ejbActivate()
      和ejbPassivate()方法不需应用在无状态会话bean,因此,这两个方法为空。
      建立“Hello,World!”Home接口
      Home接口继承了javax.ejb.EJBHome。Home接口为EJB对象扩展了一个不带参数的方法??create()方法。
      package com.wiley.compBooks.roman.session.helloworld;
      import javax.ejb.*;
      import java.rmi.RemoteException;
      /**
      * This is the home interface for HelloBean. This interface
      * is implemented by the EJB Server´s glue-code tools - the
      * implemented object is called the Home Object and serves
      * as a factory for EJB Objects.
      *
      * One create() method is in this Home Interface, which
      * corresponds to the ejbCreate() method in HelloBean.
      */
      public interface HelloHome extends EJBHome {
      /*
      * This method creates the EJB Object.
      *
      * @return The newly created EJB Object.
      */
      Hello create() throws RemoteException, CreateException;
      }
      creat方法抛出了a java.rmi.RemoteException和aavax.ejb.CreateException.异常。
      写配置描述符
      在EJB1.0中,配置描述符是作为文件存储在磁盘上的java对象。在EJB1.1种,配置描述符是一个XML文档。EJB容器或IDE环境应该提供生成配置描述符的工具。

      配置描述符的设置
      bean home的名字
      企业级bean类名
      home接口类名
      远程接口类名
      Re-entrant
      状态或无状态
      会话时间

 

      HelloBean的配置描述符
      环境属性
      bean通过使用此信息来适应不同的特殊环境。
      Ejb-jar文件
      我们需要将我们所需要的文件打包成Ejb-jar文件。
      企业级的bean
      远程接口
      home接口
      配置描述符,包括属性
      以上这些必须被包含进Ejb-jar文件。在EJB1.0中,jar文件理有一个文本文件的列表。它表示jar的详细信息。它用来鉴别哪个企业bean在Ejb-jar文件。在EJB1.1中,XML文件包含了所有的必要信息。

      生成Ejb-jar文件
      jar cmf ..\manifest HelloWorld.jar *
      配置bean
      最后,我们还需要在Ejb容器中配置bean。常常执行一下步骤:
      Ejb-jar文件的检验
      容器工具来产生EJB对象和home对象
      容器工具来生成RMI所需的stubs和skeletons
      写无状态bean的客户代码
      package com.wiley.compBooks.roman.session.helloworld;
      import javax.ejb.*;
      import javax.naming.*;
      import java.rmi.*;
      import java.util.Properties;
      /**
      * This class is an example of client code that invokes
      * methods on a simple stateless session bean.
      */
      public class HelloClient {
      public static void main(String[] args) {
      try {
      /*
      * Get System properties for JNDI initialization
      */
      Properties props = System.getProperties();
      /*
      * Form an initial context
      */
      Context ctx = new InitialContext(props);
      /*
      * Get a reference to the home object
      * (the factory for EJB objects)
      */
      HelloHome home = (HelloHome) ctx.lookup("HelloHome");
      /*
      * Use the factory to create the EJB Object
      */
      Hello hello = home.create();
      /*
      * Call the hello() method, and print it
      */
      System.out.println(hello.hello());
      /*
      * Done with EJB Object, so remove it
      */
      hello.remove();
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      }
      客户端代码执行了一下任务:
      定位home接口
      使用home接口建立EJB对象
      调用EJB对象上的hello()
      移走EJB对象
      运行
      首先运行应用服务器。对于BEA的WebLogic,执行
      t3server
      客户端执行:
      java -Djava.naming.factory.initial=
      weblogic.jndi.TengahInitialContextFactory
      -Djava.naming.provider.url=
      t3://localhost:7001
      com.wiley.compBooks.roman.session.helloworld.HelloClient
      服务端输出:
      setSessionContext()
      ejbCreate()
      hello()
      ejbRemove()
      客户端输出:
      Hello, World!

 


      状态会话Bean基础

      Stateful Session Bean可以一对一的维持某个调用客户的状态,并且在不同的方法调用中维持这个状态,
      由于对于每一个并发用户,必须有一个对应的Stateful Session Bean,为了提高系统的效率,Stateful Session
      Bean可以在一定的客户空闲时间后被写入二级存储设备(如硬盘),在客户发出新的调用请求后,再从二级存储
      设备恢复到内存中。但是在多用户下,Stateless Session Bean运行效率高于Stateful Session Bean。
      javax.ejb.EnterpriseBean接口继承了java.io.Serializable,用以实现写入读出操作。
      当EJB容器调用ejbPassivate()方法钝化了bean之后,就可以把它写入二级存储设备,然后容器调用ejbActivate()方法激活bean,把它从二级存储设备中读出。


      状态bean的钝化过程
      计数bean的远程接口
      远程接口定义了一个业务方法count(),它将在企业bean类中实现。


      激活状态bean
      package com.wiley.compBooks.roman.session.count;
      import javax.ejb.*;
      import java.rmi.RemoteException;
      /**
      * These are CountBean´s business logic methods.
      *
      * This interface is what clients operate on when they
      * interact with EJB objects. The container vendor will
      implement this interface; the implemented object is
      * the EJB Object, which delegates invocations to the
      * actual bean.
      */
      public interface Count extends EJBObject {
      /**
      * Increments the int stored as conversational state
      */
      public int count() throws RemoteException;
      }
      Source Count.java

      package com.wiley.compBooks.roman.session.count;
      import javax.ejb.*;
      /**
      * Demonstration Stateful Session Bean. This bean is
      * initialized to some integer value and has a business
      * method that increments the value.
      *
      * This example shows the basics of how to write a stateful
      * session bean and how passivation/activation works.
      */
      public class CountBean implements SessionBean {
      private SessionContext ctx;
      // The current counter is our conversational state.
      public int val;
      //
      // Business methods
      //
      /**
      * Counts up
      */
      public int count() {
      System.out.println("count()");
      return ++val;
      }
      //
      // EJB-required methods
      //
      public void ejbCreate(int val) throws CreateException {
      this.val = val;
      System.out.println("ejbCreate()");
      }
      public void ejbRemove() {
      System.out.println("ejbRemove()");
      }
      public void ejbActivate() {
      System.out.println("ejbActivate()");
      }
      public void ejbPassivate() {
      System.out.println("ejbPassivate()");
      }
      public void setSessionContext(SessionContext ctx) {
      }
      }
      Source CountBean.java
      Bean实现了javax.ejb.SessionBean。所以,它必须定义所有SessionBean定义的方法。
      OjbCreate()初始化带了val的参数。它将作为counter的初始状态。在钝化和激活bean的过程中,val变量被保护。

      计数bean的home接口
      package com.wiley.compBooks.roman.session.count;
      import javax.ejb.*;
      import java.rmi.RemoteException;
      /**
      * This is the home interface for CountBean. This interface
      * is implemented by the EJB Server´s glue-code tools - the
      * implemented object is called the Home Object and serves
      * as a factory for EJB Objects.
      *
      * One create() method is in this Home Interface, which
      * corresponds to the ejbCreate() method in the CountBean file.
      */
      public interface CountHome extends EJBHome {
      /*
      * This method creates the EJB Object.
      *
      * @param val Value to initialize counter to
      *
      * @return The newly created EJB Object.
      */
      Count create(int val) throws RemoteException, CreateException;
      }
      Source CountHome.java.
      计数bean的配置描述符

      计数bean的配置描述符
      计数bean的环境属性
      生成计数bean的Ejb-jar文件
      计数bean的客户端代码
      package com.wiley.compBooks.roman.session.count;
      import javax.ejb.*;
      import javax.naming.*;
      import java.util.Properties;
      /**
      * This class is a simple example of client code that invokes
      * methods on a simple Stateless Enterprise Bean.
      *
      * We create 3 EJB Objects in this example, but we allow
      * the container to have only 2 in memory. This illustrates how
      * beans are passivated to storage.
      */
      public class CountClient {
      public static void main(String[] args) {
      try {
      /*
      * Get System properties for JNDI initialization
      */
      Properties props = System.getProperties();
      /*
      * Get a reference to the Home Object - the
      * factory for EJB Objects
      */
      Source CountClient.java
      1、需要JNDL初始化上下文
      2、使用JNDL定位home接口
      3、使用home对象建立3个不同的计数EJB对象,因此也就和三个不同的客户端建立了会话
      4、配置描述符限制了同时只能有两个bean工作,因此3个bean中一定有钝化的。在调用ejbPassivate()时,打印一条信息。
      5、在每个EJB对象上调用count()方法,调用ejbActivate()方法激活bean,该方法打印一条信息。
      6、最后所有的EJB对象被删除。
      package com.wiley.compBooks.roman.session.count;
      import javax.ejb.*;
      import javax.naming.*;
      import java.util.Properties;
      /**
      * This class is a simple example of client code that invokes
      * methods on a simple Stateless Enterprise Bean.
      *
      * We create 3 EJB Objects in this example, but we allow
      * the container to have only 2 in memory. This illustrates how
      * beans are passivated to storage.
      */
      public class CountClient {
      public static void main(String[] args) {
      try {
      /*
      * Get System properties for JNDI initialization
      */
      Properties props = System.getProperties();
      /*
      * Get a reference to the Home Object - the
      * factory for EJB Objects
      */
      Context ctx = new InitialContext(props);
      CountHome home = (CountHome) ctx.lookup("CountHome");
      /*
      * An array to hold 3 Count EJB Objects
      */
      Count count[] = new Count[3];
      int countVal = 0;
      /*
      * Create and count() on each member of array
      */
      System.out.println("Instantiating beans...");
      for (int i=0; i < 3; i++) {
      /*
      * Create an EJB Object and initialize
      * it to the current count value.
      */
      count[i] = home.create(countVal);
      /*
      * Add 1 and print
      */
      countVal = count[i].count();
      System.out.println(countVal);
      /*
      * Sleep for 1/2 second
      */
      Thread.sleep(500);
      }
      /*
      * Let´s call count() on each EJB Object to
      * make sure the beans were passivated and
      * activated properly.
      */
      System.out.println("Calling count() on beans...");
      for (int i=0; i < 3; i++) {
      /*
      * Add 1 and print
      */
      countVal = count[i].count();
      System.out.println(countVal);
      /*
      * Sleep for 1/2 second
      */
      Thread.sleep(500);
      }
      /*
      * Done with EJB Objects, so remove them
      */
      for (int i=0; i < 3; i++) {
      count[i].remove();
      }
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      }
      Source CountClient.java
      运行客户端:
      对于BEA的WebLogic,执行:
      java -Djava.naming.factory.initial=
      weblogic.jndi.TengahInitialContextFactory
      -Djava.naming.provider.url=
      t3://localhost:7001
      com.wiley.compBooks.roman.session.count.CountClient
      客户端输出:
      Instantiating beans...
      1
      2
      3
      Calling count() on beans...
      2
      3
      4
      服务端输出:
      ejbCreate()
      count()
      ejbCreate()
      count()
      ejbCreate()
      ejbPassivate()
      count()
      ejbPassivate()
      ejbActivate()
      count()
      ejbPassivate()
      ejbActivate()
      count()
      ejbPassivate()
      ejbActivate()
      count()
      ejbPassivate()
      ejbActivate()
      ejbRemove()
      ejbActivate()
      ejbRemove()
      ejbRemove()

      为Beans增加功能

      EJB 上下文:通往容器的门户
      存在如下信息:
      1、关于bean的home对象和EJB对象的信息
      2、bean的当前事务信息。
      3、 对于客户授权的安全信息。Bean可以通过查询环境决定客户执行操作所需要的安全层次。
      4、 bean的环境属性。
      容器将所有这些信息保存在一个称为EJB context
      object的对象里。EJB上下文作为容器的物理部分,可以被bean访问。这些访问可以让bean得到当前的状态和改变当前的状态。
      上下文可以在bean的生命期中被更改。
      EJB1.0 javax.ejb.EJBContext接口:
      public interface javax.ejb.EJBContext
      {
      public javax.ejb.EJBHome getEJBHome();
      public java.util.Properties getEnvironment();
      public java.security.Identity getCallerIdentity();
      public boolean isCallerInRole(java.security.Identity);
      public javax.jts.UserTransaction getUserTransaction();
      public void setRollbackOnly();
      public boolean getRollbackOnly();
      }
      会话bean的上下文
      上下文根据bean的不同分为:会话上下文和实体上下文。它们分别被用于会话bean和实体bean

      javax.ejb.EJBContext
      public interface javax.ejb.SessionContext
      extends javax.ejb.EJBContext
      {
      public javax.ejb.EJBObject getEJBObject();
      }
      注意:SessionContext接口继承了EJBContext接口,在EJBContext中定义的方法提供了对会话bean的存取路径。
      对于会话bean,调用setSessionContext,这个方法在javax.ejb.SessionBean接口中被定义。对于实体bean,调用setEntityContext。

      SessionContext.getEJBObject()
      在EJB中,beans可以作为其他bean的客户端。如果一个bean需要调用另外的bean,getEJBObject()方法则是必需的。在java中,对象可以使用this关键字保存自身的参考。在EJB中,bean不能使用this关键字给其他bean传递对象,这是因为所有的客户调用bean上的方法都是间接调用bean的EJB对象。Bean可以使用this关键字将自己传给EJB对象。

      了解EJB的安全性
      首先,客户端必须是可被鉴别的。
      其次,客户端必须是已经授权的。
      第一步:鉴别
      不同的EJB容器拥有不同的鉴别客户端的方法。例如:BEA的WebLogic中,当不同客户端代码使用JNDL定位Home对象时,提供不同的用户名和密码。

      Properties props = System.getProperties();
      props.put(Context.SECURITY_PRINCIPAL, "EmployeeA");
      props.put(Context.SECURITY_CREDENTIALS, "myPassword1");
      Context ctx = new InitialContext(props);
      // Use the initial context to lookup home objects...
      EJB没有制定如何鉴别的规范,因此这样就影响了可移植性。要了解这方面,查看各类容器的文档。
      当运行这段代码时,应用服务器将验证你的用户名和密码,这是应用服务器规范。许多应用服务器允许在属性文件里设置用户名和密码。这个文件将在运行时由应用服务器读。

      高级点的服务器支持已经存在的验证系统的整合。例如将用户名和密码列表存储在LDAP服务器中。
      第二步:授权
      只有经过授权的客户端才可以调用bean中的方法。EJB中有两种验证授权的方法:declaratively和programmatically。即:由容器执行所有的授权检验、在程序中进行授权检查。

      Declarative授权检查时,要在配置描述符中声明bean的授权需要。例如使用BEA的WebLogic服务器的配置描述符的例子:
      (accessControlEntries
      submitPurchaseOrder [employees]
      approvePurchaseOrder [managers]
      DEFAULT [administrators]
      ); end accessControlEntries
      容器将在运行时自动的执行安全检查。抛会出java.lang.SecurityException异常。
      Programmatic授权检查,必须查询EJB上下文得到当前客户端的授权信息。由两种方法调用CallerInRole(Identity
      role)和getCallerIdentity()。
      isCallerInRole()
      import java.security.Identity;
      ...
      public class MyBean implements SessionBean {
      private SessionContext ctx;
      ...
      public void foo() {
      Identity id = new MyIdentity("administrators");
      if (ctx.isCallerInRole(id)) {
      System.out.println("An admin called me");
      return;
      }
      System.out.println("A non-admin called me");
      }
      }

      import java.security.Identity;
      public class MyIdentity extends Identity {
      public MyIdentity(String id) {
      super(id);
      }
      }
      getCallerIdentity()
      import java.security.Identity;
      ...
      public class MyBean implements SessionBean {
      private SessionContext ctx;
      ...
      public void bar() {
      Identity id = ctx.getCallerIdentity();
      String name = id.getName();
      System.out.println("The caller´s name is " + name);
      }
      }
      了解EJB对象的操作
      许多EJB应用程序需要客户端有与bean断开的能力,还要有与bean重建连接的能力。EJB提供了EJB object
      handles。EJB对象操作对于EJB对象是一个长生命期的代理。可以用它来重建与EJB对象的连接,并保证会话状态不被丢失。下面是EJB对象操作的代码

      // First, get the EJB object handle from the EJB object.
      javax.ejb.Handle myHandle = myEJBObject.getHandle();
      // Next, serialize myHandle, and then save it in
      // permanent storage.
      ObjectOutputStream stream = ...;
      stream.writeObject(myHandle);
      // time passes...
      // When we want to use the EJB object again,
      // deserialize the EJB object handle
      ObjectInputStream stream = ...;
      Handle myHandle = (Handle) stream.readObject();
      // Convert the EJB object handle back into an EJB object
      MyRemoteInterface myEJBObject =
      (MyRemoteInterface) myHandle.getEJBObject();
      // Resume calling methods again
      myEJBObject.callMethod();
      例子:The Puzzle Game “Fazuul”(参考原文)