回應:EJB 3 和 Spring 對比分析
昨晚我參加了新英格蘭 Java 使用者組(NEJUG)的一次會議,Reza Rahman 在會上展示了關於EJB 3 和 Spring 的“對比分析”。Reza 是《EJB 3 in Action》的作者之一。我很高興見到 Reza,也很敬佩他能就一個可能引起爭議的話題進行演示。我也很讚賞他嘗試分析 EJB 3 和 Spring 的優缺點。然而,我感到有必要澄清一些他對 Spring 的介紹中並非完全準確的觀點,這些觀點讓我(以及其他與會者)認為本次演示帶有偏向 EJB 3 的傾向。公平地說,與固定規範版本不同,Spring 正在不斷發展,我在這裡指出的一些內容是新特性。另一方面,有些是 Spring 2.0 的特性,已經存在一年多了。我個人認為,“對比分析”必須考慮所比較產品最新穩定版本的最新特性集。我想不言而喻的是,我也可能有一點偏見,但我在這裡的動機是提供一個完全客觀的回應,以便簡報可以修改,以反映更“同類相比”的比較。我將對簡報的 10 個“主題”提供簡要回應。
1. EJB 使用註解表示元資料。Spring 使用 XML。
演示中提到,Spring 開始支援更多註解,但“還需要一段時間”。然而,Spring 2.0 版本提供了完整的 JPA 整合,使用 @PersistenceContext 注入 EntityManager,並支援基於註解的事務管理,使用 Spring 的 @Transactional 註解(支援與預設傳播 REQUIRED 的 @Stateless EJB 相同的語義)。特別讓我沮喪的是,本次比較沒有在雙方都包含 JPA(參見下面的第 3 點)。Spring 2.0 還引入了完整的基於註解的 AspectJ 支援(@Aspect、@Before、@After、@Around)和“stereotype”註解的概念。例如,@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 支援注入任何型別的物件)與 EJB 3 主要優勢(使用註解而非 XML)結合起來。Spring 2.5 還引入了一種更細粒度的基於註解的依賴注入模型,該模型基於 @Autowired 和(可擴充套件的)@Qualifier 註解。Spring 2.5 還將“stereotype”註解擴充套件到包括 @Service 和 @Controller。每個 stereotype 註解透過將其作為元註解應用來擴充套件通用的 @Component 註解。透過應用相同的技術,@Component 註解為使用者定義的 stereotypes 提供了一個擴充套件點。Spring 甚至可以自動檢測這些帶註解的元件,作為 XML 配置的替代方案。例如,以下摘錄來自 PetClinic 示例應用的 2.5 版本:
<context:component-scan base-package="org.springframework.samples.petclinic.web" />
對於 web 控制器來說,不需要額外的 XML 配置,因為它們使用了基於註解的依賴注入和請求對映註解。我之所以指出這一點,是因為演示特別強調了 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 版本的文章和部落格。
2. Spring 允許您支援多種部署環境,但需要更多配置。
這一點實際上被作為 Spring 的一個優點提出,但強調了配置開銷。事實是,任何認真對待測試和敏捷開發的專案都需要支援“多種部署環境”。換句話說,這個話題常常被扭曲,彷彿只適用於多個生產環境。實際上,在每個開發和測試周期都必須部署到應用伺服器,這是敏捷性的主要障礙。通常,Spring 使用者會將配置模組化,以便“基礎設施”配置(例如 DataSource、TransactionManager、JMS ConnectionFactory)是分開的,並且動態屬性是外部化的。由於 Spring 支援根據外部化屬性替換 '${placeholders}',因此包含不同的屬性檔案通常會成為一個透明的問題。
3. EJB 使用 JPA,Spring 使用 Hibernate
我必須承認這一點最讓我困擾。在對比幻燈片中,EJB 3 的示例展示了透過 entityManager 進行資料訪問的 JPA,並且 entityManager 例項是透過 @PersistenceContext 註解提供的。另一方面,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 事務管理和異常轉換方面以一致的方式進行。這為使用超出 JPA 限制的 Hibernate 特性提供了機會,例如 Hibernate 的“criteria”查詢 API。同樣地,如果您想在 ORM 過於繁重的地方新增一些直接的 JDBC 進行資料訪問,Spring 也支援這樣做——即使在與 Hibernate 或 JPA 資料訪問相同的事務中呼叫。
4. Spring 不做任何假設,您必須提供配置。
一個具體的例子是事務管理器的定義。演示中提到,您必須瞭解容器供應商級別的細節才能配置 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 定義,但這確實是一次性的開銷——而且非常值得。
5. Spring 沒有有狀態的應用正規化。
無狀態服務層的好處作為最佳實踐已經相當成熟,Spring 也接受這一點。然而,Spring 確實提供了除 singleton 之外的其他作用域。Spring 的“prototype”作用域為每次注入或查詢提供一個不同的例項,Spring 2.0 引入了 web 作用域:“request”和“session”。作用域機制本身甚至是可以擴充套件的;可以將自定義作用域定義並對映到會話概念。Spring 也支援使用 CommonsPoolTargetSource 進行簡單的物件池化,但物件池化很少是狀態管理的最佳解決方案。
更重要的是,Spring 透過 Spring Web Flow 為 Web 應用提供了非常健壯、高度可配置的狀態管理功能。在那裡,會話狀態是透明管理的,這與本次演示中聲稱開發者必須直接與 HTTP Session 互動來管理 Spring 應用中的狀態的說法是矛盾的。此外,儲存庫配置是可插拔的,因此可以使用各種策略來物理儲存狀態(會話、客戶端、後端快取等)。最後,Spring Web Flow 的最新進展包括對擴充套件持久化上下文的支援以及對 JSF 的完全整合支援。
6. Spring 需要為每個 MessageListener 配置一個容器。
Spring 2.5 提供了一個新的 'jms' 名稱空間,極大地簡化了訊息監聽器的配置。請注意,不需要為每個監聽器單獨配置一個容器。多個監聽器共享配置,並且廣泛使用了智慧預設設定:
<jms:listener-container>
<jms:listener destination="queue.confirm" ref="logger" method="log"/>
<jms:listener destination="queue.order…