Skip to content
Chapter 06 · Spring Boot

Beans, the IoC Container and the Bean Lifecycle ​

The word "bean" has come up in every chapter so far: the controller, service and repository from chapter 3 were beans, and none of them was created with new. This chapter explains what a bean is, how one is created, and everything that happens to it, from application startup until the application shuts down. Each point is shown with a small experiment: change one thing, start the application, read the output.

What a bean is ​

  • A bean is a Java object managed by the Spring container. It is an ordinary object; the only difference is who creates and manages it.
  • The IoC container (Inversion of Control container) creates the beans, holds them, and manages their whole lifecycle: creation, initialization, use, destruction. Your code does not call new: control over object creation is inverted from your code to Spring.

The questions this chapter answers, in order:

  1. How do you tell Spring to create a bean? (@Component, @Bean)
  2. How does Spring find the classes to create beans for?
  3. When is a bean created? (eager, lazy)
  4. What happens to a bean from start to end? (the lifecycle)

1. Creating a bean ​

There are two ways: @Component on the class, or a @Bean method. Usually @Component is enough; one situation needs @Bean.

Way 1: @Component ​

java
@Component
public class User {

    private String username;
    private String email;

    public User() {
        System.out.println("initializing user");
    }

    // getters and setters
}

@Component tells Spring: create an object of this class and manage it. It follows convention over configuration: you give Spring no instructions on how to build the object, so it uses its convention, which is to call the constructor, here new User().

@Controller, @RestController, @Service and @Repository are specialised @Components (each is annotated with @Component), so classes carrying them become beans too. That is how the classes in chapter 3 became beans.

When @Component is not enough ​

Give User only a constructor with parameters (Java then adds no default constructor):

java
@Component
public class User {

    private String username;
    private String email;

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }
}

Start the application. It fails:

text
APPLICATION FAILED TO START

Description:
Parameter 0 of constructor in com.example.demo.User required a bean of type 'java.lang.String' that could not be found.

Spring has to call the only constructor there is, and it does not know what to pass as username and email. It tries to treat them as dependencies (find a bean of type String), finds none, and stops.

You need to tell Spring how to build the object. That is what @Bean is for.

Way 2: @Bean ​

Remove @Component from User, and write the construction yourself in a configuration class:

java
@Configuration
public class AppConfig {

    @Bean
    public User createUserBean() {
        return new User("defaultusername", "defaultemail");
    }
}
  • @Configuration marks a class that contains bean definitions.
  • @Bean on a method says: call this method and manage what it returns as a bean. The method name is up to you (it becomes the bean's name); the return type is what matters.

Now Spring calls createUserBean() at startup and manages the User it returns. @Bean is the way to create beans when you must supply constructor values, and also for classes you cannot annotate, such as classes from a library.

Several beans of the same type ​

Two @Bean methods returning User create two User beans, each managed by the container. Likewise, @Component on User plus an @Bean method for it does not make one "win": Spring creates both. With User having a no-argument constructor and the @Bean method above, startup prints:

text
User() via @Component
User(defaultusername, defaultemail) via @Bean

Two beans, named user (from @Component, the class name with a lower-case first letter) and createUserBean (from the method). A class that then asks for "a User" fails:

text
Field myUser in com.example.demo.Consumer required a single bean, but 2 were found:
	- user: defined in ... User.class
	- createUserBean: defined by method 'createUserBean' in class path resource [.../AppConfig.class]

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

(Had the field been named user, Spring would have used the bean whose name matches the field name as a fallback.) Chapter 7 shows how to choose with @Primary and @Qualifier.

2. How Spring finds the beans ​

An application has thousands of classes. How does Spring know which ones to create beans for?

@ComponentScan tells Spring to search a package and all its sub-packages for classes annotated @Component (or @Service, @Repository, @Controller, @RestController, @Configuration, which are all components):

java
@SpringBootApplication
@ComponentScan(basePackages = "com.example.learningspringboot")
public class LearningspringbootApplication { ... }

You rarely write this. @SpringBootApplication already contains @ComponentScan, and by default it starts from the package of the main class. That is why the main class sits in the base package: everything below it is scanned.

@Configuration classes are components too, so the scan finds them, and Spring then registers every @Bean method inside them.

A class outside the scanned packages is invisible: its @Component does nothing.

3. When a bean is created ​

WhenWhich beans
Eagerat application startupsingleton beans, the default
Lazyonly when the bean is first neededbeans marked @Lazy, and prototype beans

Scope decides how many instances of a bean exist; the default scope is singleton (one instance). Chapter 8 covers scopes in full. For now: unless you say otherwise, a bean is a singleton and is created at startup.

java
@Lazy
@Component
public class Order {
    public Order() {
        System.out.println("initializing order");
    }
}

@Lazy says: don't create this at startup; create it the first time something needs it. Section 4 shows what "needs it" means.

4. The bean lifecycle ​

Build one example step by step: a User bean that depends on the lazy Order bean above.

Step 1: the container starts ​

When the application starts, Spring Boot creates the IoC container. Its concrete form is an ApplicationContext; in a web application the log shows it:

text
Initializing Spring embedded WebApplicationContext
Root WebApplicationContext: initialization completed in 652 ms

Step 2: the container finds the beans ​

Using @ComponentScan and the @Configuration classes, as in section 2. It finds User and Order.

Step 3: construct the bean ​

java
@Component
public class User {
    public User() {
        System.out.println("initializing user");
    }
}

User is a singleton without @Lazy, so it is constructed at startup. The log shows initializing user. Order is @Lazy and nobody needs it yet, so its constructor does not run.

Step 4: inject dependencies ​

Now User needs an Order:

java
@Component
public class User {

    @Autowired
    Order order;

    public User() {
        System.out.println("initializing user");
    }
}

@Autowired asks the container to inject a bean of that type. For each @Autowired dependency the container:

  1. checks whether an Order bean already exists, and if so injects it;
  2. otherwise creates it first, then injects it;
  3. and if no Order bean is defined anywhere, fails at startup ("required a bean of type '...Order' that could not be found").
text
initializing user
initializing order

Order is @Lazy, yet it was created at startup, right after User: an eager bean needed it. "Lazy" means not before it is needed; here it was needed immediately. Remove the @Autowired Order order; field and initializing order disappears from the startup log.

(Field injection, as here, is one of three ways to inject; chapter 7 compares them and recommends constructor injection.)

Step 5: @PostConstruct ​

The bean is now constructed and has its dependencies. A method annotated @PostConstruct runs at exactly this point, before the bean is used:

java
@PostConstruct
public void initialize() {
    System.out.println("post construct: order is " + (order != null ? "injected" : "missing"));
}
text
initializing user
initializing order
post construct: order is injected

Why not do this work in the constructor? When the constructor runs, field-injected dependencies are still null; injection happens after construction. @PostConstruct is the first moment the bean is complete, so it is the place for setup that uses the dependencies: filling a cache or a map, checking configuration, logging.

Step 6: use the bean ​

The bean is ready. Your code (or another bean) calls its methods; this is most of its life.

Step 7: @PreDestroy ​

When the container shuts down, it destroys its beans. A method annotated @PreDestroy runs just before that: the place to release resources, such as closing a database connection.

java
@PreDestroy
public void preDestroy() {
    System.out.println("pre destroy: user bean is about to be destroyed");
}

To see it without stopping a server by hand, close the container yourself in main (only for this demonstration; normally the container closes when the application shuts down):

java
@SpringBootApplication
public class LearningspringbootApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(LearningspringbootApplication.class, args);
        context.close();
    }
}

