领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多上个月我在土耳其进行了一次核心 Spring 培训。在课程结束时,我讨论了一个应用程序的架构,一些参与者将在完成课程后构建该应用程序。此应用程序将包含一个 ear 文件,其中包含几个 war 文件,并且提出了一个问题,即是否可以定义一个单一的 ApplicationContext,该上下文可用作所有 war 文件的 WebApplicationContext 的共享父级。此上下文将保存服务、DAO 和其他不特定于单个 Web 模块的 bean 的 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>
最后,我们需要使用Class-Path每个 war 的 MANIFEST.MF 文件中的条目使此 jar 在 war 文件的类路径上可用。
一个更简单的解决方案是跳过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()服务将证明 war 确实使用了共享服务的同一实例。
有关此功能的最佳信息在 Spring 优秀的 API 文档中:查看 ContextLoader.loadParentContext 方法和 SingletonBeanFactoryLocator 类的 JavaDoc。这些文档包含有关如何配置 web.xml 和如何编写 beanRefFactory.xml 的更多信息。