Java-based Container Configuration
This section covers how to use annotations in your Java code to configure the Spring container. It includes the following topics:
Basic Concepts: @Bean
and @Configuration
The central artifacts in Spring’s new Java-configuration support are
@Configuration
-annotated classes and @Bean
-annotated methods.
The @Bean
annotation is used to indicate that a method instantiates, configures, and
initializes a new object to be managed by the Spring IoC container. For those familiar
with Spring’s <beans/>
XML configuration, the @Bean
annotation plays the same role as
the <bean/>
element. You can use @Bean
-annotated methods with any Spring
@Component
. However, they are most often used with @Configuration
beans.
Annotating a class with @Configuration
indicates that its primary purpose is as a
source of bean definitions. Furthermore, @Configuration
classes let inter-bean
dependencies be defined by calling other @Bean
methods in the same class.
The simplest possible @Configuration
class reads as follows:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun myService(): MyService {
return MyServiceImpl()
}
}
The preceding AppConfig
class is equivalent to the following Spring <beans/>
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
The @Bean
and @Configuration
annotations are discussed in depth in the following sections.
First, however, we cover the various ways of creating a spring container using by
Java-based configuration.
Instantiating the Spring Container by Using AnnotationConfigApplicationContext
The following sections document Spring’s AnnotationConfigApplicationContext
, introduced in Spring
3.0. This versatile ApplicationContext
implementation is capable of accepting not only
@Configuration
classes as input but also plain @Component
classes and classes
annotated with JSR-330 metadata.
When @Configuration
classes are provided as input, the @Configuration
class itself
is registered as a bean definition and all declared @Bean
methods within the class
are also registered as bean definitions.
When @Component
and JSR-330 classes are provided, they are registered as bean
definitions, and it is assumed that DI metadata such as @Autowired
or @Inject
are
used within those classes where necessary.
Simple Construction
In much the same way that Spring XML files are used as input when instantiating a
ClassPathXmlApplicationContext
, you can use @Configuration
classes as input when
instantiating an AnnotationConfigApplicationContext
. This allows for completely
XML-free usage of the Spring container, as the following example shows:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
As mentioned earlier, AnnotationConfigApplicationContext
is not limited to working only
with @Configuration
classes. Any @Component
or JSR-330 annotated class may be supplied
as input to the constructor, as the following example shows:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
The preceding example assumes that MyServiceImpl
, Dependency1
, and Dependency2
use Spring
dependency injection annotations such as @Autowired
.
Building the Container Programmatically by Using register(Class<?>…)
You can instantiate an AnnotationConfigApplicationContext
by using a no-arg constructor
and then configure it by using the register()
method. This approach is particularly useful
when programmatically building an AnnotationConfigApplicationContext
. The following
example shows how to do so:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.register(AppConfig::class.java, OtherConfig::class.java)
ctx.register(AdditionalConfig::class.java)
ctx.refresh()
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
Enabling Component Scanning with scan(String…)
To enable component scanning, you can annotate your @Configuration
class as follows:
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig {
...
}
1 | This annotation enables component scanning. |
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig {
// ...
}
1 | This annotation enables component scanning. |
Experienced Spring users may be familiar with the XML declaration equivalent from
Spring’s
|
In the preceding example, the com.acme
package is scanned to look for any
@Component
-annotated classes, and those classes are registered as Spring bean
definitions within the container. AnnotationConfigApplicationContext
exposes the
scan(String…)
method to allow for the same component-scanning functionality, as the
following example shows:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.scan("com.acme")
ctx.refresh()
val myService = ctx.getBean<MyService>()
}
Remember that @Configuration classes are meta-annotated
with @Component , so they are candidates for component-scanning. In the preceding example,
assuming that AppConfig is declared within the com.acme package (or any package
underneath), it is picked up during the call to scan() . Upon refresh() , all its @Bean
methods are processed and registered as bean definitions within the container.
|
Support for Web Applications with AnnotationConfigWebApplicationContext
A WebApplicationContext
variant of AnnotationConfigApplicationContext
is available
with AnnotationConfigWebApplicationContext
. You can use this implementation when
configuring the Spring ContextLoaderListener
servlet listener, Spring MVC
DispatcherServlet
, and so forth. The following web.xml
snippet configures a typical
Spring MVC web application (note the use of the contextClass
context-param and
init-param):
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
Using the @Bean
Annotation
@Bean
is a method-level annotation and a direct analog of the XML <bean/>
element.
The annotation supports some of the attributes offered by <bean/>
, such as:
* init-method
* destroy-method
* autowiring
* name
.
You can use the @Bean
annotation in a @Configuration
-annotated or in a
@Component
-annotated class.
Declaring a Bean
To declare a bean, you can annotate a method with the @Bean
annotation. You use this
method to register a bean definition within an ApplicationContext
of the type
specified as the method’s return value. By default, the bean name is the same as
the method name. The following example shows a @Bean
method declaration:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService() = TransferServiceImpl()
}
The preceding configuration is exactly equivalent to the following Spring XML:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
Both declarations make a bean named transferService
available in the
ApplicationContext
, bound to an object instance of type TransferServiceImpl
, as the
following text image shows:
transferService -> com.acme.TransferServiceImpl
You can also declare your @Bean
method with an interface (or base class)
return type, as the following example shows:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(): TransferService {
return TransferServiceImpl()
}
}
However, this limits the visibility for advance type prediction to the specified
interface type (TransferService
). Then, with the full type (TransferServiceImpl
)
known to the container only once, the affected singleton bean has been instantiated.
Non-lazy singleton beans get instantiated according to their declaration order,
so you may see different type matching results depending on when another component
tries to match by a non-declared type (such as @Autowired TransferServiceImpl
,
which resolves only once the transferService
bean has been instantiated).
If you consistently refer to your types by a declared service interface, your
@Bean return types may safely join that design decision. However, for components
that implement several interfaces or for components potentially referred to by their
implementation type, it is safer to declare the most specific return type possible
(at least as specific as required by the injection points that refer to your bean).
|
Bean Dependencies
A @Bean
-annotated method can have an arbitrary number of parameters that describe the
dependencies required to build that bean. For instance, if our TransferService
requires an AccountRepository
, we can materialize that dependency with a method
parameter, as the following example shows:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
The resolution mechanism is pretty much identical to constructor-based dependency injection. See the relevant section for more details.
Receiving Lifecycle Callbacks
Any classes defined with the @Bean
annotation support the regular lifecycle callbacks
and can use the @PostConstruct
and @PreDestroy
annotations from JSR-250. See
JSR-250 annotations for further
details.
The regular Spring lifecycle callbacks are fully supported as
well. If a bean implements InitializingBean
, DisposableBean
, or Lifecycle
, their
respective methods are called by the container.
The standard set of *Aware
interfaces (such as BeanFactoryAware,
BeanNameAware,
MessageSourceAware,
ApplicationContextAware, and so on) are also fully supported.
The @Bean
annotation supports specifying arbitrary initialization and destruction
callback methods, much like Spring XML’s init-method
and destroy-method
attributes
on the bean
element, as the following example shows:
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
class BeanOne {
fun init() {
// initialization logic
}
}
class BeanTwo {
fun cleanup() {
// destruction logic
}
}
@Configuration
class AppConfig {
@Bean(initMethod = "init")
fun beanOne() = BeanOne()
@Bean(destroyMethod = "cleanup")
fun beanTwo() = BeanTwo()
}
By default, beans defined with Java configuration that have a public You may want to do that by default for a resource that you acquire with JNDI, as its
lifecycle is managed outside the application. In particular, make sure to always do it
for a The following example shows how to prevent an automatic destruction callback for a
Java
Kotlin
Also, with |
In the case of BeanOne
from the example above the preceding note, it would be equally valid to call the init()
method directly during construction, as the following example shows:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne().apply {
init()
}
// ...
}
When you work directly in Java, you can do anything you like with your objects and do not always need to rely on the container lifecycle. |
Specifying Bean Scope
Spring includes the @Scope
annotation so that you can specify the scope of a bean.
Using the @Scope
Annotation
You can specify that your beans defined with the @Bean
annotation should have a
specific scope. You can use any of the standard scopes specified in the
Bean Scopes section.
The default scope is singleton
, but you can override this with the @Scope
annotation,
as the following example shows:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
@Scope
and scoped-proxy
Spring offers a convenient way of working with scoped dependencies through
scoped proxies. The easiest way to create
such a proxy when using the XML configuration is the <aop:scoped-proxy/>
element.
Configuring your beans in Java with a @Scope
annotation offers equivalent support
with the proxyMode
attribute. The default is no proxy (ScopedProxyMode.NO
),
but you can specify ScopedProxyMode.TARGET_CLASS
or ScopedProxyMode.INTERFACES
.
If you port the scoped proxy example from the XML reference documentation (see
scoped proxies) to our @Bean
using Java,
it resembles the following:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()
@Bean
fun userService(): Service {
return SimpleUserService().apply {
// a reference to the proxied userPreferences bean
setUserPreferences(userPreferences()
}
}
Customizing Bean Naming
By default, configuration classes use a @Bean
method’s name as the name of the
resulting bean. This functionality can be overridden, however, with the name
attribute,
as the following example shows:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
Bean Aliasing
As discussed in beans-beanname, it is sometimes desirable to give a single bean
multiple names, otherwise known as bean aliasing. The name
attribute of the @Bean
annotation accepts a String array for this purpose. The following example shows how to set
a number of aliases for a bean:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Configuration
class AppConfig {
@Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
fun dataSource(): DataSource {
// instantiate, configure and return DataSource bean...
}
}
Bean Description
Sometimes, it is helpful to provide a more detailed textual description of a bean. This can be particularly useful when beans are exposed (perhaps through JMX) for monitoring purposes.
To add a description to a @Bean
, you can use the
@Description
annotation, as the following example shows:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
fun thing() = Thing()
}
Using the @Configuration
annotation
@Configuration
is a class-level annotation indicating that an object is a source of
bean definitions. @Configuration
classes declare beans through public @Bean
annotated
methods. Calls to @Bean
methods on @Configuration
classes can also be used to define
inter-bean dependencies. See beans-java-basic-concepts for a general introduction.
Injecting Inter-bean Dependencies
When beans have dependencies on one another, expressing that dependency is as simple as having one bean method call another, as the following example shows:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne(beanTwo())
@Bean
fun beanTwo() = BeanTwo()
}
In the preceding example, beanOne
receives a reference to beanTwo
through constructor
injection.
This method of declaring inter-bean dependencies works only when the @Bean method
is declared within a @Configuration class. You cannot declare inter-bean dependencies
by using plain @Component classes.
|
Lookup Method Injection
As noted earlier, lookup method injection is an advanced feature that you should use rarely. It is useful in cases where a singleton-scoped bean has a dependency on a prototype-scoped bean. Using Java for this type of configuration provides a natural means for implementing this pattern. The following example shows how to use lookup method injection:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.setState(commandState)
return command.execute()
}
// okay... but where is the implementation of this method?
protected abstract fun createCommand(): Command
}
By using Java configuration, you can create a subclass of CommandManager
where
the abstract createCommand()
method is overridden in such a way that it looks up a new
(prototype) command object. The following example shows how to do so:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
val command = AsyncCommand()
// inject dependencies here as required
return command
}
@Bean
fun commandManager(): CommandManager {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return object : CommandManager() {
override fun createCommand(): Command {
return asyncCommand()
}
}
}
Further Information About How Java-based Configuration Works Internally
Consider the following example, which shows a @Bean
annotated method being called twice:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun clientService1(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientService2(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientDao(): ClientDao {
return ClientDaoImpl()
}
}
clientDao()
has been called once in clientService1()
and once in clientService2()
.
Since this method creates a new instance of ClientDaoImpl
and returns it, you would
normally expect to have two instances (one for each service). That definitely would be
problematic: In Spring, instantiated beans have a singleton
scope by default. This is
where the magic comes in: All @Configuration
classes are subclassed at startup-time
with CGLIB
. In the subclass, the child method checks the container first for any
cached (scoped) beans before it calls the parent method and creates a new instance.
The behavior could be different according to the scope of your bean. We are talking about singletons here. |
As of Spring 3.2, it is no longer necessary to add CGLIB to your classpath because CGLIB
classes have been repackaged under |
There are a few restrictions due to the fact that CGLIB dynamically adds features at
startup-time. In particular, configuration classes must not be final. However, as
of 4.3, any constructors are allowed on configuration classes, including the use of
If you prefer to avoid any CGLIB-imposed limitations, consider declaring your |
Composing Java-based Configurations
Spring’s Java-based configuration feature lets you compose annotations, which can reduce the complexity of your configuration.
Using the @Import
Annotation
Much as the <import/>
element is used within Spring XML files to aid in modularizing
configurations, the @Import
annotation allows for loading @Bean
definitions from
another configuration class, as the following example shows:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
@Configuration
class ConfigA {
@Bean
fun a() = A()
}
@Configuration
@Import(ConfigA::class)
class ConfigB {
@Bean
fun b() = B()
}
Now, rather than needing to specify both ConfigA.class
and ConfigB.class
when
instantiating the context, only ConfigB
needs to be supplied explicitly, as the
following example shows:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)
// now both beans A and B will be available...
val a = ctx.getBean<A>()
val b = ctx.getBean<B>()
}
This approach simplifies container instantiation, as only one class needs to be dealt
with, rather than requiring you to remember a potentially large number of
@Configuration
classes during construction.
As of Spring Framework 4.2, @Import also supports references to regular component
classes, analogous to the AnnotationConfigApplicationContext.register method.
This is particularly useful if you want to avoid component scanning, by using a few
configuration classes as entry points to explicitly define all your components.
|
Injecting Dependencies on Imported @Bean
Definitions
The preceding example works but is simplistic. In most practical scenarios, beans have
dependencies on one another across configuration classes. When using XML, this is not an
issue, because no compiler is involved, and you can declare
ref="someBean"
and trust Spring to work it out during container initialization.
When using @Configuration
classes, the Java compiler places constraints on
the configuration model, in that references to other beans must be valid Java syntax.
Fortunately, solving this problem is simple. As we already discussed,
a @Bean
method can have an arbitrary number of parameters that describe the bean
dependencies. Consider the following more real-world scenario with several @Configuration
classes, each depending on beans declared in the others:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig {
@Bean
fun accountRepository(dataSource: DataSource): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
There is another way to achieve the same result. Remember that @Configuration
classes are
ultimately only another bean in the container: This means that they can take advantage of
@Autowired
and @Value
injection and other features the same as any other bean.
Make sure that the dependencies you inject that way are of the simplest kind only. Also, be particularly careful with |
The following example shows how one bean can be autowired to another bean:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
lateinit var accountRepository: AccountRepository
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig(private val dataSource: DataSource) {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
Constructor injection in @Configuration classes is only supported as of Spring
Framework 4.3. Note also that there is no need to specify @Autowired if the target
bean defines only one constructor.
|
In the preceding scenario, using @Autowired
works well and provides the desired
modularity, but determining exactly where the autowired bean definitions are declared is
still somewhat ambiguous. For example, as a developer looking at ServiceConfig
, how do
you know exactly where the @Autowired AccountRepository
bean is declared? It is not
explicit in the code, and this may be just fine. Remember that the
Spring Tool Suite provides tooling that
can render graphs showing how everything is wired, which may be all you need. Also,
your Java IDE can easily find all declarations and uses of the AccountRepository
type
and quickly show you the location of @Bean
methods that return that type.
In cases where this ambiguity is not acceptable and you wish to have direct navigation
from within your IDE from one @Configuration
class to another, consider autowiring the
configuration classes themselves. The following example shows how to do so:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method!
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
In the preceding situation, where AccountRepository
is defined is completely explicit.
However, ServiceConfig
is now tightly coupled to RepositoryConfig
. That is the
tradeoff. This tight coupling can be somewhat mitigated by using interface-based or
abstract class-based @Configuration
classes. Consider the following example:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
@Configuration
interface RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository
}
@Configuration
class DefaultRepositoryConfig : RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(...)
}
}
@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class) // import the concrete config!
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
Now ServiceConfig
is loosely coupled with respect to the concrete
DefaultRepositoryConfig
, and built-in IDE tooling is still useful: You can easily
get a type hierarchy of RepositoryConfig
implementations. In this
way, navigating @Configuration
classes and their dependencies becomes no different
than the usual process of navigating interface-based code.
If you want to influence the startup creation order of certain beans, consider
declaring some of them as @Lazy (for creation on first access instead of on startup)
or as @DependsOn certain other beans (making sure that specific other beans are
created before the current bean, beyond what the latter’s direct dependencies imply).
|
Conditionally Include @Configuration
Classes or @Bean
Methods
It is often useful to conditionally enable or disable a complete @Configuration
class
or even individual @Bean
methods, based on some arbitrary system state. One common
example of this is to use the @Profile
annotation to activate beans only when a specific
profile has been enabled in the Spring Environment
(see beans-definition-profiles
for details).
The @Profile
annotation is actually implemented by using a much more flexible annotation
called @Conditional
.
The @Conditional
annotation indicates specific
org.springframework.context.annotation.Condition
implementations that should be
consulted before a @Bean
is registered.
Implementations of the Condition
interface provide a matches(…)
method that returns true
or false
. For example, the following listing shows the actual
Condition
implementation used for @Profile
:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
// Read the @Profile annotation attributes
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.acceptsProfiles(Profiles .of(*value as Array<String>))) {
return true
}
}
return false
}
return true
}
See the @Conditional
javadoc for more detail.
Combining Java and XML Configuration
Spring’s @Configuration
class support does not aim to be a 100% complete replacement
for Spring XML. Some facilities, such as Spring XML namespaces, remain an ideal way to
configure the container. In cases where XML is convenient or necessary, you have a
choice: either instantiate the container in an “XML-centric” way by using, for example,
ClassPathXmlApplicationContext
, or instantiate it in a “Java-centric” way by using
AnnotationConfigApplicationContext
and the @ImportResource
annotation to import XML
as needed.
XML-centric Use of @Configuration
Classes
It may be preferable to bootstrap the Spring container from XML and include
@Configuration
classes in an ad-hoc fashion. For example, in a large existing codebase
that uses Spring XML, it is easier to create @Configuration
classes on an
as-needed basis and include them from the existing XML files. Later in this section, we cover the
options for using @Configuration
classes in this kind of “XML-centric” situation.
Remember that @Configuration
classes are ultimately bean definitions in the
container. In this series examples, we create a @Configuration
class named AppConfig
and
include it within system-test-config.xml
as a <bean/>
definition. Because
<context:annotation-config/>
is switched on, the container recognizes the
@Configuration
annotation and processes the @Bean
methods declared in AppConfig
properly.
The following example shows an ordinary configuration class in Java:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
@Configuration
class AppConfig {
@Autowired
private lateinit var dataSource: DataSource
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService() = TransferService(accountRepository())
}
The following example shows part of a sample system-test-config.xml
file:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
The following example shows a possible jdbc.properties
file:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
fun main() {
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
val transferService = ctx.getBean<TransferService>()
// ...
}
In system-test-config.xml file, the AppConfig <bean/> does not declare an id
element. While it would be acceptable to do so, it is unnecessary, given that no other bean
ever refers to it, and it is unlikely to be explicitly fetched from the container by name.
Similarly, the DataSource bean is only ever autowired by type, so an explicit bean id
is not strictly required.
|
Because @Configuration
is meta-annotated with @Component
, @Configuration
-annotated
classes are automatically candidates for component scanning. Using the same scenario as
describe in the previous example, we can redefine system-test-config.xml
to take advantage of component-scanning.
Note that, in this case, we need not explicitly declare
<context:annotation-config/>
, because <context:component-scan/>
enables the same
functionality.
The following example shows the modified system-test-config.xml
file:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration
Class-centric Use of XML with @ImportResource
In applications where @Configuration
classes are the primary mechanism for configuring
the container, it is still likely necessary to use at least some XML. In these
scenarios, you can use @ImportResource
and define only as much XML as you need. Doing
so achieves a “Java-centric” approach to configuring the container and keeps XML to a
bare minimum. The following example (which includes a configuration class, an XML file
that defines a bean, a properties file, and the main
class) shows how to use
the @ImportResource
annotation to achieve “Java-centric” configuration that uses XML
as needed:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {
@Value("\${jdbc.url}")
private lateinit var url: String
@Value("\${jdbc.username}")
private lateinit var username: String
@Value("\${jdbc.password}")
private lateinit var password: String
@Bean
fun dataSource(): DataSource {
return DriverManagerDataSource(url, username, password)
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val transferService = ctx.getBean<TransferService>()
// ...
}