Java 如何在 Spring XML 上下文中实现条件资源导入?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3035630/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 15:50:36  来源:igfitidea点击:

How to achieve conditional resource import in a Spring XML context?

javaxmlspring

提问by Boris Terzic

What I would like to achieve is the ability to "dynamically" (i.e. based on a property defined in a configuration file) enable/disable the importing of a child Spring XML context.

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

I imagine something like:

我想象这样的事情:

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

Where the property is resolved (to a boolean) and when true the context is imported, otherwise it isn't.

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

Some of my research so far:

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

  • Writing a custom NamespaceHandler (and related classes) so I can register my own custom element in my own namespace. For example: <myns:import condition="some.property.name" resource="some-context.xml"/>

    The problem with this approach is that I do not want to replicate the entire resource importing logic from Spring and it isn't obvious to me what I need to delegate to to do this.

  • Overriding DefaultBeanDefinitionDocumentReaderto extend the behaviour of the "import" element parsing and interpretation (which happens there in the importBeanDefinitionResourcemethod). However I'm not sure where I can register this extension.
  • 编写自定义 NamespaceHandler(和相关类),以便我可以在自己的命名空间中注册自己的自定义元素。例如:<myns:import condition="some.property.name" resource="some-context.xml"/>

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

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

采纳答案by ptomli

This is now completely possible, using Spring 4.

现在完全可以使用 Spring 4。

In your main application content file

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

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

And the MyConditionalConfiguration looks like

而 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;
         }
    }
}

And then finally, you put the bean definitions you want included in the /com/example/context-fragment.xml

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

See the JavaDoc for @Conditional

请参阅@ConditionalJavaDoc

回答by Stephen C

Prior to Spring 4, the closest you can get using standard Spring components is:

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

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

where ${xyzzy}interpolates a property from the system properties. (I use a hacky custom version of the context loader class that adds properties from other places to the system properties object before starting the loading process.)

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

But you can also get away with importing lots of unnecessary stuff ... and use various tricks to only cause the necessary beans to be instantiated. These tricks include:

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

  • placeholder and property substitution
  • selecting different beans using the new Spring expression language,
  • bean aliases with placeholders in the target name,
  • lazy bean initialization, and
  • smart bean factories.
  • 占位符和属性替换
  • 使用新的 Spring 表达式语言选择不同的 bean,
  • 目标名称中带有占位符的 bean 别名,
  • 延迟 bean 初始化,以及
  • 智能豆工厂。

回答by espinchi

For the record, Robert Maldon explains how to accomplish conditional definition of beans in this post: http://robertmaldon.blogspot.com/2007/04/conditionally-defining-spring-beans.html. It is a bit long to copy it here (besides, I don't think I should copy-paste his article anyway).

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

The end result with this approach, adapted for your example, is:

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

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

It is certainly not so simple as Stephen C's solution, but it is much more poweful.

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

回答by teabot

With Spring 3.1.x you can use bean profilesto achieve conditional resource import and bean instantiation. This is of course of no help if you are using an earlier version :)

在 Spring 3.1.x 中,您可以使用bean 配置文件来实现有条件的资源导入和 bean 实例化。如果您使用的是早期版本,这当然没有帮助:)

回答by Marko Vranjkovic

As mentioned earlier, this can be easily accomplished with profiles if you're using Spring 3.1+

如前所述,如果您使用的是 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 can be easily activated with e.g.

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

mvn install -Dspring.profiles.active=otherProfile

if you're using different profiles in tests, just add -DforkMode=neverto make sure that the tests will run inside same VM, therefore the param spring.profiles.activewont be lost

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

回答by ScottD

Another option is to have your app load a modules-config.xml file that is located in the /conf folder and edit it during the install/config phase to uncomment the modules you want loaded.

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

This is the solution I'm using with a web application that serves as a container for different integration modules. The web application is distributed with all the different integration modules. A modules-config.xml is placed in tomcat's /conf folder and the conf folder is added to the classpath (via catalina.properties/common.loader property). My web app webapp-config.xml has a <import resource="classpath:/modules-config.xml"/>to get it loaded.

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

回答by hawkeye

Another one to consider for Spring 3.0:

Spring 3.0 要考虑的另一个问题:

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

where ${xyzzy}interpolates a property from the system properties.

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

回答by szezso

You can override contextInitialized(javax.servlet.ServletContextEvent event) in your own ContextLoaderListener and set required System property before super.contextInitialized(event) called like this

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

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);
    }
}

And than replace ContextLoaderListener to MyContextLoaderListener in your web.xml

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

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

Now you can use in your spring.xml

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

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

I hope this will help.

我希望这将有所帮助。