李小e 的 EJB 奮鬥札記(一)Session Bean 的存取模式與介面 by Eric | CodeData
top

李小e 的 EJB 奮鬥札記(一)Session Bean 的存取模式與介面

分享:

前言

  • 高難度與高成本的刻版印象

Enterprise Java Bean(簡稱 EJB)是 Java EE 技術領域中,負責封裝商業邏輯、流程的伺服器端元件,必須佈署在容器(Container)之中,由容器管理並提供所需的執行環境;在早期,開發EJB的程序、設定、佈署、測試與使用上都有較高的技術門檻,甚至在設計不良的情況下還會帶來效能上的瓶頸,所以總是帶給大家高難度與高成本的刻板印象,使得開發系統的相關人員望之卻步。

的確,從 EJB 1.x 到 2.x 的實務開發經驗中,常可以體會到許多令人抓狂的問題,例如:一個EJB元件必須要建立兩個介面(Home、Business 介面)跟一個 EJB 類別的煩瑣程序、又或是在用戶端程式使用 EJB 時,必須先做好與 JNDI 搏鬥的心理準備等,都會讓開發人員聞之色變,而埋下了 EJB 技術在日後未能普及運用的潛在因素。

  • 擺脫包袱,讓 EJB 變簡單

隨著 Java 技術的進步,EJB 從 3.0 開始能使用標記(Annotation)進行開發,讓程式的撰寫與其相關設定能一次到位,省去了早期還要額外撰寫、維護佈署描述檔(Deployment Descriptor)的煩人程序;同時,也運用了 JPA 來取代傳統 Entity Bean 及 EJB QL,對永續保存的資料進行處理;這些大幅度的革新,簡化了 EJB 的學習曲線與開發程序。EJB 3.1 規格中的非同步方法(Asynchronous Methods)、標準化的 EJB JNDI 名稱(Global JNDI)、單一實例的 Session Bean(Singleton Session Bean)及 EJB Lite(EJB 規格簡化版)等,讓 EJB 技術更加多元、容易測試及使用。

在 2013/05/28 所推出的EJB 3.2 新規格,雖不像 3.1 時那般的大刀闊斧,但仍舊承襲了簡化 EJB 的精神,將 Entity Bean及 JAX-RPC 為基礎的 Web Service 端點(End Point)等相關技術移出主要規格外,使 EJB 的規格一分為二:核心(Core)與非必要(Optional)兩種;如此一來,EJB 的容器供應商(Container Provider)就可以選擇是否實作非必要的規格,讓 EJB 的架構及容器更為輕盈、有效率。

  • 重新認識 EJB

EJB 是 Java EE 架構下商業元件的標準規格,因此元件的可移植性與技術的整合性自然不在話下,此外,商業元件必須有高度的再用性及維護性,而 EJB 能讓不同性質的應用程式(Web、Stand-alone、Web Service、另一個 EJB)透過本地(Local)及遠端(Remote)的模式存取、操作,這些特性凸顯出了 EJB 重要的價值與地位。EJB 3.x 的規格歷經了種種的改革,無論在開發或是使用上,都變的既簡單又直觀,筆者希望將自身的學習及實務經驗,透過一系列的文章,讓大家重新認識並使用 EJB。

EJB 的存取模式

EJB 元件可以分為兩大類:負責主要的商業流程與邏輯 Session Bean 及提供非同步訊息處理架構 Message Driven Bean(簡稱 MDB);其中,MDB 是藉由 JMS 的訊息所觸發並執行,不會讓用戶端進行存取,相較之下 Session Bean 可以開放用戶端操作,依特性又可以分為Stateless(SLSB)、Stateful(SFSB)及 Singlton(SSB)三種,在本篇的文章中並不介紹各種 EJB 元件的特性與應用,先針對 Session Bean 的存取模式與介面進行討論。

  • 本地存取模式

Session Bean 元件可以開放讓用戶端進行本地及遠端兩種存取模式;當用戶端與 Session Bean佈署在同一個 JVM 中,稱為本地存取,如下圖:

Local

這種存取模式的用戶端多半為 Web 程式,而我們將 EJB 元件與 Web 程式包裝在同一個企業歸檔檔案(Enterprise Archive File-EAR)中進行佈署,又或者將兩者佈署在一個應用伺服器下的兩個獨立程式。

從 EJB 3.1 開始,因為 EJB Lite 及 Web Profile(Web 程式的常用功能-Java EE 簡化版)等規格的建立,更可以將 EJB 元件直接包裝在 Web 歸檔檔案(Web Archive File-WAR)中,進行佈署及本地存取。本地存取是著重於效能的解決方案,用戶端與 EJB 元件間進行溝通時,會遵循 Java 語言既定規則,以 By Value 方式傳遞基本型別(Primitive),By Reference 的方式傳遞參考型別(Class、Array 等)。

  • 遠端存取模式

相較之下,當用戶端與 EJB 元件佈署在不同的 JVM 中則稱為遠端存取,如下圖:

Remote

在這種存取模式下,無論是基本或參考型別,都會採用 By Value 的方式進行資料傳遞;此外,物件必須進行序列化與反序列化的程序才能在不同 JVM 間進行傳遞,因此遠端存取需要的成本要比本地存取來得高出許多。

