在多 WAR Spring 应用中使用共享父应用上下文

工程 | Joris Kuipers | 2007 年 6 月 11 日 | ...

上个月我在土耳其做了一个 Core Spring 培训。课程结束时,我讨论了一个应用程序的架构,一些参与者将在完成课程后构建该应用程序。此应用程序将包含一个 ear 文件,其中包含多个 war 文件,并且提出了一个问题,是否可以定义一个单一的 ApplicationContext,它可以作为所有 war 文件的 WebApplicationContext 的共享父级。此上下文将包含服务、DAO 和其他非特定于单个 Web 模块的 bean 定义。

实际上,Spring 使这件事变得非常容易,但是课程和参考手册都没有详细解释如何从 Web 应用程序中使用此功能。因此,我编写了一个简短的示例应用程序来说明它是如何工作的,我将在我的第一篇博客文章中对此进行讨论。

ContextLoader 和 SingletonBeanFactoryLocator 类

在一个典型的 Spring Web 应用程序中,您使用 ContextLoaderListener(或者,如果您使用的是 Servlet 2.2 / 2.3 容器,则使用 ContextLoaderServlet)来引导 WebApplicationContext。您可以通过 web.xml 中的上下文参数来配置此类使用的 ContextLoader。如果您使用过此功能,您可能熟悉contextConfigLocation参数,该参数允许您指定哪些文件构成要构造的 WebApplicationContext。

事实证明,还有另一个参数可以用来以声明方式获得所需的功能:parentContextKey。使用此参数,您可以指示 ContextLoader 使用另一个名为 ContextSingletonBeanFactoryLocator 的类来搜索一个 bean,该 bean 的名称由parentContextKey的值指定,该值定义在一个名称与特定模式匹配的配置文件中。默认情况下,此模式为“classpath*:beanRefContext.xml”,表示 classpath 上的所有名为 beanRefContext 的文件。(对于普通的 SingletonBeanFactoryLocator,它是“classpath*:beanRefFactory.xml”)这个 bean 必须是 ApplicationContext 本身,并且此上下文将成为 ContextLoader 创建的 WebApplicationContext 的父上下文。但是,如果此上下文已经存在,则将使用该上下文,并且不会创建新的上下文(因此名称为 SingletonBeanFactoryLocator)。

让我们看看这意味着什么:首先,我们需要在 ear 中单独的 jar,其中包含服务、DAO 等的代码。在此 jar 中,我们放置一个 beanRefContext.xml 文件,该文件包含 ApplicationContext 的单个 bean 定义。通常,这将是一个 ClassPathXmlApplicationContext。然后,该 bean 定义将引用一个或多个“常规”bean 配置文件,这些文件包含 war 文件中的代码要使用的服务 bean 和其他内容; 像这样


<!-- 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>

最后,我们需要使用Class-Path在每个 war 的 MANIFEST.MF 文件中的条目,使这个 jar 在 war 文件的 classpath 上可用。

为什么要使用这个?

一个更简单的解决方案是跳过parentContextKey并使用contextConfigLocation参数从每个 war 加载共享 bean 定义文件。这样,每个 war 将拥有每个共享 bean 的自己的实例。对于简单的无状态 bean,例如典型的服务,这实际上是一个不错的解决方案。

但是,为每个 war 实例化每个共享 bean 的新实例可能会有几个缺点:一个常见的例子是创建 Hibernate SessionFactory。这通常是一个昂贵的过程,因此为了防止您的启动时间失控,所描述的解决方案将确保只执行一次此操作。拥有 SessionFactory 的单个实例的另一个优点是,它可以安全地充当二级缓存:您不希望单个应用程序中存在多个副本!一般来说,如果您有应该真正用作单例(在 Spring 意义上,即每个应用程序一个实例,而不是每个 JVM)的状态 bean,您应该在其他上下文访问的单个上下文中定义它们。

示例

我已经包含了示例,包括 ear 文件源代码。为了上传 ear 文件,我必须给它一个 .zip 扩展名:请在部署之前将文件重命名为 .ear!该源代码实际上是一个 Eclipse 工作区,因此您可以轻松导入和查看它(它需要 WTP 并为 Spring IDE 配置)。所需的所有 Spring jar 都已包含在内。部署该应用程序后,转到 URL /web1 和 /web2 以查看第一个和第二个 war 文件中的 servlet 的输出。toString()服务的 toString() 将证明 wars 确实使用共享服务的同一实例。

更多信息

有关此功能的最佳信息位于 Spring 的出色 API 文档中:请查看 ContextLoader.loadParentContext 方法和 SingletonBeanFactoryLocator 类的 JavaDoc。 这些文档包含有关如何配置 web.xml 以及如何编写 beanRefFactory.xml 的更多信息。

获取 Spring 新闻资讯

保持与 Spring 新闻资讯的连接

订阅

遥遥领先

VMware 提供培训和认证来加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中为 OpenJDK™、Spring 和 Apache Tomcat® 提供支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部