1. 程式人生 > >EJB3與EJB2架構對比

EJB3與EJB2架構對比

本文從實戰角度比較EJB2和EJB3的異同,通過深入剖析揭示EJB3.0的真相,EJB3.0真是帶來簡化?還是一種表象上的簡化?EJB3.0真的變得輕量了,還是披著羊皮的狼?

EJB程式設計模型的簡化

  首先,EJB3簡化的一個主要表現是:在EJB3中,一個EJB不再象EJB2中需要兩個介面一個Bean實現類,雖然我們以前使用JBuilder這樣視覺化開發工具自動生成了EJB2的這三個類,好像不覺得複雜,但是當EJB個數增加時,就顯得累贅了。

  簡化後的EJB3的sessionBean依靠annotations元註釋來定義SessionBean的型別,也就是說,EJB2中的SessionBean型別區分在EJB3繼續繼承,只不過書寫程式碼的方式不同而已,例如下程式碼使用@Stateless表示一個無狀態Bean。

package example;

@Stateless
public class TestSessionBean implements TestSessionLocal{
public void xxxx(){

    System.out.println("hello");  

  }

}

  上述Session Bean中沒有了EJB2中ejbCreate等多餘方法,這樣TestSessionBean很象一個普通JavaBeans了。是不是簡單?先別急,我們需要接著看看這個TestSessionBean是如何呼叫?

  在EJB2中,一個EJB物件的呼叫需要經過兩個步驟:JNDI尋找和工廠建立,如下例:

Context ctx = new InitialContext(); 
TestSessionLocalHome home = (TestSessionLocalHome)ctx.lookup("java:comp/env/ejb/TestSession");
TestSessionLocal bean = home.create();

bean.xxxx();//真正目的 物件使用

  其實上述程式碼最後一句才是我們真正目的,但是為了這個目的,必須經過前面冗長的程式碼建立,而在EJB3中,為建立型模式的Ioc模式(或稱依賴注射)取代了home.create這樣簡單工廠建立模式,以一種更加鬆耦合和簡潔的方式解決了物件建立問題,可以讓我們精力更集中在物件的使用上了。

  下面是annotations+Ioc/DI的EJB3呼叫程式碼:

@EJB //注意這裡後面是空白 
private TestSessionLocal testbean; //使用介面宣告 

