领先一步
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 版本提供了与 @PersistenceContext(用于注入 EntityManager)的完整 JPA 集成,以及使用 Spring 的 @Transactional 注解(支持与 @Stateless EJB 相同的语义,默认传播为 REQUIRED)的注解驱动的事务管理。我特别沮丧的是,比较中没有在两侧都包含 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 配置的替代方案。例如,此摘录取自 2.5 版本的 PetClinic 示例应用程序
<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 版本。
这个实际上被当作 Spring 的优势来介绍,但强调了配置开销。事实是,任何认真对待测试和敏捷开发的项目都需要支持“多个部署环境”。换句话说,这个特定主题通常会被歪曲,就好像它只适用于多个_生产_环境。实际上,在每个开发和测试周期中都必须部署到应用服务器是敏捷性的主要障碍。通常,Spring 用户会将其配置模块化,以便“基础设施”配置(例如 DataSource、TransactionManager、JMS ConnectionFactory)是分开的,并且动态属性是外部化的。由于 Spring 支持基于外部化属性替换“${占位符}”,因此包含不同的属性文件通常成为一个透明的问题。
我必须承认,这个让我最困扰。在比较幻灯片中,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。同样,如果您想添加一些直接 JDBC 用于数据访问(其中 ORM 过于复杂),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很容易支持Atomikos和JOTM等独立的JTA提供程序。当然,Spring的事务管理器设置需要配置一个单个bean定义,但这确实是一次性成本——并且非常值得。
无状态服务层的优势作为最佳实践已经得到很好的确立,Spring也接受了这一点。但是,Spring提供了除单例之外的其他作用域。Spring的“原型”作用域为每次注入或查找启用一个不同的实例,而Spring 2.0引入了web作用域:“请求”和“会话”。作用域机制本身甚至可以扩展;可以定义并将自定义作用域映射到对话的概念。Spring还支持使用CommonsPoolTargetSource进行简单的对象池化,但对象池化很少是状态管理的最佳解决方案。
更重要的是,Spring通过Spring Web Flow为web应用程序提供了非常强大、高度可配置的状态管理。与该演示文稿声称开发人员必须直接与HTTP会话交互以管理Spring应用程序中的状态相反,在这里,会话状态是透明管理的。此外,存储库配置是可插拔的,因此可以使用各种策略进行状态的物理存储(会话、客户端、后端缓存等)。最后,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”元素(如上所示)上可用,在逻辑上它将设置1次,但由为每个侦听器定义在内部创建的每个容器实例共享。
好吧,这确实是当晚最奇怪的时刻。代码幻灯片描绘了一个MessageListener的完美无状态实现(应该如此!),然后配置幻灯片显示“maxConcurrentConsumers”值设置为1。此时,有人指出将该值设置为除1以外的任何值都会导致线程安全问题。很抱歉说,但这是完全错误的信息。并发使用者设置决定了可用于接收消息的线程数量,“maxConcurrentConsumers”决定了在负载过重的情况下使用者池可以增长到什么程度(随着需求减少,使用者的数量会下降回设置为“concurrentConsumers”的值)。只要MessageListener本身是线程安全的,就可以增加此值以控制吞吐量。我个人永远不会将MessageListener用于除委托给“服务”以外的任何用途,以便即使在我(非常不可能)想要让最终处理消息内容的状态对象的情况下,该目标对象也将使用池化目标源进行配置。MessageListener本身将始终是线程安全的,因此“concurrentConsumers”和“maxConcurrentConsumers”的值可以按预期使用来管理吞吐量。
此主题提出了另一个要点。全面的比较将揭示Spring在这里的另一个优点——即Spring的侦听器适配器。适配器提供从JMS消息到简单Java有效负载的自动转换,然后委托给任何Spring管理的对象来处理该有效负载。例如,在上面的配置中,“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中,并且一旦您想要在应用程序支持的不同数据访问策略之间切换,其可移植性程度实际上远远超过EJB 3应用程序:JDBC、Hibernate和JPA。仔细查看这些不同版本的不同配置文件(位于'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。