從另一個角度去思考,遠端存取模式能將呈現層與商業層的軟體元件,實質分離在不同的硬體設備中,讓複雜、耗時費工的商業邏輯在運算能力及資源較充足的設備上執行,有助於負載的分散與平衡。

開放用戶端存取 Session Bean 元件

容器為了要管理 Session Bean 元件的生命周期及交易、安全、同時存取等相關機制,因此,無論是本地或是遠端的存取模式,都會替 Session Bean 實例建立代理(Proxy)物件,讓用戶端透過代理物件對 Session Bean 擁有透通性(Transparent)的操作,如下圖:

Proxy

我們建立一個簡單的 SLSB,藉此來說明如何將 Session Bean 開放給用戶端存取;在下列程式中,建立一個 POJO 類別及 test() 方法並以 @Stateless 標註:

@Stateless
public class MySimpleBean{
    public String test() {
        return "Kitty";
    }
}
  • 使用 No-Interface 的建立本地存取模式

別懷疑,我們已經建立了一個能實際運作的 SLSB;事實上,從 EJB 3.1 開始,Session Bean 可以不需要實作任何的商業界面,預設就可以開放 EJB 內所有的 public 方法讓用戶端以 No-Interface 的方式進行本地存取。其實,以 @LocalBean 標記 Bean 類別才算是比較正規的做法,程式修改如下:

@LocalBean
@Stateless
public class MySimpleBean{
    public String test() {
        return "Kitty";
    }
}

接著,寫個簡單的 Servlet 作為用戶端,並藉由 @EJB 注入 No-Interface Session Bean 後,就能進行存取:

@WebServlet(name="MySimpleServlet", urlPatterns={"/MySimpleServlet"})
public class MySimpleServlet extends HttpServlet {
    @EJB
    private MySimpleBean mySimpleBean;
    protected void doGet(HttpServletRequest request,HttpServletResponse response){
        PrintWriter out = response.getWriter();
        out.println("<h1>Hello " + mySimpleBean.test() + "</h1>");
    }
}

程式與執行結果如下:
HelloWorld

 

  • 使用 Local Interface 的建立本地存取模式

在 No-Interface(EJB 3.1)規格建立之前,如果要讓 EJB 能提供本地存取,必須建立商業介面並由 Bean 類別實作,實務上常用的做法有以下兩種。

做法(一)建立 POJI(Plain Old Java Interface)商業介面並@Local 標記,程式如下:

@Local
public interface IMySimple{
    public String test() ;
}
@Stateless
public class MySimpleBean implements IMySimple {
    public String test() {
        return "Kitty";
    }
}

對用戶端來說,必須透過 IMySimple 介面存取 EJB 元件,這種做法會使得用戶端與 EJB 相關的API(javax.ejb.Local)產生緊密的依賴關係,程式如下:

@WebServlet(name="MySimpleServlet", urlPatterns={"/MySimpleServlet"})
public class MySimpleServlet extends HttpServlet {
    @EJB
    private IMySimple mySimpleBean;
    protected void doGet(HttpServletRequest request,HttpServletResponse response){
        PrintWriter out = response.getWriter();
        out.println("<h1>Hello " + mySimpleBean.test() + "</h1>");
    }
}

做法(二)建立 POJI(Plain Old Java Interface)商業介面,在 Bean 類別中以 @Local 標記,程式如下:

public interface IMySimple{
    public String test() ;
}
@Local
@Stateless
public class MySimpleBean implements IMySimple {
    public String test() {
        return "Kitty";
    }
}

上述的做法避免了用戶端對 EJB API 的依賴性,但付出了些許的代價-必須使用傳統 JNDI 方式取得 EJB 元件的參考,就程式的一致性來說,無法(不會)再用 @EJB(javax.ejb.EJB)標記進行注入,程式如下:

@WebServlet(name="MySimpleServlet", urlPatterns={"/MySimpleServlet"})
public class MySimpleServlet extends HttpServlet {
    private IMySimple mySimpleBean = lookup();
    private IMySimple lookup(){        
        try {
            IMySimple mySimpleBean;
            String jndi = ".....";        
            InitialContext context = new InitialContext();
            mySimpleBean = (IMySimple)context.lookup(jndi);
            return mySimpleBean;
        } catch (NamingException ex) {
            throw new RuntimeException(ex);
        }        
    }        
    protected void doGet(HttpServletRequest request,HttpServletResponse response){
        PrintWriter out = response.getWriter();
        out.println("<h1>Hello " + mySimpleBean.test() + "</h1>");
    }
}

這種做法能帶來其他的好處-開放給用戶端存取的介面都會集中在 Bean 類別內;換句話說,一旦 Session Bean 開放了複合存取模式與多重介面時(將於本篇中介紹),開發人員就不需要在各個介面中進行註記,使程式在未來更容易維護也具有彈性。

  • No-Interface 與 Local Interface 的抉擇

