Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding beans in Java-based spring configuration hierarchy

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 @Configuration class which is imported by parent configuration using @Import. This creates the following hierarchy:

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

For example ModuleAConfig looks like this:

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

Let's say that SubModuleA1Config defines bean someBean of type SomeBean:

@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 SomeBean as someBean.

How can I achieve this with minimum duplication?

One of my ideas was to prepare alternative hierarchy with C1Config inheriting from MainConfig, C1ModuleAConfig from ModuleAConfig and C1SubModuleA1Config from SubModuleA1Config. C1SubModuleA1Config would override someBean() method returning C1SomeBean. Unfortunately with Spring 4.0.6 I get something like:

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

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

like image 693
Dawid Pytel Avatar asked Aug 11 '14 21:08

Dawid Pytel


1 Answers

Note that you cannot override @Import extending configuration classes.

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

However, @Configuration classes 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
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.

@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();
    }
}
like image 159
Jose Luis Martin Avatar answered Sep 30 '22 04:09

Jose Luis Martin