領先一步
VMware 提供培訓和認證,助您加速前進。
了解更多昨晚我參加了新英格蘭 Java 使用者群組 (NEJUG) 的會議,Reza Rahman 在會中發表了關於 EJB 3 與 Spring 的「比較分析」。Reza 是 EJB 3 in Action 的作者之一。我很榮幸能與 Reza 見面,並敬佩他發表這個可能被認為是有爭議的主題。我也感謝他嘗試討論 EJB 3 和 Spring 的優缺點。然而,我必須澄清一些他在 Spring 方面的報導並不完全準確,這讓我(和其他與會者)認為該簡報帶有對 EJB 3 的偏見。公平地說,與固定的規格版本不同,Spring 不斷發展,我將在此提出的某些內容是新功能。另一方面,有些是 Spring 2.0 的功能,已經可用一年多了。我個人認為「比較分析」必須考慮所比較產品的最新穩定版本的最新功能集。我想不用說我也可能有點偏頗,但我在此的動機是提供一個完全客觀的回應,以便可以修改簡報,以反映更「蘋果對蘋果」的比較。我將針對簡報的 10 個「主題」提供簡短的回應。
有人提到 Spring 正在開始支援更多註解,但「這需要一段時間」。然而,Spring 2.0 版本提供了完整的 JPA 整合,包括使用 @PersistenceContext 注入 EntityManager,以及使用 Spring 的 @Transactional 註解進行註解驅動的交易管理(支援與具有 REQUIRED 預設傳播的 @Stateless EJB 相同的語義)。特別令人沮喪的是,比較沒有同時包含 JPA(請參閱下面的第 3 點)。Spring 2.0 還引入了完整的基於註解的 AspectJ 支援(@Aspect、@Before、@After、@Around)和「定型觀念」註解的概念。例如,@Repository 註解為直接使用 JPA 或 Hibernate API(沒有 Spring 的模板)的資料存取程式碼啟用了非侵入性的異常轉換。Spring 甚至早在 1.2 版本就提供了註解支援,例如 @ManagedResource 用於將任何 Spring 管理的物件透明地匯出為 JMX MBean。
現在,對我來說,這個問題排在第一位的主要原因是「這需要一段時間」的評論。作為 Spring 2.5 的註解驅動配置支援的主要開發人員之一,我必須說 Spring 元數據模型非常靈活,因此我們能夠比預期更快地提供全面的基於註解的模型。事實上,Spring 2.5 提供了對 JSR-250 註解的支援:@Resource、@PostConstruct 和 @PreDestroy,以及 @WebServiceRef 和 @EJB。特別令人感興趣的是 @Resource,因為它是 EJB 3 中用於依賴注入的主要註解。使用 Spring,@Resource 註解不僅支援 JNDI 查找(與 EJB 3 相同),還支援注入任何 Spring 管理的物件。這有效地結合了簡報中提到的 Spring 的主要優勢(Spring 支援任何物件類型的 DI)與 EJB 3 的主要優勢(使用註解而不是 XML)。Spring 2.5 還引入了更細粒度的基於註解的依賴注入模型,該模型基於 @Autowired 和(可擴展的)@Qualifier 註解。Spring 2.5 還擴展了「定型觀念」註解,以包括 @Service 和 @Controller。每個定型觀念註解都透過將其作為元註解應用來擴展通用 @Component 註解。透過應用相同的技術,@Component 註解為使用者定義的定型觀念提供了一個擴充點。Spring 甚至可以自動偵測這些註解的元件,作為 XML 配置的替代方案。例如,以下摘錄來自 PetClinic 範例應用程式的 2.5 版本
<context:component-scan base-package="org.springframework.samples.petclinic.web" />
不需要額外的 XML 程式碼即可設定 Web 控制器,因為它們使用註解驅動的依賴注入和註解進行請求對應。我指出這一點,是因為簡報特別強調了 Web 層配置的冗長性
@Controller
public class ClinicController {
private final Clinic clinic;
@Autowired
public ClinicController(Clinic clinic) {
this.clinic = clinic;
}
...
如需 Spring 註解支援的最新報導,請參閱:The Server Side 上的 Spring 2.5 簡介,或 Spring 參考手冊的最新版本 - 特別是 基於註解的配置章節。此外,請繼續關注此部落格和 Spring Framework 首頁,以取得即將發布的有關 2.5 版的文章和部落格。
這實際上是作為 Spring 的一個優勢提出的,但強調了配置的開銷。事實是,任何認真對待測試和敏捷開發的專案都需要支援「多個部署環境」。換句話說,這個特殊主題經常被扭曲,彷彿它僅適用於多個生產環境。實際上,在每個開發和測試週期中都必須部署到應用程式伺服器是敏捷性的主要障礙。通常,Spring 使用者會將其配置模組化,以便將「基礎架構」配置(例如 DataSource、TransactionManager、JMS ConnectionFactory)分開,並將動態屬性外部化。由於 Spring 支援基於外部化屬性替換 '${placeholders}',因此包含不同的屬性檔案通常會成為一個透明的問題。
我必須承認這一點最困擾我。在比較幻燈片中,EJB 3 範例顯示了 JPA 與透過entityManager進行資料存取,並且使用 @PersistenceContext 註解提供entityManager實例。另一方面,Spring 範例使用 Hibernate 並顯示 Hibernate SessionFactory 的 Setter 注入。在我看來,這違反了真實「比較分析」的第一條規則:使用比較雙方提供的最相似的功能。在這種特殊情況下,Spring 確實支援直接使用 JPA API(即,JpaTemplate 是完全可選的;直接使用 'entityManager' 仍然參與 Spring 交易等),並且 Spring 也識別 @PersistenceContext 註解。自 Spring 2.0 以來(最終版本已發布一年多),此支援已可用,因此我不明白為什麼比較沒有同時使用 Spring 端的 JPA。比較的其他部分顯然是基於 Spring 2.0 的,因此這給人一種選擇性過時並暴露出偏見的印象。如果修改此特定範例以使其「蘋果對蘋果」,它將破壞主要總體主題之一:Spring 需要更多配置,而 EJB 3 依賴標準註解。
現在,即使我相信在 Spring 端使用 Hibernate 而不是 JPA 扭曲了比較,它同時也揭示了 Spring 的優勢。如果您確實想直接使用 Hibernate API 而不是依賴 JPA API,Spring 可以啟用它,並且它在 Spring 交易管理和異常轉換方面以一致的方式進行。然後,這就開啟了使用 Hibernate 功能的機會,這些功能超出了 JPA 的限制,例如 Hibernate 的「criteria」查詢 API。同樣地,如果您想為 ORM 過多的資料存取添加一些直接 JDBC,Spring 也支援這一點 - 即使在與 Hibernate 或 JPA 資料存取相同的交易中調用時也是如此。
一個具體的例子是交易管理員的定義。有人指出,您必須了解容器廠商層面的事情才能配置 Spring 整合。這是錯誤的。例如,以下 bean 定義不包含任何容器特定的資訊,但 Spring 會自動偵測所有 Java EE 應用程式伺服器中的交易管理員
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
如果您確實想要利用容器特定的功能,例如每個交易的隔離等級,那麼 Spring 還提供了一些專門的實現:WebLogicJtaTransactionManager、WebSphereUowTransactionManager 和 OC4JJtaTransactionManager。在這些實現之間切換只需要更改這個單一定義。
除此之外,Spring 配置幻燈片不必要地冗長。恐怕這也可能是因為強調 EJB 與 Spring 不同,依賴智慧預設的目標所驅動的。例如,幻燈片顯示
<tx:annotation-driven transaction-manager="transactionManager"/>
實際上,如果在 Spring 環境中定義了單一的「transactionManager」,則不需要在「annotation-driven」元素上顯式提供該屬性。該屬性僅用於在一個應用程式中啟用多個交易管理員的使用如果需要。這些「自動偵測」和「智慧預設」的技術適用於整個 Spring,例如訊息接聽器的 JMS「connectionFactory」(在下面的第 6 點的範例中隱含),以及現有 MBean 伺服器或 RMI 登錄的自動位置。
從正面來看,Spring 允許「本地」交易管理,這實際上被認為是一個優勢。雖然 EJB 需要 JTA 進行交易管理,但許多應用程式不需要跨雙階段提交功能資源的分散式交易。在這種情況下,Spring 允許使用更簡單、開銷更少的交易管理員:DataSourceTransactionManager (適用於 JDBC)、HibernateTransactionManager 或 JpaTransactionManager。如果目標是準確地描述優缺點,我希望聽到更多關於 Spring 優勢的細節。例如,這對於在容器外進行測試或在 Eclipse 或 IDEA 等輕量級 IDE 環境中進行開發來說是一個巨大的優勢。
此外,如果您確實需要 JTA 進行分散式交易,但想在 Tomcat 或 Jetty 等輕量級容器中運行,Spring 可以輕鬆支援獨立的 JTA 提供者,如 Atomikos 和 JOTM。當然,Spring 的交易管理員設定需要配置單一 bean 定義,但這確實是一次性的成本 - 並且非常值得。
無狀態服務層的優點已被公認為最佳實務,Spring 也擁抱這一點。但是,Spring 確實提供了 singleton 以外的其他作用域。Spring 的「prototype」作用域為每次注入或查找啟用一個不同的實例,Spring 2.0 引入了 Web 作用域:「request」和「session」。作用域機制本身甚至是可擴展的;可以定義自訂作用域並將其對應到對話的概念。Spring 還支援使用 CommonsPoolTargetSource 的簡單物件池,但物件池很少是狀態管理的最佳解決方案。
更重要的是,Spring 透過 Spring Web Flow 為 Web 應用程式提供了非常強大、高度可配置的狀態管理。與該簡報宣稱開發人員必須直接與 HTTP Session 互動才能在 Spring 應用程式中管理狀態相反,在 Spring Web Flow 中,對話狀態是以透明的方式進行管理的。此外,儲存庫的組態是可插拔的,因此可以使用各種策略來進行狀態的實體儲存(Session、用戶端、後端快取等等)。最後,Spring Web Flow 的最新發展包括支援擴充的持久性上下文和完全整合的 JSF 支援。
Spring 2.5 提供了一個新的 'jms' 命名空間,可以大大簡化訊息接聽器的配置。請注意,每個接聽器沒有單獨的容器配置。多個接聽器共享配置,並且廣泛使用智慧型預設值。
<jms:listener-container>
<jms:listener destination="queue.confirm" ref="logger" method="log"/>
<jms:listener destination="queue.order" ref="tradeService" method="placeOrder"/>
</jms:listener-container>
簡報中還提到,執行緒管理始終是每個容器的問題。然而,這並非事實。訊息接聽器容器實際上使用 Spring 的 TaskExecutor 抽象,並且有許多可用的實現方式。例如,如果運行在 Java 5+ 上,您可以配置一個執行緒池執行器,甚至可以配置一個 CommonJ WorkManager 執行器。如果需要,執行器可以輕鬆地在多個接聽器容器之間共享。實際上,'task-executor' 屬性在 'listener-container' 元素(如上所示)上可用,在邏輯上它只會被設定一次,但會被每個內部創建的每個接聽器定義的容器實例共享。
好吧,這確實是當晚最奇怪的時刻。程式碼幻燈片描繪了一個 MessageListener 的完美無狀態實現(應該如此!),然後配置幻燈片顯示 'maxConcurrentConsumers' 值設定為 1。此時,有人聲稱將該值設定為 1 以外的任何值都會導致執行緒安全問題。很抱歉這麼說,但這是徹頭徹尾的錯誤訊息。並行消費者設定決定了可用於接收訊息的執行緒數量,而 'maxConcurrentConsumers' 決定了消費者池在高負載下可以增長到什麼程度(隨著需求減少,消費者數量會降回設定為 'concurrentConsumers' 的值)。只要 MessageListener 本身是執行緒安全的,就可以增加此值來控制吞吐量。我個人永遠不會使用 MessageListener 來委派給“服務”以外的任何東西,這樣即使在(非常不可能的)情況下,我想要有一個有狀態的物件最終處理訊息的內容,那麼該目標物件也將配置一個池化目標源。 MessageListener 本身始終是執行緒安全的,因此 'concurrentConsumers' 和 'maxConcurrentConsumers' 的值可以按預期用於管理吞吐量。
這個主題引出了另一個重點。一個全面的比較將揭示 Spring 在此方面的另一個優點 - 即 Spring 的接聽器適配器。該適配器提供了從 JMS 訊息到簡單 Java Payload 的自動轉換,然後委派給任何 Spring 管理的物件來處理該 Payload。例如,在上面的配置中,“logger”和“tradeService”接聽器甚至不必實現 MessageListener 介面。如果他們不實現,那麼 Spring 會自動使用一個適配器包裝這些 POJO,該適配器會轉換訊息並確定要調用的方法。它甚至將返回值(如果有的話)轉換為 JMS 回覆訊息,並自動回覆到傳入訊息的 'reply-to' 屬性指定的目標。由於 JMS MessageListener 處理方法的返回值類型為 'void',因此從頭開始實現相同的行為非常困難。
public interface MessageListener {
void onMessage(Message message);
}
EJB 3 僅限於 @AroundInvoke,並且該範例透過攔截應用了一些簡單的審計。 Spring 範例顯示了 @Before 通知,因為審計只需要在方法執行之前發生(而不是周圍)。我很高興該範例強調需要在 EJB 3 端呼叫 context.proceed(),而 AspectJ @Before 通知要簡單得多。然而,我感到失望的是,一些與會者似乎認為 AspectJ 模型僅限於 @Before,因此 EJB 3 @AroundInvoke 更強大。為了在此處進行全面說明,我本來應該在 Spring 端包含一個 @Around 通知的範例 - 以澄清它受到支援,但並非總是必要的。
EJB 3 攔截模型的最大限制是應該被攔截的方法(或類別)直接被註釋,而 AOP 的基本目標之一是非侵入性 - 最終甚至支援對您控制之外的程式碼進行通知。鑑於這個目標,AspectJ 表達式語言可以說是在支援所有可以應用通知的可能結構的同時,盡可能清晰簡潔。雖然乍看之下可能很晦澀難懂,但它很容易學習。例如,它在概念上與正則表達式相似,但在範圍上更為有限(有關詳細資訊,請參閱 AspectJ 首頁上的表達式語言參考)。
關於這一點,我首先要指出的是,使用 Spring 有助於縮短開發/測試週期,以便開發人員的大部分時間都花在 IDE 而不是 部署到應用程式伺服器上,而 IDE 是很棒的工具。基於 Eclipse 的 Spring IDE 是一個非常有價值的附加元件,可用於 Spring 專案中的開發輔助,並且 IntelliJ 也在 IDEA 中提供 Spring 支援。至於部署工具,基於 Spring 的應用程式當然可以部署到任何容器中,並且由於 Spring 可以利用底層資源(DataSource、TransactionManager、JMS ConnectionFactory 等),因此它們的管理方式與部署在特定容器中的任何應用程式相同。 Spring 將任何物件公開為 JMX MBean(包括支援前面提到的 @ManagedResource)以及它對 JMX 通知/接聽器的支援,對於自訂監控和管理需求非常強大。
也就是說,顯然 Spring 可以從增加工具支援中受益。這就是為什麼最近才建立了 'Spring Tool Suite',將 Spring IDE、AJDT、AspectJ 和 Mylyn 整合在一起並發展成更多。有關更多資訊,請參閱 此處 提供的文章和連結。
每個人都知道可移植性“說起來容易做起來難”。雖然 EJB 3 在這方面可能比 EJB 2 不那麼痛苦(配置中的冗長性較少),但事實仍然是應用程式伺服器提供不同的功能,因此提供不同的配置選項。顯然,如果您要部署到應用程式伺服器,您可能應該利用某些特定功能。原始陳述的問題在於,它暗示了一些應用程式配置級別的事情,這使得 Spring 本質上不那麼可移植。相反,任何從一個應用程式伺服器遷移到另一個應用程式伺服器的 Spring 使用者都會同意,Spring 在這方面提供了大量的抽象。在上面的第 4 點中,我提到 Spring 的 JtaTransactionManager 在任何 Java EE 應用程式伺服器中使用自動偵測,並且同樣適用於 MBean 伺服器和 RMI 註冊表。同樣,當使用 JPA 時,Spring 會偵測 persistence.xml 並相應地創建 EntityManagerFactory。在所有這些情況下,Spring 元資料 - 無論是註釋(@PersistenceContext、@Transactional、@Resource 等)還是 XML ('jee:jndi-lookup' 等) 都與任何 EJB 3 應用程式一樣可移植。
即使超越了典型 EJB 3 應用程式的功能,最小的配置更改也能提供極大的便利。在這方面,Spring 實際上促進了更廣泛環境的可移植性:Tomcat、Jetty、獨立、Eclipse、IDEA 等等。我的建議是獲取 Spring 發行版本的 PetClinic 範例應用程式,並嘗試將 WAR 檔案建置和部署到多個應用程式伺服器中。然後,請注意,它也可以很容易地部署在 Tomcat 中,並且一旦您想在應用程式支援的不同資料存取策略之間切換時:JDBC、Hibernate 和 JPA,可移植性程度實際上遠遠超過 EJB 3 應用程式。仔細查看這些不同版本的不同配置檔案(位於 'samples/petclinic/war/WEB-INF' 目錄中)。尤其是 Spring 2.5 的新增功能,配置非常簡潔。請注意,在這些不同版本之間切換唯一需要的更改是 web.xml 中的一行,Spring 上下文在此處引導啟動。如果您想使用容器管理的 DataSource 運行,請使用 'jee:jndi-lookup' 元素。否則,有一個用於使用獨立 DataSource 的 bean 定義,並且實際的資料庫屬性會外部化到 jdbc.properties 中。
嗯,看起來我說的比我想像的要多 :). 我的目的是從 Spring 的角度提供一些客觀的澄清,我希望讀者能明顯感受到這一點。我知道這個簡報在 JUG 和會議上非常受歡迎,我認為這是一個重要的討論。許多 Java 開發人員對當今大量選擇感到不知所措,他們必須掌握所有必要的事實才能做出明智的決定。雖然我沒有在這裡強調它(並且您可能不希望我繼續說下去),但簡報以及這本書 (EJB 3 in Action) 的重點是 Spring 和 EJB 3 無需相互排斥。 Spring 可以在 EJB 應用程式中使用,EJB 可以從 Spring 應用程式中存取,並且 Spring 現在支援大多數相同的註釋:@Resource、@PersistenceContext、@PostConstruct、@PreDestroy、@EJB 和 @WebServiceRef。