本系列共三部分,在第一部分中,Java 開發人員 David Currie 介紹了 Java 2 Enterprise Edition (J2EE) Connector Architecture (JCA) 1.5 中所作的一些優化,它們可以加快現有或新外部資源適配器的運行速度。他還分析了一些使資源適配器煥發生機的新功能。若想提高現有資源適配器的性能或要加入新的功能,或者正在考慮編寫新的 JCA 1.5 資源適配器,那么本系列是必不可少的。若要編寫使用資源適配器的應用程序,并想知道更多的幕后情況,那么本系列也值得一讀。

JCA 1.5 是 J2EE Connector Architecture 的最新版本,它包含了許多重要的增強和新增功能。討論這些變化的系列文章分為三個部分,本文是第一部分,將介紹該版本中所作的一些優化,它們可以加快那些使用出站資源適配器的應用程序,特別是那些使用事務的應用程序的運行速度。然后介紹對生命周期管理契約所作的擴展,它們可以防止應用程序的連接斷開,并使資源適配器煥發新生。這將為第 2、3 部分奠定基礎。第 2 部分討論新的工作管理和事務流入契約,第 3 部分探討期待已久的消息流入契約,其更常見的叫法是消息驅動的 bean (MDB) 支持。

本文假定讀者對 JCA 連接管理契約有一定的了解。原來 1.0 規范中的所有內容仍然適用,因此如果剛剛接觸 JCA,建議先看一看 Willy Farrell 的入門教程(請參閱 參考資料)。

連接加快
JCA 規范的新版本不會使應用程序與后端系統之間的連接變得更快,但 JCA 1.5 引入了兩組接口,可以加快使用連接的應用程序的運行。第一組接口解除了以前 JCA 版本中應用服務器管理連接句柄方式的限制。

許多讀者都知道,J2EE 支持兩種連接使用模式,本文稱之為 get-use-close cached-handle。對這些模式的進一步分析有助于理解 JCA 1.5 在這方面所帶來的性能改善。

get-use-close
在 get-use-close 模式中,應用程序在需要新連接時,總是先獲取新連接,然后使用,然后再關閉它,如清單 1 所示。(為清楚起見,本文沒有在示例清單中加入異常處理邏輯。)

清單 1. 使用 get-use-close 模式的一個 Enterprise JavaBean (EJB)
public class GetUseCloseEJB implements SessionBean {

    ...

    public void businessMethod() {

       InitialContext context = new InitialContext();
       DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/mydatasource");
       Connection connection = datasource.getConnection();

       ...

       connection.close();

    }

}

get-use-close 模式看起來效率不高,但是應用服務器實現的連接池可降低“獲得”操作的成本。此外,因為應用程序只在需要時才保留連接,所以應用程序的不同實例或者部分可以重復使用該連接,從而降低了總的資源占用,如圖 1 所示。

圖 1. 使用中的 get-use-close 模式
使用中的 get-use-close 模式

如圖 1 所示,每當 bean 方法調用 getConnection 時,連接管理器都會重復使用池中托管的連接,并只創建一個新連接句柄。當連接句柄通知連接管理器它已關閉時,托管的連接就被清除并返回池中。圖 1 中的綠色線條表示托管連接與應用程序的這個實例相關聯的時間。

cached-handle
在 cached-handle 模式中,應用程序在一開始是獲得連接,然后在一個實例字段中緩存對它的引用,如清單 2 所示。

清單 2. cached-handle 模式
public class CachedHandleEJB implements SessionBean {

    private Connection _connection;

    ...

    public void ejbCreate() {

       InitialContext context = new InitialContext();
       DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/mydatasource");
       _connection = datasource.getConnection();

    }

    public void businessMethod() {

       ...

    }

}