public void invoke(){ 
  testbean.xxxx(); //直接使用 


  上述EJB3呼叫程式碼中,@EJB後面是空白,這其實使用了TestSessionLocal的預設JNDI名稱,一直到這裡,我們一直滿足於EJB3的簡化,但是如果研究@EJB語法後,會發現其完整寫法如下:

@EJB(
name = “ejb/shopping-cart”,//被呼叫者Cart實現類的ejb-reference名稱
beanName = “cart1”, //被呼叫者的名稱 beanName
beanInterface = ShoppingCart.class, //介面名稱 
description = “The shopping cart for this application”
)
private Cart myCart;

  上述完整@EJB寫法適用於同一個介面有多個實現子類時,其中關鍵是 beanName的定義:beanName是被呼叫EJB的類名 (不帶包名,稱為unqualified name ),或者, 如果被呼叫EJB有 XML descriptor定義, 它就是配置項ejb-name值(如果你使用過EJB2,就容易理解這個ejb-name了)。

  @EJB還有一個屬性mappedName,這是被呼叫者的JNDI名稱,一般不使用,因為這個JNDI名稱和具體伺服器有關,如果是JBoss4,那麼它的預設形式是:"EAR-FILE-BASE-NAME/BEAN--CLASS-NAME/local" (or remote). 也就是:被呼叫者EJB所在EAR包的名稱/Bean實現子類(不帶包名)/local,如果是remote呼叫,就是remote。 如果這個EJB被打包在jar包中,那麼JNDI名稱就是EJB-CLASS-NAME/local and EJB-CLASS-NAME/remote,當然,作為替換@RemoteBinding 和 @LocalBinding 也可定義JNDI名稱。

  也就是說:JBoss的EJB3中,如果你不使用XML配置,直接使用annotations,那麼JNDI預設名稱沒有一個統一規定名稱,有的可以直接是類名;在JBoss中還和EJB打包的形式有關,是動態變化的。如果你以為在EJB3中不會接觸到這個變化的JNDI預設名稱,那你就錯了。

  JBoss 4 在Servlet中不支援類似EJB呼叫EJB那樣的依賴注射 binding-by-injection,因為Web容器和EJB容器是兩個不同容器,當然藉助另外JBoss Seam則是另外一回事,因此,在Web層呼叫EJB,就必須通過JNDI繫結一個session bean,這時,你就必須使用到那個變化不定的預設JNDI名稱了。 

JNDI Naming Context

  無論J2EE還是Java EE中,JNDI是一個好像不起眼,但是極其重要的概念,不理解JNDI可以說,對J2EE或JavaEE只瞭解一半。

  JNDI本來是EJB2中比較複雜的一個概念,不同容器有自己的JNDI名稱,由此EJB2引入了第三者EJB-Reference,雖然解決了程式碼中耦合JNDI名稱問題,但是又帶來了更加煩瑣的配置,這種現象當然被JavaEE5.0繼續繼承了下來,問題遠非這麼簡單。

  J在Java EE5.0中(包括EJB3和Web環境),當我們需要訪問一個JNDI環境下資源時,有兩種方式:除了傳統EJB2中的JNDI呼叫方式;還有一種就是:使用依賴注射Ioc模式,這個依賴注射的表達方式是使用annotations。

  因此,在EJB3中,必須好好搞清楚annotations、依賴注射和JNDI之間的關係,如果這個問題不弄明白,EJB3就絕非EJB2那麼容易搞定,當然,搞定了的結果很簡單,讓人感覺簡化輕量了,真不知道EJB3這種簡化是不是有點象”掩耳盜鈴“。

  可以總結一句:凡是EJB2中使用配置檔案定義的;EJB3一般都可以使用 annotations定義(當然EJB3也支援配置檔案定義);凡是EJB2通過JNDI尋找的資源(呼叫容器中其他EJB、呼叫環境變數等Resource資源等),都是可以依靠annotations+依賴注射機制完成。

JPA替代實體Bean

.   如果說EJB3與EJB2變化最大的部分,就是持久層使用Java Persistence API 替代了EJB2的實體Bean,這樣,我們通過Evans DDD建模得到的Domain Model類可以直接持久化儲存到資料庫,不像EJB2中還需要在Model類和實體Bean中進行一次轉換。

  EJB3引入EntityManager進行需要持久實體的查詢及其新增修改;EntityManager非常類似JDBCTemp/HibernateTemplate等持久化模板。

  JPA和JDO以及Hibernate等O/R mapping框架都是非常相似的。

  雖然在JPA中,我們都可以使用Annotation來替代配置,實現很多過去需要專門配置檔案才能實現功能,不再一定需要 每個伺服器不同的cmp對映檔案,增強了移植性,但是EJB3還是需要 一個叫persistence.xml配置檔案,在這個配置中進行資料庫JNDI配置;當然,還有一些和具體伺服器有關的配置屬性,如果使用JBoss,JBoss的JPA底層使用Hibernate實現,因此在persistence.xml要進行有關Hibernate屬性配置:

<persistence>
  <persistence-unit name="Ejb3Tutorial"> 
    <jta-data-source>java:/TestDS</jta-data-source> 
    <properties> 
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/> 
    </properties>
  </persistence-unit>
</persistence>

攔截器概念

  EJB3.0引入了類似AOP中的攔截器概念(注意,AOP不只等於攔截器,所以不能認為EJB3就是完全AOP了),JBoss使用JBossAOP來實現攔截器功能,自己定義的攔截器方法可以攔截任何一個業務方法或生命週期事件回撥;攔截方法可以在bean中定義或專門的攔截器類。

@Stateless
@Interceptors( { NullChecker.class, ArgumentsChecker.class })
public class StatelessSessionBean implements StatelessSession {

  // This business method is called after
  // the above two interceptor's @AroundInvoke
  // methods are invoked. Hence it is guaranteed
  // that the argument to this method is not null
  // and it starts with a letter
  public String initUpperCase(String val) {
    String first = val.substring(0, 1);
    return first.toUpperCase() + val.substring(1);
  }

}

  NullChecker和ArgumentsChecker是StatelessSessionBean兩個攔截器,在攔截器NullChecker中,必須指定的攔截方法為@AroundInvoke。

public class NullChecker {

  @AroundInvoke
  public Object checkIfNull(InvocationContext ctx)
        throws Exception {
      Method method = ctx.getMethod();
      if (method.getName().equals("initUpperCase")) {
        String param = (String) (ctx.getParameters()[0]);
      }
       .........

      }
       // Proceed to the next interceptor
      return ctx.proceed();
  }

}

總結

  總之,從上面EJB2和EJB3的總結上看,EJB3.0在EJB2基礎上,引入了更多概念,最大變化就是Annotation替代了配置檔案,對於一些配置檔案厭惡者來說,是一個好事;但是在實戰中,在一些依賴注射不能照顧到地方,我們還必須和更加複雜的JNDI名稱打交道,這恐怕是EJB3的一個不是很完美的地方。

  關於EJB3中可測試性的優點被很多人津津樂道,將EJB脫離容器測試,雖然可以進行微觀的單元測試,但是脫離容器就是脫離特定完整的業務場景,所以,基於容器的(也就是基於完整的業務場景)單點跟蹤除錯才是最重要的,這些必須依賴開發工具的發展,目前已經在Eclipse3.2以後版本+WPT(或JBossIDE/Lomboz)中實現,這個功能適合大部分J2EE/JavaEE程式。

  所以,板橋個人對脫離容器的測試要求並不以為然,而這個曾經是Martin Fowler定義POJO的主要內容,因為在過去容器概念剛剛出現時,很多人都有容器恐懼症,以為容器都是不透明的,我們的業務物件放入進去,就失去了控制,這些都是落後設計觀念導致,其實,Java語言本身提供的可跟蹤性和介入性是非常強大,目前效能跟蹤工具Profiler可以在容器執行時,跟蹤到容器中某個具體類佔據CPU多少,佔用多少記憶體資源,那麼一個單點除錯豈是一個個所謂容器可以阻擋的?容器是Java語言的特點,ClassLoader決定了Java就是一個容器性的語言,關鍵這個容器是必須透明的。