昨晚我參加了新英格蘭 Java 使用者組 (NEJUG) 的一個會議,Reza Rahman 在會上介紹了 EJB 3 與 Spring 的“比較分析”。Reza 是 《EJB 3實戰》一書的作者之一。我很享受與 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 支援任何型別物件的 DI)與 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 的示例顯示使用 JPA 透過 entityManager 進行資料訪問,並且 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 輕鬆支援 Atomikos 和 JOTM 等獨立的 JTA 提供程式。當然,Spring 的事務管理器設定需要配置一個單一的 Bean 定義,但這確實是一次性成本——並且非常值得受益。
5. Spring 沒有有狀態應用正規化。
無狀態服務層的好處作為最佳實踐已經相當成熟,Spring 也擁抱了這一點。然而,Spring 確實提供了除單例之外的其他作用域。Spring 的“prototype”作用域為每次注入或查詢提供一個獨立的例項,並且 Spring 2.0 引入了 Web 作用域:“request”和“session”。作用域機制本身甚至是可以擴充套件的;可以將自定義作用域定義並對映到會話的概念。Spring 還透過 CommonsPoolTargetSource 支援簡單的物件池化,但物件池化很少是狀態管理的最佳解決方案。
更重要的是,Spring 透過 Spring Web Flow 為 Web 應用提供了非常健壯、高度可配置的狀態管理。在 Spring Web Flow 中,會話狀態是透明管理的,這與本次演講中聲稱開發者必須直接與 HTTP Session 互動來管理 Spring 應用中的狀態不同。此外,儲存庫配置是可插拔的,因此可以使用各種策略來物理儲存狀態(session、客戶端、後端快取等)。最後,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…