领先一步
VMware 提供培训和认证,助您加速进步。
了解更多上个月我在土耳其进行了一次 Spring 核心培训。课程结束时,我讨论了一个应用程序的架构,一些参与者在完成课程后将要构建这个应用程序。这个应用程序将包含一个 ear 文件和几个 war 文件,并且提出了一个问题,是否有可能定义一个单独的 ApplicationContext,它可以作为所有 war 文件的 WebApplicationContexts 的共享父级。这个上下文将包含服务、DAO 和其他不特定于单个 web 模块的 bean 定义。
实际上,Spring 很容易就能做到这一点,但是课程和参考手册都没有详细解释如何在您的 Web 应用程序中使用此功能。因此,我编写了一个简短的示例应用程序来演示其工作原理,我将在我的第一篇博客文章中讨论它。
在典型的 Spring Web 应用程序中,您使用 ContextLoaderListener(或者,如果您使用的是 Servlet 2.2 / 2.3 容器,则使用 ContextLoaderServlet)来引导 WebApplicationContext。您通过 web.xml 中的上下文参数来配置此类的 ContextLoader。如果您使用过此功能,您可能熟悉contextConfigLocation参数,该参数允许您指定构成要构造的 WebApplicationContext 的文件。
事实证明,您可以使用另一个参数以声明方式获得所需的功能:parentContextKey。使用此参数,您指示 ContextLoader 使用另一个名为 ContextSingletonBeanFactoryLocator 的类来搜索由parentContextKey的值命名的 bean,该 bean 在名称与特定模式匹配的配置文件中定义。默认情况下,此模式是“classpath*:beanRefContext.xml”,这意味着类路径上所有名为 beanRefContext 的文件。(对于普通的 SingletonBeanFactoryLocator,它是“classpath*:beanRefFactory.xml”)此 bean 本身必须是 ApplicationContext,并且此上下文将成为 ContextLoader 创建的 WebApplicationContext 的父上下文。但是,如果此上下文已经存在,则将使用该上下文,并且不会创建新上下文(因此得名 SingletonBeanFactoryLocator)。
让我们看看这意味着什么:首先,我们的 ear 中需要一个单独的 jar,其中包含服务、DAO 等的代码。在此 jar 中,我们放置一个 beanRefContext.xml 文件,其中包含一个 ApplicationContext 的单个 bean 定义。通常,这将是一个 ClassPathXmlApplicationContext。然后,该 bean 定义将引用一个或多个“常规”bean 配置文件,其中包含服务 bean 和其他要由您的 war 文件中的代码使用的内容;类似于这样
<!-- contents of beanRefContext.xml:
notice that the bean id is the value specified by the parentContextKey param -->
<bean id="ear.context" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>services-context.xml</value>
</list>
</constructor-arg>
</bean>
最后,我们需要通过在每个 war 的 MANIFEST.MF 文件中使用Class-Path条目,使此 jar 在 war 文件的类路径上可用。
一个更简单的解决方案是跳过parentContextKey,并使用contextConfigLocation参数从每个 war 中加载共享 bean 定义文件。这样,每个 war 都将拥有自己的每个共享 bean 实例。对于简单的无状态 bean,例如典型的服务,这实际上是一个很好的解决方案。
但是,为每个 war 实例化每个共享 bean 的新实例可能会有几个缺点:一个常见的例子是创建 Hibernate SessionFactory。这通常是一个昂贵的过程,因此为了防止您的启动时间失控,所描述的解决方案将确保只执行一次此操作。拥有 SessionFactory 的单个实例的另一个优点是它可以安全地充当二级缓存:您不希望在单个应用程序中存在多个副本!通常,如果您有应该真正用作单例的有状态 bean(在 Spring 意义上,即每个应用程序一个实例而不是每个 JVM 一个实例),您应该将它们定义在由其他上下文访问的单个上下文中。
我已将示例包含在内,既有 ear 文件,也有 源代码。为了上传 ear,我不得不给它一个 .zip 扩展名:请在部署之前将文件重命名为 .ear!源代码实际上是一个 Eclipse 工作区,因此您可以轻松导入和查看它(它需要 WTP 并为 Spring IDE 配置)。所有需要的 Spring jar 都已包含在内。部署应用程序后,转到 URL /web1 和 /web2 以查看第一个和第二个 war 文件中 servlet 的输出。toString()服务将证明 war 确实使用了共享服务的同一个实例。
有关此功能的最佳信息可在 Spring 出色的 API 文档中找到:请查看 ContextLoader.loadParentContext 方法和 SingletonBeanFactoryLocator 类的 JavaDoc。这些文档包含有关如何配置 web.xml 以及如何编写 beanRefFactory.xml 的更多信息。