Spring Basics Wiring and Injecting Beans With Java Configuration
For new projects, Java configuration is preferred over XML-based configuration. In this post, we’re going to look at how to configure Spring with configuration in Java, instead of the traditional XML.
For XML-based configuration, see a future blog post.
The code
You can find the code from this blog post on GitLab.
Dependencies
The only dependency you need to get a Spring container running is
spring-context
. Add in Maven:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
Detecting beans by scanning for components
Create a class (which can have any name, here I chose AppConfig
) and
annotate it with @Configuration
and @ComponentScan
:
package com.relentlesscoding.wirebeans;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class AppConfig {}
By default, the @ComponentScan
will recursively scan the package in
which the AppConfig
class is declared. To change this, you can add a
package to the value
element
(@ComponentScan("com.relentlesscoding.wirebeans.beans")
) to scan only
that package. If it troubles you that this is not type safe and hinders
refactoring (it is a simple string after all), you can also pass Class
objects to the basePackageClasses
element. It will then scan the
package that class is part of. Some people even recommend to create
marker interfaces in each package for this purpose, but I think this
clutters up the source code too much.
Three ways to declare beans
Now Spring is able to detect our beans. We can declare beans in three ways:
- By annotating a class with
@Component
. - By annotating a method with a non-
void
return type with@Bean
in a class annotated with@Configuration
. - By annotating any method with a non-
void
return type with@Bean
.
Automatic configuration with @Component
The simplest way to declare a bean is by annotating a class with
@Component
:
@Component
public class Running implements Habit {
private final String name;
private final String description;
private final List<Streak> streaks;
// accessors omitted for brevity
}
If we do not specify the value
element of @Component
, the id of the
bean will be the lowercase name of the class, in this case running
. We
can use this id elsewhere to specify this particular bean, should
ambiguities arise.
Explicit configuration in class annotated with @Configuration
If you have control over the beans you are creating, i.e. you are
writing the source code, you would always go with automatic
configuration by annotating your bean classes with @Component
. If you
are creating a bean for a class from a library, you can define your
beans in your AppConfig
class:
@Configuration
@ComponentScan
public class AppConfig {
@Bean
public List<Streak> streaks() {
List<Streak> streaks = new ArrayList<>();
streaks.add(new PositiveStreak(LocalDate.now()));
return streaks;
}
}
Here, we defined a bean of type List<Streak>
that we can now inject
into any other bean by using the @Autowired
annotation (see below).
Lite Beans
Actually, we can declare any method with a non-void
return type to be
a @Bean
. If we declare a bean outside of a configuration class, it
will become a “lite bean”. Spring will still manage its life cycle and
scope and we can still autowire the bean into other beans, but when
invoking the method directly, it will just be a plain-old Java method
invocation without Spring magic. (Normally, Spring would create a proxy
around the bean and all invocations would go through the Spring
container. This would mean that by default only a single instance of the
bean would exist, for example. In “lite” mode, however, the annotated
method is just a factory method, and will happily instantiate a new
object every time it is called.)
Read more about lite beans here.
Using the declared beans
To use Spring’s dependency injection, you have a couple of options, all
of which involve annotating a method or field with @Autowired
. By
default, a matching bean of the specified type needs to exist in the
Spring context or else Spring will throw an exception. To make the
injection optional, set the required
element of @Autowired
to
false
.
Constructor injection
For mandatory dependencies, you should use constructor injection.
“Mandatory” means the bean would not make sense without the bean on
which it depends. For example, a HabitService
persists Habit
s to the
database. So a DAO or repository would be a mandatory dependency.
@Component
public class HabitService {
private final HabitRepository habitRepository;
@Autowired
public Running(HabitRepository habitRepository) {
this.habitRepository = habitRepository;
}
...
}
Field injection
Field injection should not be preferred, because it makes testing (e.g. with mocks) harder to pull off. In the following example we inject a dependency into a private field. When we would try to mock the dependency, we would have to deal with the access restriction.
@Component
public class HabitService {
@Autowired private HabitRepository habitRepository;
...
}
Setter injection
A third way to inject dependencies is through setter injection. Putting
@Autowired
on any method with one or more parameters will make Spring
look for appropriate bean candidates in the Spring context.
@Component
public class Running implements Habit {
private final String name = "Running";
private final String description = "Run 10 km every day";
private List<Streak> streaks;
@Autowired
public setStreaks(List<Streak> streaks) {
this.streaks = streaks;
}
...
}
Taking the application for a test run
package com.relentlesscoding.wirebeans;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class HabitTest {
@Autowired
Habit runningHabit;
@Test
public void runningHabitIsNotNull() {
Assert.assertNotNull(runningHabit);
}
@Test
public void runningHabitHasSingleStreak() {
Assert.assertEquals(1, runningHabit.getStreaks().size());
}
}
We can specify the application context by using the
@ContextConfiguration
annotation and filling in the classes
element.
The JUnit 4 annotation @RunWith
specifies the SpringRunner.class
(which is a convenience extension of the longer
SpringJUnit4ClassRunner
).
@ContextConfiguration
is part of the spring-test
library:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
<scope>test</scope>
</dependency>