在基于 Java 的 spring 配置层次结构中覆盖 bean

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25252374/
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-11-02 07:40:49  来源:igfitidea点击:

Overriding beans in Java-based spring configuration hierarchy

javaspringspring-java-config

提问by Dawid Pytel

Let's assume we have an application that can be customized for some customers. The application is using Java-based spring configuration (a.k.a. Java config) for dependency injection. The application consists of modules and their submodules. Each module and submodule has its own @Configurationclass which is imported by parent configuration using @Import. This creates the following hierarchy:

假设我们有一个可以为某些客户定制的应用程序。该应用程序使用基于 Java 的 spring 配置(又名 Java 配置)进行依赖注入。该应用程序由模块及其子模块组成。每个模块和子模块都有自己的@Configuration类,由父配置使用@Import. 这将创建以下层次结构:

                MainConfig
          ----------+----------------   ....
          |                         |
    ModuleAConfig               ModuleBConfig
      |--------------------|
      |                    |
    SubModuleA1Config    SubModuleA2Config

For example ModuleAConfiglooks like this:

例如ModuleAConfig看起来像这样:

@Configuration
@Import({SubModuleA1Config.class, SubModuleA2Config.class})
public class ModuleAConfig {
    // some module level beans
}

Let's say that SubModuleA1Configdefines bean someBeanof type SomeBean:

假设SubModuleA1Config定义someBean了 SomeBean 类型的 bean:

@Configuration
public class SubModuleA1Config {
    @Bean
    public SomeBean someBean() { return new SomeBean(); }
}

Now I want to customize the application for Customer1 (C1) - I want to use C1SomeBean(extending SomeBean) instead of SomeBeanas someBean.

现在我想为 Customer1 (C1) 自定义应用程序 - 我想使用C1SomeBean(extending SomeBean) 而不是SomeBeanas someBean

How can I achieve this with minimum duplication?

我怎样才能以最少的重复实现这一目标?

One of my ideas was to prepare alternative hierarchy with C1Configinheriting from MainConfig, C1ModuleAConfigfrom ModuleAConfigand C1SubModuleA1Configfrom SubModuleA1Config. C1SubModuleA1Configwould override someBean()method returning C1SomeBean. Unfortunately with Spring 4.0.6 I get something like:

我的一个想法是准备替代层次结构,C1Config继承自MainConfigC1ModuleAConfigModuleAConfigC1SubModuleA1ConfigSubModuleA1ConfigC1SubModuleA1Config将覆盖someBean()方法返回C1SomeBean。不幸的是,在 Spring 4.0.6 中,我得到了类似的信息:

   Overriding bean definition for bean 'someBean': replacing [someBean defined in class C1SubmoduleA1Config] with [someBean defined in class SubModuleA1Config]

and indeed SomeBeanclass is returned from context instead of C1SomeBean. This is clearly not what I want.

实际上SomeBeanclass 是从上下文而不是C1SomeBean. 这显然不是我想要的。

回答by Jose Luis Martin

Note that you cannot override @Importextending configuration classes.

请注意,您不能覆盖@Import扩展配置类。

If you want to select which imports to use at runtime, you could use a @ImportSelectorinstead.

如果要选择在运行时使用哪些导入,可以改用 a @ImportSelector

However, @Configurationclasses are not more that spring (scoped) managed factories so as you already have a factory method for someBean you don't need to go even further:

但是,@Configuration类并不比 spring(范围)管理的工厂更多,因此您已经拥有 someBean 的工厂方法,您不需要更进一步:

@Configuration
public class SubModuleA1Config {

@Autowired
private Environment env;

@Bean
public SomeBean someBean() {
      String customerProperty = env.getProperty("customer");
      if ("C1".equals(customerProperty))  
        return new C1SomeBean();

      return new SomeBean(); 

   }
}

Update

更新

Using a ImportSelector:

使用导入选择器:

class CustomerImportSelector implements ImportSelector, EnvironmentAware {

    private static final String PACKAGE = "org.example.config";
    private static final String CONFIG_CLASS = "SubModuleConfig";

    private Environment env;

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String customer = env.getProperty("customer");
        return new String[] { PACKAGE +  "." + customer + "." + CONFIG_CLASS };
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }
}

@Configuration
@Import(CustomerImportSelector.class) 
public class ModuleAConfig {
    // some module level beans
}

However, as every customer has a a separate package, consider also using @ComponentScan. This will pick the configuration class present and don't need a extra configuration property.

但是,由于每个客户都有一个单独的包,因此还可以考虑使用@ComponentScan. 这将选择存在的配置类并且不需要额外的配置属性。

@Configuration
@ComponentScan(basePackages="org.example.customer")
public class SubModuleA1Config {

    @Autowired
    private CustomerFactory customerFactory;

    @Bean
    public SomeBean someBean() {
        return customerFactory.someBean();
   }
}

public interface CustomerFactory {
    SomeBean someBean();
}

@Component
public class C1CustomerFactory implements CustomerFactory {

    @Override
    public SomeBean someBean() {
        return new C1SomeBean();
    }
}