在基于Java的spring配置层次结构中重写bean

在基于Java的spring配置层次结构中重写bean,java,spring,spring-java-config,Java,Spring,Spring Java Config,假设我们有一个可以为某些客户定制的应用程序。应用程序使用基于Java的spring配置(又称Java配置)进行依赖项注入。应用程序由模块及其子模块组成。每个模块和子模块都有自己的@Configuration类,该类由父配置使用@Import导入。这将创建以下层次结构: MainConfig ----------+---------------- .... | | M

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

                MainConfig
          ----------+----------------   ....
          |                         |
    ModuleAConfig               ModuleBConfig
      |--------------------|
      |                    |
    SubModuleA1Config    SubModuleA2Config
例如,
moduleConfig
如下所示:

@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

如何以最少的重复实现这一点

我的一个想法是用继承自
MainConfig
C1Config
、继承自
moduleconfig
c1moduleconfig
和继承自
SubModuleA1Config
C1SubModuleA1Config
来准备替代层次结构
C1SubModuleA1Config
将覆盖返回
C1SomeBean
someBean()
方法。不幸的是,在Spring 4.0.6中,我得到了如下结果:

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

实际上,
SomeBean
类是从上下文而不是
C1SomeBean
返回的。这显然不是我想要的。

请注意,您不能覆盖
@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(); 

   }
}
更新

使用导入选择器:

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
}

不过,由于每个客户都有一个单独的包,所以也要考虑使用<代码> @ CypTrimeStudio。这将选择当前的配置类,不需要额外的配置属性

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

我更喜欢将特定于客户的配置放在一个地方,而不是分散在所有不同的配置类中。此外,如果有3个以上的客户,这样的代码看起来不太好。反对的第三个论点是,我交付的每个包都必须为所有客户提供所有代码(除非我使用了使解决方案更加丑陋的反射)。我只想向客户提供core+C1代码1。但是,
@ImportSelector
可能有助于实现这种模块化。您将如何应用它来获得结果?使用它的例子很少,关于导入选择器,我看到了这个想法,但是IMHO在配置类中仍然存在继承问题,非客户bean覆盖客户bean。可能更好的办法是在非客户配置已经加载时,实现
delferredimportselector
以在最后执行导入。这样,客户配置将覆盖非客户配置。关于“工厂解决方案”,如果我有一组定义良好的bean可以被覆盖,它就会工作。就我而言,这是真的。因此接受了答案。一个修正:
@组件公共类C1CustomerFactory…
@DawidBytel哦!thanx用于修复。请注意,在配置类中扩展和重写bean方法是合法的。问题是,从超类导入的内容也会被解析。