應用程序開發人員通常都使用 cached-handle 方法,因為他們認為這樣應用程序的性能會更好。但是因為 get-use-close 模式中連接池的作用,這兩種使用模式的性能差別一般來說是很小的。盡管 cached-handle 模式可使業務方法中的邏輯更簡單,但是需要額外的邏輯以便在鈍化(passivation)時關閉連接,并在激活時重建連接。當容器毀壞 bean 時(如由于方法生成了一個運行時異常),還有可能留下打開的連接。

cached-handle 模式的最大問題是當 bean 或者 servlet 的一個實例使用該連接時,其他實例就不能使用它——因此連接數最多只會有實例那么多。如圖 2 所示。

圖 2. 使用中的 cached-handle 模式
使用中的 cached-handle 模式

從圖 2 中可以看到,創建 EJB 時(從綠色線條開始),托管的連接與連接句柄關聯,并且這個 bean 實例以外的任何其他對象都不能使用這個連接。(綠色線條無限延伸。)

針對這種情況,JCA 1.5 規范引入了兩個新的接口,如清單 3 所示。

清單 3. 解除關聯(dissociation)和惰性關聯(lazy association)接口
public interface DissociatableManagedConnection {

    void dissociateConnections() throws ResourceException;

}

public interface LazyAssociatableConnectionManager {

    void associateConnection(Object connection,
                             ManagedConnectionFactory mcf,
                             ConnectionRequestInfo cxReqInfo)
            throws ResourceException

}

解除關聯
資源適配器的托管連接實現第一個接口—— 解除關聯 接口——以向連接管理器表明適配器支持這種優化。當連接暫時超出范圍時(即當 bean 或者 servlet 方法退出時),如果連接管理器支持這種優化的話,它就可以解除由應用程序使用的連接句柄與表示物理資源的托管連接的關聯。這使托管連接可以返回連接池,為應用程序的其他部分所使用。

惰性關聯
這種優化使用 惰性關聯,而不是在下次調用方法時重新將連接句柄與托管連接關聯。如果方法沒有使用連接,或者它只調用連接句柄上不需要訪問后端的簡單方法,那么托管連接未必會從池中移出。相反,當連接句柄確定它不需要與一個托管連接重新關聯時,它就可以將連接管理器強制轉換為 LazyAssociatableConnectionManager 并調用 associateConnection 方法。該方法以連接句柄為第一個參數,然后是托管連接廠,以及對 allocateConnection 的第一次調用時傳遞的請求信息。然后連接管理器從池中找到另一個合適的托管連接,并使用這個托管連接的 associateConnection 方法將它與連接句柄關聯。

圖 3 顯示了這種優化對 圖 2 中的 cached-handle 用法的效果。

圖 3. 惰性關聯降低資源用量
惰性關聯降低資源用量

圖 3 中虛線箭頭表示方法完成時 EJB 容器對連接管理器的通知。這時,托管的連接與連接句柄解除關聯,只有當方法試圖使用這個句柄時,托管連接才會重新關聯。短的綠色線條顯示托管連接現在綁定到 EJB 上,以縮短時間并可以在別的地方重新使用。

征募,還是不征募
大家都知道事務代價很高——特別是 XA(全局)事務。這使得讓事務不執行非必需的工作變得很重要。JCA 1.5 中的另外兩個新接口防止了 XAResource 對象的不必要征募。

我們現在來更詳細地分析一下這種增強所針對的問題。假定取用 清單 1 中的 EJB,并用容器托管的事務部署它,將業務方法的事務屬性設定為 RequiresNew。調用這個方法時便開始一個新的事務。創建連接時,連接管理器不知道如何使用它,因此它必須從關聯的托管連接獲得一個 XAResource,并在事務中征募它。連接可能只用于查詢數據庫或者根本不被使用。但是連接管理器必須征募連接,以備插入或者更新操作。這意味著,資源適配器至少必須進行開始、提交或者回滾,并結束流程返回后端。圖 4 展示了這樣的事務流程。

圖 4. 急切事務征募
急切事務征募

