Chapter 5. Modularizing configurations

While the simplest configuration may be expressed as a single class that exposes several beans, it is often desirable to modularize configurations for reuse and clarity.

5.1. Partitioning bean definitions across multiple @Configuration classes

The simplest technique for modularizing configurations is to simply split up large @Configuration classes containing many @Bean definitions into multiple smaller classes:

// monolithic configuration
@Configuration
public class AppConfig {
    @Bean
    public ServiceA serviceA() {
       // ...
    }

    @Bean
    public ServiceB serviceB() {
       // ...
    }

   // assume many bean definitions follow
}

The above configuration class might be supplied as a parameter to JavaConfigApplicationContext:

JavaConfigApplicationContext context = new JavaConfigApplicationContext(AppConfig.class);
ServiceA serviceA = context.getBean(ServiceA.class);
ServiceB serviceB = context.getBean(ServiceB.class);

We can easily partition this configuration such that bean definitions are spread across two classes, instead of one:

// partitioned configuration
@Configuration
public class AppConfigA {
    @Bean
    public ServiceA serviceA() {
       // ...
    }
}

@Configuration
public class AppConfigB {
    @Bean
    public ServiceB serviceB() {
       // ...
    }
}

Now simply supply both configuration classes to the constructor of JavaConfigApplicationContext:

JavaConfigApplicationContext context =
    new JavaConfigApplicationContext(AppConfigA.class, AppConfigB.class);
// both beans are still available in the resulting application context
ServiceA serviceA = context.getBean(ServiceA.class);
ServiceB serviceB = context.getBean(ServiceB.class);

5.2. Referencing externally defined beans with @ExternalBean

One configuration class may need to reference a bean defined in another configuration class (or in XML, for that matter). The @ExternalBean annotation provides just such a mechanism. When JavaConfig encounters a method annotated as @ExternalBean, it replaces that method definition with a lookup to the enclosing bean factory for a bean with the same name as the method name.

@Configuration
public class ConfigOne {
    @Bean
    public AccountRepository accountRepository() {
        // create and return an AccountRepository object
    }
}

@Configuration
public abstract class ConfigTwo {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository());
    }

    @ExternalBean
    public abstract AccountRepository accountRepository();
}

Given that both these configuration classes are supplied to the application context at runtime, JavaConfig will be able to resolve the 'accountRepository' @ExternalBean by name and everything will 'wire-up' accordingly.

JavaConfigApplicationContext context = new JavaConfigApplicationContext(ConfigOne.class, ConfigTwo.class);

5.3. Importing @Configuration classes with @Import

@Import represents JavaConfig's equivalent of XML configuration's <import/> element. One configuration class can import any number of other configuration classes, and their bean definitions will be processed as if locally defined.

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(...);
    }
}

@Configuration
@Import(DataSourceConfig.class)
public class AppConfig extends ConfigurationSupport {
    @Bean
    public void TransferService transferService() {
        return new TransferServiceImpl(getBean(DataSource.class);
    }
}

Importing multiple configurations can be done by supplying an array of classes to the @Import annotation

@Configuration
@Import({ DataSourceConfig.class, TransactionConfig.class })
public class AppConfig extends ConfigurationSupport {
    // bean definitions here can reference bean definitions in DataSourceConfig or TransactionConfig
}

5.4. ConfigurationSupport

As a convenience, @Configuration classses can extend ConfigurationSupport, primarily in order to facilitate easy lookup of beans from the enclosing BeanFactory / ApplicationContext.

@Configuration
public class AppConfig extends ConfigurationSupport {
    @Bean
    public Service serviceA() {
        DataSource dataSource = this.getBean(DataSource.class); // provided by base class
        // ...
    }
}

5.5. Externalizing values with @ExternalValue and @ResourceBundles

[Tip]Tip

Important: This functionality may change! We are currently evaluating approaches for best supporting externalized values. See SJC-74 for more details.

Access externally defined string values such as usernames, passwords, and the like using @ResourceBundles and @ExternalValue.

@ResourceBundles("classpath:/org/springframework/config/java/simple")
@Configuration
public abstract static class ConfigWithPlaceholders {
    @ExternalValue("datasource.username")
    public abstract String username();

    @Bean
    public TestBean testBean() {
        return new TestBean(username());
    }
}

The 'org/springframework/config/java/simple' argument to @ResourceBundles tells JavaConfig that there must be a file in the classpath at org/springframework/config/java/simple[locale].[properties|xml] from which key/value pairs can be read. For example:

datasource.username=scott
datasource.password=tiger
...

Multiple values can be supplied to @ResourceBundles:

@ResourceBundles({"classpath:/com/foo/myapp/simple", "classpath:/com/foo/myapp/complex"})
@Configuration
public class AppConfig {
    // ...
}

5.6. Nesting @Configuration classes

Consider the case of nested @Configuration classes:

@Configuration
public class OuterConfig {
    @Bean
    public Foo outerBean() {
        // ...
    }

    @Configuration
    public static class InnerConfig {
        @Bean
        public Bar innerBean() {
            // ...
        }
    }
}

Nested @Configuration classes will be processed as child ApplicationContexts. Perhaps better said, any declaring (outer) @Configuration classes will be processed as parent ApplicationContexts. Using the example above, Instantiate an ApplicationContext using InnerConfig as an argument

JavaConfigApplicationContext context = new JavaConfigApplicationContext(InnerConfig.class);
context.getBean("innerBean"); // locally defined beans are available
context.getBean("outerBean"); // as are beans defined in the declaring OuterConfig class.

Note that when supplying OuterConfig as the argument, InnerConfig is ignored entirely. If it were to be processed, it would become a child context, but its beans would would be inaccessible (parent contexts have no access to child context beans).

JavaConfigApplicationContext context = new JavaConfigApplicationContext(OuterConfig.class);
context.getBean("outerBean"); // works fine
context.getBean("innerBean"); // throws NoSuchBeanDefinitionException!