| EJB3.0規范解讀 |
| 引言 在本文中將對新的規范進(jìn)行一個(gè)概要性的介紹,包括新增的元數據支持,EJBQL的修改,實(shí)體Bean模型訪(fǎng)問(wèn)bean上下文的新方法和運行時(shí)環(huán)境等等。作者還討論了EJB在未來(lái)要作出的調整以及EJB3.0與其他開(kāi)發(fā)規范之間的關(guān)系。 開(kāi)始 無(wú)論如何由于EJB的復雜性使之在J2EE架構中的表現一直不是很好。EJB大概是J2EE架構中唯一一個(gè)沒(méi)有兌現其能夠簡(jiǎn)單開(kāi)發(fā)并提高生產(chǎn)力的組建。EJB3.0規范正嘗試在這方面作出努力以減輕其開(kāi)發(fā)的復雜性。EJB3.0減輕了開(kāi)發(fā)人員進(jìn)行底層開(kāi)發(fā)的工作量,它取消或最小化了很多(以前這些是必須實(shí)現)回調方法的實(shí)現,并且降低了實(shí)體Bean及O/R映射模型的復雜性。 在本文中,我首先會(huì )介紹EJB3.0中幾個(gè)主要的改變。它對進(jìn)一步深入了解EJB3.0是非常重要的。隨后,我會(huì )從更高的層面來(lái)描述已經(jīng)被提交到EJB3.0規范中的細節,并一個(gè)個(gè)的講解新的規范中的改變:實(shí)體Bean,O/R映射模型,實(shí)體關(guān)系模型和EJB QL(EJB查詢(xún)語(yǔ)言)等等。 背景 EJB3.0中兩個(gè)重要的變更分別是:使用了Java5中的程序注釋工具和基于Hibernate的O/R映射模型。 Java5中的元數據工具 Java5(以前叫J2SE1.5或Tiger)中加入了一種新的程序注釋工具。通過(guò)這個(gè)工具你可以自定義注釋標記,通過(guò)這些自定義標記來(lái)注釋字段、方法、類(lèi)等等。這些注釋并不會(huì )影響程序的語(yǔ)義,但是可以通過(guò)工具(編譯時(shí)或運行時(shí))來(lái)解釋這些標記并產(chǎn)生附加的內容(比如部署描述文件),或者強制某些必須的運行時(shí)行為(比如EJB組件的狀態(tài)特性)。注釋的解析可以通過(guò)源文件的解析(比如編譯器或這IDE工具)或者使用Java5中的APIs反射機制。注釋只能被定義在源代碼層。由于所有被提交到EJB3.0草案中的注釋標記都有一個(gè)運行時(shí)的RetentionPolicy,因此會(huì )增加類(lèi)文件占用的存儲空間,但這卻給容器制造商和工具制造商帶來(lái)了方便。 Hibernate 目前Hibernate非常受歡迎,它是開(kāi)發(fā)源代碼的Java O/R映射框架,目的是把開(kāi)發(fā)人員從繁瑣的數據持久化編程中解脫出來(lái)。它也有一個(gè)標準的HQL(Hibernate 查詢(xún)語(yǔ)言)語(yǔ)言,你可以在新的EJB QL中看到它的影子。Hibernate在處理如數據查詢(xún)、更新、連接池、事務(wù)處理、實(shí)體關(guān)系處理等方面非常簡(jiǎn)單。 深入研究 現在是時(shí)候詳細了解EJB3.0草案了。讓我們開(kāi)始探討所有EJB中四種企業(yè)級bean,并看看他們在新的規范中是什么樣子。 無(wú)狀態(tài)會(huì )話(huà)bean 在EJB3.0規范中,寫(xiě)一個(gè)無(wú)狀態(tài)會(huì )話(huà)bean(SLSB)只需要一個(gè)簡(jiǎn)單的Java文件并在類(lèi)層加上@Stateless注釋就可以了。這個(gè)bean可以擴展javax.ejb.SessionBean接口,但這些不是必須的。 一個(gè)SLSB不再需要home接口,沒(méi)有哪類(lèi)EJB再需要它了。Bean類(lèi)可以實(shí)現業(yè)務(wù)接口也可以不實(shí)現它。如果沒(méi)有實(shí)現任何業(yè)務(wù)接口,業(yè)務(wù)接口會(huì )由任意public的方法產(chǎn)生。如果只有幾個(gè)業(yè)務(wù)方法會(huì )被暴露在業(yè)務(wù)接口中,這些方法可以使用@BusinessMethod注釋。缺省情況下所有產(chǎn)生的接口都是local(本地)接口,你也可以使用@Remote注釋來(lái)聲明這個(gè)接口為remote(遠程)接口。 下面的幾行代碼就可以定義一個(gè)HelloWorldbean了。而在EJB2.1中同樣的bean至少需要兩個(gè)接口,一個(gè)實(shí)現類(lèi)和幾個(gè)空的實(shí)現方法,再加上部署描述符?! ? import javax.ejb.*; @Stateless @Remote public class HelloWorldBean { public String sayHello() { return "Hello World!!!"; } } 有狀態(tài)會(huì )話(huà)bean 除了幾個(gè)SFSB的特別說(shuō)明之外,有狀態(tài)會(huì )話(huà)bean(SFSB)和SLSB一樣精簡(jiǎn): 1) 一個(gè)SFSB應該有一個(gè)方法來(lái)初始化自己(在EJB2.1中是通過(guò)ejbCreate()來(lái)實(shí)現的)。在EJB3.0的規范中建議這些初始化操作可以通過(guò)自定義方法完成,并把他們暴露在業(yè)務(wù)接口中。在使用這個(gè)bean之前由客戶(hù)端來(lái)調用相應的初始化方法。目前規范組織就是否提供一個(gè)注釋來(lái)標記某個(gè)方法用于初始化還存在爭議。 2) Bean的提供者可以用@Remove注釋來(lái)標記任何SFSB的方法,以說(shuō)明這個(gè)方法被調用之后bean的實(shí)例將被移除。同樣,規范組織仍然在討論是否要有一種機制來(lái)處理這種特殊的情況,即當這個(gè)方法出現異常的情況下bean的實(shí)例是否被移除。 下面是對以上問(wèn)題我個(gè)人的觀(guān)點(diǎn): 1) 是否應該有一個(gè)注釋來(lái)標明一個(gè)方法進(jìn)行初始化呢?我的觀(guān)點(diǎn)是――應該有,這樣容器就可以在調用其他方法之前至少調用一個(gè)方法來(lái)進(jìn)行初始化。這不僅可以避免不必要的錯誤(由于沒(méi)有調用初始化方法)而且可以使容器更明確的判斷是否可以重用SFSB實(shí)例。我暫且把這個(gè)問(wèn)題放一放,規范組織只考慮為一個(gè)方法提供一個(gè)注釋來(lái)聲明它是一個(gè)初始化方法。 2) 對于第二個(gè)問(wèn)題我的觀(guān)點(diǎn)也是肯定的。這有利于Bean的提供者合客戶(hù)端程序對其進(jìn)行控制。只有一個(gè)遺留的問(wèn)題:那就是一旦調用這個(gè)方法失敗,是否能移除這個(gè)bean 的實(shí)例?答案是不能,但是它將會(huì )在會(huì )話(huà)結束的時(shí)候被移除。 消息驅動(dòng)Bean 消息驅動(dòng)Bean是唯一一種必須實(shí)現一個(gè)業(yè)務(wù)接口的Bean。這個(gè)接口指出bean支持的是哪一種消息系統。對于以JMS為基礎的MDB來(lái)說(shuō),這個(gè)接口是javax.jms.MessageListener。注意MDB業(yè)務(wù)接口不是一個(gè)真正意義上的業(yè)務(wù)接口,它只是一個(gè)消息接口。 實(shí)體Bean 1) 實(shí)體Bean使用@Entity注釋來(lái)標記,所有實(shí)體bean中的屬性/字段不必使用@Transient注釋來(lái)標記。實(shí)體bean的持久化字段可以通過(guò)JavaBean-style機制或者聲明為public/protected字段來(lái)實(shí)現。 2) 實(shí)體bean可以使用助手類(lèi)來(lái)描述其狀態(tài),但是這些類(lèi)的實(shí)例并沒(méi)有持久化唯一性(persistent identity)的特性(即,唯一標識這個(gè)bean的字段等),實(shí)際上這些助手類(lèi)與他們的實(shí)體bean實(shí)例是緊密結合的;并且這些對象還是以非共享方式來(lái)訪(fǎng)問(wèn)實(shí)體對象的。 實(shí)體關(guān)聯(lián) EJB3.0同時(shí)支持Bean之間雙向的合單向的關(guān)聯(lián),它們可以是一對一、一對多、多對一或者是多對多的關(guān)聯(lián)。然而雙向關(guān)聯(lián)的兩端還要分為自身端(owning side)和對方端(inverse side)不同的端。自身端負責向數據庫通告關(guān)聯(lián)的變更。對于多對多的關(guān)聯(lián)自身端必須明確的聲明。實(shí)際上對方端通過(guò)isInverse=true進(jìn)行注釋?zhuān)ㄓ纱俗陨矶司筒槐卣f(shuō)明了而是由另一段推斷出)??磥?lái)上面的描述,規范組織還能說(shuō)讓EJB變的簡(jiǎn)單了嗎? O/R映射 EJB3.0中的O/R映射模型也有了重要的改變,它從原來(lái)的abstract-persistence-schema-based變成了現在的Hibernate-inspired模式。盡管目前規范組織還在就此進(jìn)行討論但是一個(gè)明確的模型將會(huì )出現在下一個(gè)版本的草案中。 舉例來(lái)說(shuō),O/R映射模型將通過(guò)bean類(lèi)中的注釋來(lái)聲明。而且此方法還會(huì )指出對應的具體表和字段。O/R映射模型提供了一套自有的SQL;而且除了提供一些基本的SQL外還支持某些高層開(kāi)發(fā)的功能。比如,有一個(gè)通過(guò)@Column注釋聲明的字段columnDefinition,那么可以寫(xiě)這樣的SQL:columnDefinition="BLOB NOT NULL" 客戶(hù)端程序模型 一個(gè)EJB客戶(hù)端可以通過(guò)@Inject注釋以一種“注入”的方式獲得一個(gè)bean的業(yè)務(wù)接口引用。你也可以使用另一個(gè)注釋@javax.ejb.EJBContext.lookup()來(lái)完成上面的操作,但是規范中沒(méi)有告訴我們一個(gè)普通的Java客戶(hù)端怎樣獲得一個(gè)Bean的實(shí)例,因為這個(gè)普通的Java客戶(hù)端是運行在一個(gè)客戶(hù)端容器中,它無(wú)法訪(fǎng)問(wèn)@javax.ejb.EJBContex對象?,F在還有另外一種機制來(lái)完成上面的工作那就是使用一個(gè)超級上下文環(huán)境對象:@javax.ejb.Context()。但是規范中沒(méi)有指出該如何在客戶(hù)端中使用這個(gè)對象。 EJB QL EJB QL可以通過(guò)@NamedQuery來(lái)注釋。這個(gè)注釋有兩個(gè)成員屬性分別是name和queryString.一旦定義了這些屬性,就可以通過(guò)EntityManager.createNamedQuery(name)來(lái)指向這個(gè)查詢(xún)。你也可以創(chuàng )建一個(gè)標準的JDBC風(fēng)格的查詢(xún)并使用EntityManager.createQuery(ejbqlString)或EntityManager.createNativeQuery(nativeSqlString)(這個(gè)方法用于執行一個(gè)本地查詢(xún))來(lái)執行查詢(xún)。 EJB QL有兩個(gè)地方可以定義其參數。javax.ejb.Query接口提供了定義參數、指向查詢(xún)、更新數據等等方法。下面是一個(gè)EJBQL指向查詢(xún)的例子: .. .. @NamedQuery( name="findAllCustomersWithName", queryString="SELECT c FROM Customer c WHERE c.name LIKE :custName" ) .. .. @Inject public EntityManager em; customers = em.createNamedQuery("findAllCustomersWithName") .setParameter("custName", "Smith") .listResults(); 下面列出了一些EJB QL的增強特性: 1) 支持批量更新和刪除。 2) 直接支持內連接和外連接。FETCH JOIN運行你指出關(guān)聯(lián)的實(shí)體,Order可以指定只查詢(xún)某個(gè)字段。 3) 查詢(xún)語(yǔ)句可以返回一個(gè)以上的結果值。實(shí)際上,你可以返回一個(gè)依賴(lài)的類(lèi)比如下面這樣: SELECT new CustomerDetails(c.id, c.status, o.count) FROM Customer c JOIN c.orders o WHERE o.count > 100 4) 支持group by 和having。 5) 支持where子句的嵌套子查詢(xún)。 在提交的EJB3.0草案中,EJB QL與標準SQL非常的接近。實(shí)際上規范中甚至直接支持本地的SQL(就像我們上面提到的那樣)。這一點(diǎn)對某些程序員來(lái)說(shuō)也許有些不是很清楚,我們將在下面進(jìn)行更詳細的講解。 多樣性 方法許可(Method permissions)可以通過(guò)@MethodPermissions或@Unchecked注釋來(lái)聲明;同樣的,事務(wù)屬性也可以通過(guò)@TransactionAttribute注釋來(lái)聲明。規范中仍然保留資源引用和資源環(huán)境引用。這些一樣可以通過(guò)注釋來(lái)聲明,但是有一些細微的差別。比如,上下文(context)環(huán)境要通過(guò)注入工具控制。容器根據bean對外部環(huán)境引用自動(dòng)初始化一個(gè)適當的已經(jīng)聲明的實(shí)例變量。比如,你可以象下面這樣獲得一個(gè)數據源(DataSource): @Resource(name="myDataSource") //Type is inferred from variable public DataSource customerDB; 在上面的例子中如果你不指定引用資源的名稱(chēng)(name)那么其中的customerDB會(huì )被認為是默認值。當所有的引用屬性都可得到時(shí),@Injec注釋就可以這樣寫(xiě): @Inject public DataSource customerDB; 容器負責在運行時(shí)初始化customerDB數據源實(shí)例。部署人員必須在此之前在容器中定義好這些資源屬性。 更好的消息是:那些以前必須檢測的異常將一去不復返。你可以聲明任意的應用程序異常,而不必在再拋出或捕獲其他類(lèi)似CreateException和FinderException這樣的異常。容器會(huì )拋出封裝在javax.ejb.EJBException中的系統級異?;蛘咧辉诒匾獣r(shí)候拋出IllegalArgumentException或IllegalStateException異常。 EJB文件處理模式 在我們結束本節之前,讓我的快速的瀏覽一下容器提供商在EJB處理模式方面可能的變更。規范中對此并沒(méi)有明確的表態(tài),但我可以想到至少兩種模式。 1) 一種辦法是首先利用EJB文件生成類(lèi)似于EJB2.1部署模式的文件(包括必要的接口和部署描述符)然后再用類(lèi)似于EJB2.1的方式來(lái)部署這個(gè)EJB組件。當然,這樣產(chǎn)生的部署描述符可能并不標準但是它可以解決同一個(gè)容器對EJB2.1和EJB3.0兼容的問(wèn)題?! ? 2) 另一種方法是一種類(lèi)似于JSP托放的部署模式。你可以把一個(gè)EJB文件放到一個(gè)預先定義的目錄下,然后容器會(huì )識別這個(gè)EJB并處理它,然后部署并使之可以使用。這種方法可以建立于上面那種方法之上,在支持反復部署時(shí)有很大的幫助??紤]到部署的簡(jiǎn)單性也是EJB3.0規范的目的之一,我真誠的希望在下一個(gè)草案出來(lái)時(shí)能夠確定一個(gè)模式(至少能有一個(gè)非正式的)。 你有什么想法? EJB3.0規范的制定正在有序的進(jìn)行,為了使EJB的開(kāi)發(fā)變得更加容易,EJB規范組織作出的努力是有目共睹的。就像他們說(shuō)的那樣,一切對會(huì )變得簡(jiǎn)單,但做到這一點(diǎn)并不容易。目前已經(jīng)定義了50個(gè)注釋標記(還有幾個(gè)將在下一個(gè)草案中發(fā)布),每一個(gè)都有自己的缺省規則和其他的操作。當然,我真的不希望EJB3.0變成EJB2.1的一個(gè)翻版"EJB 3.0 = EJB 2.1 for dummies"(希望這個(gè)等式不要成立)。最后,我還是忍不住要提一些我自己的觀(guān)點(diǎn): 1) 首先,規范確實(shí)使反復部署變得容易了,并且有一個(gè)簡(jiǎn)單的模式來(lái)訪(fǎng)問(wèn)運行時(shí)環(huán)境。我還是覺(jué)得home接口應該放棄。 2) 在早期的EJB規范中,實(shí)體bean用于映射一個(gè)持久化存儲。理論上(也許只是理論上)可能需要把實(shí)體bean映射到一個(gè)遺留的EIS(enterprise information system)系統中。出于將來(lái)擴展的考慮這樣作是有好處的,并且可以使更多的業(yè)務(wù)數據模型采用實(shí)體bean。也因此其伴隨的復雜性使得實(shí)體bean不被看好。在本次提交的草案中,一個(gè)實(shí)體bean只是一個(gè)數據庫的映射。并且是基于非抽象持久化模式和簡(jiǎn)單的數據訪(fǎng)問(wèn)模式的更加簡(jiǎn)單開(kāi)發(fā)。 3) 我對模型變更持保留態(tài)度,我認為在EJB中包含SQL腳本片斷并不是個(gè)好注意。一些開(kāi)發(fā)人員完全反對包含某些“SQL片段(SQLness)”(比如@Table 和 @Column注釋?zhuān)?。我的觀(guān)點(diǎn)是這些SQLness是好的,據此我們可以清楚的知道我們到底要數據庫作些什么。但是某些SQL段我看來(lái)并不是很好,比如columnDefinition="BLOB NOT NULL",這使得EJB代碼和SQL之間的耦合太過(guò)緊密了。 4) 盡管對于本地SQL的支持看似很誘人,其實(shí)在EJB代碼中嵌入SQL是一個(gè)非常糟糕的主意。當然,有些辦法可以避免在EJB中硬編碼SQL,但是這應該在規范中說(shuō)明,而不能是某些開(kāi)發(fā)人員自己定義的模式。 5) 假設@Table注釋只用于類(lèi)。在運行時(shí)通過(guò)@Table注釋的name屬性定義的表名稱(chēng)將必須對應一個(gè)實(shí)際的數據庫表。規范對此應該給予清楚的說(shuō)明和一致的模式。 6) 規范還需要更清楚的說(shuō)明客戶(hù)端編程模型,尤其是普通java客戶(hù)端。規范中所有的參考都假設或者隱含的使用EJB客戶(hù)端。而且規范中對客戶(hù)端的向后兼容方面也沒(méi)有給出明確的說(shuō)法。 7) Transient注釋?xiě)撝匦旅员苊夂鸵延械膖ransient關(guān)鍵字發(fā)生沖突。事實(shí)上,在這一點(diǎn)上我們更樂(lè )于稍微的背離一下configuration-by-exception原則并且定義一個(gè)@Persistent注釋來(lái)明確的定義持久化字段。@Persistent注釋可以?xún)H僅是一個(gè)標記注釋或者它可以有幾個(gè)屬性來(lái)關(guān)聯(lián)O/R映射注釋。 與其他規范的關(guān)聯(lián) 目前可能影響到EJB3.0的JSR有JSR175(java語(yǔ)言元數據工具)和JSR181(Java Web服務(wù)元數據) JSR175已經(jīng)初步完成并且不會(huì )和EJB3.0有太大的沖突;但是JSR181與EJB3.0有兩個(gè)關(guān)聯(lián)的地方: 1) Web service接口:EJB規范將采用一種機制適應JSR181以便可以把一個(gè)bean實(shí)現為一個(gè)Web service并告訴Web service如何被客戶(hù)端調用。 2) JSR 181計劃采用不同的機制來(lái)處理安全問(wèn)題。在早期的規范中EJB建議使用一個(gè)一致的機制(MethodPermissions),但是JSR 181計劃使用一個(gè)稍微不同的方式(SecurityRoles和SecurityIdentity注釋?zhuān)?。同樣的RunAs注釋的定義也存在這些許差別。這一問(wèn)題還在解決中最終會(huì )在J2EE層的規范中維持其一致性。 在J2EE 1.5中的一些開(kāi)發(fā)規范可能與EJB3.0有關(guān)聯(lián)。除了上面說(shuō)到的幾個(gè)關(guān)聯(lián)之外現在沒(méi)有其他的開(kāi)發(fā)規范與EJB3.0有沖突。 結束語(yǔ) 在使EJB的開(kāi)發(fā)變得簡(jiǎn)單高效之前,我們還有很長(cháng)一段路要走。規范組織在降低EJB的開(kāi)發(fā)難度方面起了個(gè)好頭。O/R映射模型的提議還處在早期階段,規范組織正在完善它。我希望它不要太復雜也不要與SQL過(guò)分的耦合。 |
聯(lián)系客服