1. Overview

The Spring framework lets us configure the application context in different ways which include XML-based, Java-based and annotation-based approaches.

In this tutorial, we're going to investigate Spring's Java-based configuration.

2. @Bean Annotation

Let's start with creating beans.

For this purpose, we can use the @Bean annotation. More specifically, we should annotate instance returning methods with the @Bean annotation to create beans. When Spring finds a @Bean annotated method, it registers the returned instance as a bean.

Now let's look at the usage:

public class ClassService {

    public List<Student> getStudents() {
        List<Student> students = new ArrayList<>();
        students.add(new Student("John"));
        students.add(new Student("Sarah"));
        return students;
    }
}

Here, we have a plain Java class. Note that we aren't using any Spring annotation. In order to create a bean for this class we should create a method returning an instance of ClassService:

@Configuration
public class SchoolConfiguration {

    @Bean
    public ClassService classService() {
        return new ClassService();
    }
}

Here, we're declaring classService() method and annotating it with @Bean annotation. Note that this in contrast to the annotation-based configuration where we are annotating classes with the @Component annotation.

We're also using the @Configuration annotation which we'll investigate next.

3. @Configuration Annotation

Now let's look at where we should define our @Bean annotated methods.

Spring requires us to put @Bean annotated methods into @Configuration annotated classes. Configuration classes act as a container for bean definitions and also lives as a bean in the ApplicationContext.

Let's look at the usage:

public class SchoolService {

    private final ClassService classService;

    public SchoolService(ClassService classService) {
        this.classService = classService;
    }

    // Other methods
}

Here, we have another class without any Spring annotation.

We'll also use the @Bean annotation to create a bean for SchoolService:

@Configuration
public class SchoolConfiguration {

    @Bean
    public SchoolService schoolService() {
        return new SchoolService(classService());
    }

    @Bean
    public ClassService classService() {
        return new ClassService();
    }
}

There are a few things to note here. Firstly, we're defining our SchoolService bean creating another method - schoolService(). Secondly, the SchoolService class requires a ClassService instance as a constructor argument. So we're passing classService() to the constructor. This provides a similar result as the usage of the @Autowired annotation in the annotation-based configuration.

Moreover, the usage of the @Autowired annotation is not restricted to the @Component annotated classes:

public class SchoolService {

    @Autowired
    private TeacherService teacherService;

    private final ClassService classService;

    public SchoolService(ClassService classService) {
        this.classService = classService;
    }

    // Other methods
}

Here we're defining TeacherService as a dependency and annotating the field with @Autowired annotation. If Spring finds a bean for TeacherService, it will inject it to the SchoolService bean:

@Configuration
public class SchoolConfiguration {

    @Bean
    public TeacherService teacherService() {
        return new TeacherService();
    }

    // Other bean definitions
}

Here, we're defining the bean for TeacherService, so Spring can inject it as an @Autowired target.

4. @ComponentScan Annotation

Now, let's look at how we can manage bean scanning in a Java-based configuration.

The @ComponentScan annotation enables us to define how Spring should look for the bean definitions. Although it is not required for Java-based annotation, it helps us to capture multiple @Configuration classes:

@ComponentScan
public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);

        SchoolService schoolService = applicationContext.getBean(SchoolService.class);
        schoolService.teachStudents();

        applicationContext.close();
    }
}

Here, we're annotating the Application class with @Configuration. As a result, it scans all @Configuration beans.

In our case where we have only one @Configuration class, we can also start the ApplicationContext without @ComponentScan:

public class Application {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(SchoolConfiguration.class);

        SchoolService schoolService = applicationContext.getBean(SchoolService.class);
        schoolService.teachStudents();

        applicationContext.close();
    }
}

Here, we aren't using the @ComponentScan annotation and directly specifying the SchoolConfiguration class as the holder of bean definitions.

5. Summary

In this tutorial, we've looked at the Java-based configuration for Spring. We've investigated @Bean, @Configuration, and @ComponentScan annotations.

As always the source code for all examples in this tutorial is available on Github.