昨晚我参加了新英格兰 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…