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

时间:2014-08-11 21:03:00

标签: java spring spring-java-config

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

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

例如ModuleAConfig如下所示:

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

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

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

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

如何以最少的重复实现这一目标?

我的一个想法是准备备用层次结构,其中C1Config继承自MainConfigC1ModuleAConfig来自ModuleAConfigC1SubModuleA1Config来自SubModuleA1ConfigC1SubModuleA1Config将覆盖返回someBean()的{​​{1}}方法。不幸的是,在Spring 4.0.6中我得到了类似的东西:

C1SomeBean

实际上 Overriding bean definition for bean 'someBean': replacing [someBean defined in class C1SubmoduleA1Config] with [someBean defined in class SubModuleA1Config] 类是从上下文而不是SomeBean返回的。这显然不是我想要的。

1 个答案:

答案 0 :(得分:9)

请注意,您无法覆盖@Import扩展配置类。

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

但是,@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(); 

   }
}

<强>更新

使用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
}

但是,由于每个客户都有一个单独的包,因此请考虑使用@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();
    }
}