在圖 4 中可以看到,獲得連接后,XAResource 立即征募到了事務中(即接收一個開始流程)。這意味著當事務方法結束時,流程需要在資源處結束,即使方法沒有在事務中使用連接。

如果在事務中涉及另一項資源,就會強制進行不必要的兩階段提交,導致額外的準備流程。這里的惟一可取之處是資源管理器仍然可以從準備調用中返回 XA_RDONLY (用于“read only”)以表明它實際沒有做任何工作。事務管理器不需要在那個資源管理器處完成調用,并且如果在事務中只有一個資源管理器真正做了工作,那么事務管理器也許可以避免日志文件中的惰性寫操作。

惰性征募
現在您應當認識到除非絕對需要,否則不要在事務中征募。JCA 1.5 規范提供了一個解決方案:惰性征募。清單 4 顯示了支持這各種優化所引入的兩個接口。

WebSphere 為 JCA 1.5 打下基礎
IBM WebSphere 應用服務器 V5 用 JCA 1.0 的一個擴展克服了 cached-handle 連接模式的缺點,它稱為 智能連接句柄(smart connection handle)。JCA 1.5 規范通過對清單 3 中的兩個接口的修改吸取了這種擴展。

JCA 1.5 在其惰性征募支持中還采用了 WebSphere Application Server V5 的 延遲事務征募 擴展。

請閱讀 developerWorks 中 Kevin Kelle 等人撰寫的文章以了解更多的 WebSphere 擴展(請參閱 參考資料)。

清單 4. 惰性征募的接口
public interface LazyEnlistableManagedConnection {

}

public interface LazyEnlistableConnectionManager {

    void lazyEnlist(ManagedConnection mc)
            throws ResourceException;

}

LazyEnlistableManagedConnection 接口是由托管連接實現的標記接口,用以向連接管理器表明在事務中創建新的連接或者在連接已經存在的情況下開始一個新的事務時,它不需要將托管連接急切征募到現有事務中。如果連接句柄準備執行應當是任一事務中的一部分的某些工作,并且它的托管連接還沒有征募,那么應當確定連接管理器是否實施了 LazyEnlistableConnectionManager 接口。如果實施了,那么它應當調用傳遞托管連接的 lazyEnlist 方法。這個方法不返回任何結果,但是如果調用線程關聯了一個事務,那么這時就征募托管連接的 XAResource。如果連接沒有被征募,那么它需要在后面每一項工作之前再次調用 lazyEnlist,以檢查在上次調用這個方法之后,是不是沒有啟動過事務。

圖 5 顯示了這個新的事件序列。

圖 5. 惰性事務征募
惰性事務征募

在圖 5 中可以看到,在獲得連接時,XAResource 不再急切征募。相反,連接管理器會等待,直到連接用 lazyEnlist 調用表明它要完成一些事務工作時,才會征募 XAResource

什么時候出現錯誤
JCA 規范的第一個版本提供了一種讓資源適配器在連接出現嚴重錯誤時通知連接管理器的機制。這是 ConnectionEventListener 接口的 connectionErrorOccurred 方法。收到這個通知后,連接管理器就會毀環發送事件的托管連接,這樣就不會再使用它。這些都是不錯的。但是,如果到后端的連接丟失了,那么池中的許多托管連接也很有可能不能再使用。

針對這個問題,JCA 1.5 以 ValidatingManagedConnectionFactory 接口的方式引入了一種雅潔的解決方案,如清單 5 所示。

清單 5. 用于確定無效連接的接口
public interface ValidatingManagedConnectionFactory {

    Set getInvalidConnections(Set connectionSet)
            throws ResourceException;

}

由托管連接廠實現的 ValidatingManagedConnectionFactory 接口包含一個方法——getInvalidConnections——它以一組托管連接為參數,返回資源適配器認為無效的一個子集。資源適配器的驗證可以采取任何形式,不過通常涉及到對后端的某種“ping”操作,以測試連接。然后連接管理器在資源適配器指示連接錯誤時調用這個方法,甚至定期調用該方法,以便從池中排除壞掉的連接。

