Spring Basics: Wiring and Injecting Beans with Java Configuration

For new projects, Java configuration is preferred over XML-based configuration. 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 lifecycle 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 Habits 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>

Leave a Reply

Your email address will not be published. Required fields are marked *