No-Interface 簡化了 Local Interface 開發 Session Bean 本地存取的步驟,但是,用戶端會與具有實作的 Bean 類別有著高度的耦合,著實不是件好事;甚至,用戶端可以直接產生 Bean 類別的實例使用,如此一來,除了無法獲得容器所提供的執行環境、安全性及相關資源之外,更使得實作變動時會連帶影響到用戶端必須跟著置換 Bean 類別的窘境。相較之下,Local Interface 的用戶端不需要擁有 Bean 類別,也就不會有上述潛在的問題發生。

一般來說,同一個 EJB 程式(模組)通常會包含許多 EJB 元件,此時 EJB 元件間的協同運作就相當適合使用 No-Interface 的方式達成,又或是直接在 Web 程式(模組)中所開發的 EJB 原件(EJB 3.1),由於上述這兩種情況就已經會有 Bean 類別在模組中,因此可以簡化開發程序並獲得容器管理所帶來的好處。對於不同應用程式(模組)的用戶端來說,為了降低與實作(Bean 類別)間的耦合,較適合以 Local Interface 的方式提供存取。

  • 使用 Remote Interface 的建立遠端存取模式

要開放遠端存取 Session Bean 其實很簡單,只要修改"使用 Local Interface 的建立本地存取模式"所描述的兩種做法,將 @Local 改為 @Remote 標記就可以達成,對 Web 用戶端來說一樣可以使用 @EJB 進行注入或傳統的 JNDI 方式取得 Session Bean 的遠端參考,並沒有甚麼不同。

  • 本地與遠端複合存取模式

依據 EJB 規格書中"4.9.7 Session Bean’s Business Interface"章節所描述,無法在商業介面上同時標記 @Local 及 @Remote,當我們需要同時開放本地與遠端存取模式時,就必須建立兩個商業介面,由 Bean 類別同時實作,做法(一)的程式如下:

@Local
public interface IMySimpleLocal{
    public String test() ;
}
@Remote
public interface IMySimpleRemote{
    public String test() ;
}
@Stateless
public class MySimpleBean implements IMySimpleLocal,IMySimpleRemote {
    public String test() {
        return "Kitty";
    }
}

做法(二)的程式如下:

public interface IMySimpleLocal{
    public String test() ;
}
public interface IMySimpleRemote{
    public String test() ;
}
@Stateless
@Local(IMySimpleLocal.class)
@Remote(IMySimpleRemote.class)
public class MySimpleBean implements IMySimpleLocal,IMySimpleRemote {
    public String test() {
        return "Kitty";
    }
}

由於我們不再於介面中進行註記,所以為了能讓容器能在佈署時辨識所開放的本地及遠端介面,因此,必須要在 Bean 類別的 @Local 及 @Remote 標記中額外指定介面的類別。

  • 建立多重存取介面

在剛剛的討論中,無論是本地、遠端或是複合的存取模式,都是採用了單一介面開放用戶端存取,當 Session Bean 要開放不同的商業介面給用戶端時,我們依舊可以採取上述的兩種做法進行,以本地存取為範例,做法(一)的程式如下:

@Local
public interface IMySimpleLocalA{
    public String testA() ;
}
@Local
public interface IMySimpleLocalB{
    public String testB() ;
}
@Stateless
public class MySimpleBean implements IMySimpleLocalA, IMySimpleLocalB {
    public String testA() {
        return "Kitty";
    }
    public String testB() {
        return "Snoopy";
    }
}

或是採用做法(二)的程式如下:

public interface IMySimpleLocalA{
    public String testA() ;
}
public interface IMySimpleLocalB{
    public String testB() ;
}
@Stateless
@Local(value={IMySimpleLocalA.class, IMySimpleLocalB.class})
public class MySimpleBean implements IMySimpleLocalA, IMySimpleLocalB {
    public String testA() {
        return "Kitty";
    }
    public String testB() {
        return "Snoopy";
    }
}

由於要開放兩個存取介面,所以必須再使用 @Local 標記的 value 屬性指定多重介面;從 EJB 3.2 開始,可以省略 value 屬性就能達到多重介面的需求,Bean 類別的程式可以簡化如下:

@Stateless
@Local/*省略value屬性*/
public class MySimpleBean implements IMySimpleLocalA, IMySimpleLocalB {
    public String testA() {
        return "Kitty";
    }
    public String testB() {
        return "Snoopy";
    }
}

總結

EJB 元件就開發的角度來說,雖然越來越簡單,但要上手仍有些門檻,希望能藉由一系列的文章協助大家釐清觀念、突破學習上的瓶頸。最後,整理出一張簡單的表格,讓大家對於本篇文章中所介紹的主題,有更清楚的概念:

2013-07-24_142318

參考資料

  1. EJB 3.2 – JSR 345
  2. Defining EJB 3.1 Views (Local, Remote, No-Interface)
分享:
按讚!加入 CodeData Facebook 粉絲群

相關文章

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

關於作者

李小e,熱愛 Java 技術,熟悉 EJB、Spring、Struts、Hibernate、JPA、Design Pattern 等技術,目前服務於巨匠電腦與德義資訊,具有十多年的 Java EE 的教學與實務經驗。

熱門論壇文章

熱門技術文章