【问题标题】:How to achieve conditional resource import in a Spring XML context?如何在 Spring XML 上下文中实现条件资源导入?
【发布时间】:2011-03-03 09:55:21
【问题描述】:

我想要实现的是能够“动态地”(即基于配置文件中定义的属性)启用/禁用子 Spring XML 上下文的导入。

我想像这样:

<import condition="some.property.name" resource="some-context.xml"/>

属性在哪里被解析(为布尔值),当为 true 时,上下文被导入,否则不是。

到目前为止我的一些研究:

  • 编写自定义 NamespaceHandler(和相关类),以便我可以在自己的命名空间中注册自己的自定义元素。例如:&lt;myns:import condition="some.property.name" resource="some-context.xml"/&gt;

    这种方法的问题是我不想从 Spring 复制整个资源导入逻辑,而且我不清楚我需要委派什么来执行此操作。

  • 覆盖DefaultBeanDefinitionDocumentReader 以扩展“导入”元素解析和解释的行为(在importBeanDefinitionResource 方法中发生)。但是我不确定在哪里可以注册这个扩展。

【问题讨论】:

  • 与其做条件导入,不如使用类路径扫描,只部署需要的配置?我发现条件导入更复杂,并且在查看已部署的应用程序时更难以确定配置/未配置的内容。
  • 如何定义“所需配置”?我们有部分功能很好地模块化并在加载上下文时自动激活(白板模式)。但是我们需要一种机制来动态地(阅读:在安装/配置时)激活和停用这些功能。它是一种轻量级的插件系统。

标签: java xml spring


【解决方案1】:

在 Spring 4 之前,您可以使用标准 Spring 组件最接近的是:

<import resource="Whatever-${yyzzy}.xml"/>

其中${xyzzy} 从系统属性中插入一个属性。 (我使用了一个 hacky 自定义版本的上下文加载器类,它在开始加载过程之前将其他地方的属性添加到系统属性对象。)

但是您也可以避免导入许多不必要的东西……并使用各种技巧来仅实例化必要的 bean。这些技巧包括:

  • 占位符和属性替换
  • 使用新的 Spring 表达式语言选择不同的 bean,
  • 目标名称中带有占位符的 bean 别名,
  • 延迟 bean 初始化,以及
  • 智能豆工厂。

【讨论】:

  • 我不想真正触及相关模块的上下文并以这种方式阻止 bean 实例化。我真的很想保留模块的上下文边界并在该级别上切换。我曾考虑过资源 URI 的属性替换器,但正如您所说,只考虑系统属性,它只允许我切换到不同的上下文,而不是禁用一个。也许我必须提供一个通用的、空的“disabled-module-context.xml”?
  • 这仅适用于系统属性。导入时尚未加载上下文占位符中的属性。 (stackoverflow.com/questions/5253546/…)
  • @JanGoyvaerts - 我知道。见我的第二句话。顺便说一句,这在 2.5.5 及以后“有效”。
【解决方案2】:

您可以在自己的 ContextLoaderListener 中覆盖 contextInitialized(javax.servlet.ServletContextEvent event) 并在这样调用 super.contextInitialized(event) 之前设置所需的系统属性

package com.mypackage;
import org.springframework.web.context.ContextLoaderListener;
public class MyContextLoaderListener extends ContextLoaderListener {
    public void contextInitialized(javax.servlet.ServletContextEvent event) {
        System.setProperty("xyz", "import-file-name.xml");
        super.contextInitialized(event);
    }
}

然后将 ContextLoaderListener 替换为 web.xml 中的 MyContextLoaderListener

<listener>
    <listener-class>com.mypackage.MyContextLoaderListener</listener-class>
</listener>

现在你可以在你的 spring.xml 中使用了

<import resource="${xyz}" /> 

我希望这会有所帮助。

【讨论】:

    【解决方案3】:

    现在完全可以使用 Spring 4。

    在您的主应用程序内容文件中

    <bean class="com.example.MyConditionalConfiguration"/>
    

    MyConditionalConfiguration 看起来像

    @Configuration
    @Conditional(MyConditionalConfiguration.Condition.class)
    @ImportResource("/com/example/context-fragment.xml")
    public class MyConditionalConfiguration {
        static class Condition implements ConfigurationCondition {
             @Override
             public ConfigurationPhase getConfigurationPhase() {
                 return ConfigurationPhase.PARSE_CONFIGURATION;
             }
             @Override
             public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
                 // only load context-fragment.xml if the system property is defined
                 return System.getProperty("com.example.context-fragment") != null;
             }
        }
    }
    

    最后,您将想要包含的 bean 定义放在 /com/example/context-fragment.xml 中

    JavaDoc for @Conditional

    【讨论】:

    • 非常酷!感谢您在所有这些时间之后更新。很高兴他们终于为此做了点什么。
    • 我们也无法在此处获取占位符。只能使用 System.property。
    • 最初的问题是关于条件导入,而不是使用占位符。所以这实际上是正确的答案。
    【解决方案4】:

    另一个需要考虑的 Spring 3.0:

     <alias name="Whatever" alias=""Whatever-${yyzzy}" />
    

    ${xyzzy} 从系统属性中插入一个属性。

    【讨论】:

      【解决方案5】:

      另一种选择是让您的应用加载位于 /conf 文件夹中的 modules-config.xml 文件,并在安装/配置阶段对其进行编辑以取消注释您要加载的模块。

      这是我与作为不同集成模块容器的 Web 应用程序一起使用的解决方案。 Web 应用程序与所有不同的集成模块一起分发。一个 modules-config.xml 被放置在 tomcat 的 /conf 文件夹中,并且 conf 文件夹被添加到类路径中(通过 catalina.properties/common.loader 属性)。我的网络应用 webapp-config.xml 有一个 &lt;import resource="classpath:/modules-config.xml"/&gt; 来加载它。

      【讨论】:

        【解决方案6】:

        如前所述,如果您使用 Spring 3.1+,这可以通过配置文件轻松完成

        <!-- default configuration - will be loaded if no profile is specified -->
        <!-- This will only work if it's put at the end of the configuration file -->
        <!-- so no bean definitions after that -->
        <beans profile="default">
            <import resource="classpath:default.xml" />
        </beans>
        <!-- some other profile -->
        <beans profile="otherProfile">
            <import resource="classpath:other-profile.xml" />
        </beans>
        

        otherProfile 可以很容易地用例如激活

        mvn install -Dspring.profiles.active=otherProfile
        

        如果您在测试中使用不同的配置文件,只需添加 -DforkMode=never 以确保测试将在同一 VM 中运行,因此参数 spring.profiles.active 不会丢失

        【讨论】:

        • 配置文件很棒,但是调用 Condition 的其他一些实现呢?这可能吗?
        【解决方案7】:

        为了记录,Robert Maldon 在这篇文章中解释了如何完成 bean 的条件定义:http://robertmaldon.blogspot.com/2007/04/conditionally-defining-spring-beans.html。复制到这里有点长(另外我觉得还是不应该复制粘贴他的文章)。

        适用于您的示例的这种方法的最终结果是:

        <condbean:cond test="${some.property.name}">
          <import resource="some-context.xml"/>
        </condbean:cond>
        

        它当然不像 Stephen C 的解决方案那么简单,但它更强大。

        【讨论】:

          【解决方案8】:

          在 Spring 3.1.x 中,您可以使用 bean profiles 来实现条件资源导入和 bean 实例化。如果您使用的是早期版本,这当然无济于事:)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-12-28
            相关资源
            最近更新 更多