總 攬
      一、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”(參考原文)