The whole run ​

The final User:

java
@Component
public class User {

    @Autowired
    Order order;

    public User() {
        System.out.println("initializing user");
    }

    @PostConstruct
    public void initialize() {
        System.out.println("post construct: order is " + (order != null ? "injected" : "missing"));
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("pre destroy: user bean is about to be destroyed");
    }
}

Output, with the Order from section 3:

text
initializing user                                   ← step 3: construct User
initializing order                                  ← step 4: Order needed, so created and injected
post construct: order is injected                   ← step 5
Started LearningspringbootApplication in 0.872 seconds
pre destroy: user bean is about to be destroyed     ← step 7: container closing

Next: Step 4 used @Autowired on a field. Chapter 7 covers dependency injection properly: the problem it solves, the three ways to inject (field, setter, constructor), the problems you can run into, and which way to prefer.

Check yourself ​

  1. What is a bean, and what does the IoC container do?
  2. What does "convention over configuration" mean for @Component?
  3. A @Component class has only the constructor User(String username, String email). What happens at startup, and how do you fix it?
  4. What do @Configuration and @Bean each mark? What decides the bean's name?
  5. User has @Component, and an @Bean method also returns a User. How many User beans exist? What happens to @Autowired User myUser;, and to @Autowired User user;?
  6. How does Spring find the classes to create beans for? Where does scanning start by default?
  7. Which beans are created eagerly, and which lazily?
  8. Order is @Lazy, but its constructor runs at startup. Why?
  9. Why can a constructor not use a field-injected dependency, and where should that code go instead?
  10. Put in order: @PreDestroy, construct, @PostConstruct, inject dependencies, use.
Answers
  1. A Java object managed by Spring. The container creates the beans, holds them and manages their lifecycle, so your code never calls new for them.
  2. You give no instructions; Spring uses its convention of calling the constructor.
  3. Startup fails: Spring must call that constructor and looks for beans of type String to pass, finding none. Remove @Component and create the object in a @Bean method that passes the values.
  4. @Configuration: a class holding bean definitions. @Bean: a method whose return value Spring manages as a bean. The method name becomes the bean name.
  5. Two (user and createUserBean). myUser fails: a single bean was required but 2 were found. user works: with several candidates, Spring falls back to the bean whose name matches the field name.
  6. With @ComponentScan, included in @SpringBootApplication, which scans a package and its sub-packages for components; @Bean methods inside @Configuration classes are registered too. By default from the main class's package.
  7. Singleton beans (the default) eagerly, at startup. @Lazy beans and prototype beans when first needed.
  8. An eager bean (User) depends on it, so the container must create it to inject it. Lazy means "not before it is needed".
  9. Field injection happens after the constructor runs, so the field is still null there. Put the code in a @PostConstruct method.
  10. Construct, inject dependencies, @PostConstruct, use, @PreDestroy.