開始和結束
最初的 JCA 版本為托管連接及其相關聯的連接句柄提供了詳細的生命周期模式,但是它沒有為資源適配器提供這種概念。只有當創建了托管連接廠后,部署的資源適配器才知道它的存在。JCA 1.5 中引入的 ResourceAdapter 接口對此進行了糾正,如清單 6 所示。

清單 6. 新資源適配器接口的生命周期方法
public interface ResourceAdapter {

    void start(BootstrapContext ctx)
            throws ResourceAdapterInternalException;

    void stop();

    ...

}

資源適配器可以在其部署描述符(ra.xml)的 resourceadapter-class 元素中給出實現這個接口的類的名稱。除了實現 ResourceAdapter 接口,這個類可以通過 JavaBean 樣式支持某些屬性。與托管連接廠一樣,在部署描述符中可以聲明這些屬性及其默認值,如清單 7 所示。在安裝了資源適配器后,應用服務器將允許管理員覆蓋這些默認值。

清單 7. 資源適配器部署描述符
<connector>
  ...
  <resourceadapter>
    <resourceadapter-class>
      example.ExampleResourceAdapterImpl
    </resourceadapter-class>
    <config-property>
      <config-property-name>ServerName</config-property-name>
      <config-property-type>java.lang.String</config-property-type>
      <config-property-value>MyServer</config-property-value>
    </config-property>
    <config-property>
      <config-property-name>PortNumber</config-property-name>
      <config-property-type>java.lang.String</config-property-type>
      <config-property-value>1976</config-property-value>
    </config-property>
  </resourceadapter>
  ...
</connector>

在啟動時,應用服務器會創建在部署描述符中指定的類的一個實例,并設置管理員提供的屬性。這個類必須根據這些屬性實現一個 equals 方法,這樣應用服務器就可以保證它不會創建一個以上同樣的實例。然后會調用 start 方法,向它傳遞一個實現了 BootstrapContext 接口的對象。可以用這個對象創建計時器、調度其他線程上的工作和控制導入的事務,在本系列的第 2 部分中將更詳細地討論所有這些內容。這個方法將不會堵塞并會及時返回。

應用服務器通常會在關閉或者解除部署資源適配器之前,對資源適配器調用 stop 方法。JCA 1.5 規范描述了這個過程的兩個階段。首先,應用服務器保證依賴資源適配器的所有應用程序都已停止。這可保證程序線程不再使用資源適配器對象,并且所有事務都已完成。然后應用服務器調用 stop 方法。這時,資源適配器將執行一個有序的關閉(例如,釋放網絡和應用服務器資源,并將所有緩存的數據強行送回后端)。調用 stop 后,應用服務器將不會重新使用資源適配器實例。

為了保留向后兼容性,ManagedConnectionFactory 沒有改變,但是如果希望外部資源可以利用資源適配器具有的功能,那么還要實現清單 8 所示的新的 ResourceAdapterAssociation 接口。

清單 8. ResourceAdapterAssociation 接口
public interface ResourceAdapterAssociation {

    ResourceAdapter getResourceAdapter();
    void setResourceAdapter(ResourceAdapter ra)
            throws ResourceException;

}

構建了托管連接廠之后,應用服務器將調用 setResourceAdapter 方法以便將它與其資源適配器關聯在一起。在托管連接廠的生命周期內這種關聯將會固定下來。這種方法只調用一次。

結束語
本文展示了 JCA 1.5 為現有出站契約帶來的四項增強功能。惰性關聯和征募優化會提高使用連接的應用程序的性能,驗證托管連接廠會改善對故障情況的處理。在資源適配器級別引入生命周期管理為資源適配器提供了多種有趣的新機會。本系列的第 2 部分將分析如何在這個基礎上建立新的工作管理和事務流入契約。