Appearance
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:
- How do you tell Spring to create a bean? (
@Component,@Bean) - How does Spring find the classes to create beans for?
- When is a bean created? (eager, lazy)
- 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");
}
}@Configurationmarks a class that contains bean definitions.@Beanon 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 @BeanTwo 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
| When | Which beans | |
|---|---|---|
| Eager | at application startup | singleton beans, the default |
| Lazy | only when the bean is first needed | beans 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 msStep 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:
- checks whether an
Orderbean already exists, and if so injects it; - otherwise creates it first, then injects it;
- and if no
Orderbean is defined anywhere, fails at startup ("required a bean of type '...Order' that could not be found").
text
initializing user
initializing orderOrder 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 injectedWhy 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 closingNext: 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
- What is a bean, and what does the IoC container do?
- What does "convention over configuration" mean for
@Component? - A
@Componentclass has only the constructorUser(String username, String email). What happens at startup, and how do you fix it? - What do
@Configurationand@Beaneach mark? What decides the bean's name? Userhas@Component, and an@Beanmethod also returns aUser. How manyUserbeans exist? What happens to@Autowired User myUser;, and to@Autowired User user;?- How does Spring find the classes to create beans for? Where does scanning start by default?
- Which beans are created eagerly, and which lazily?
Orderis@Lazy, but its constructor runs at startup. Why?- Why can a constructor not use a field-injected dependency, and where should that code go instead?
- Put in order:
@PreDestroy, construct,@PostConstruct, inject dependencies, use.
Answers
- A Java object managed by Spring. The container creates the beans, holds them and manages their lifecycle, so your code never calls
newfor them. - You give no instructions; Spring uses its convention of calling the constructor.
- Startup fails: Spring must call that constructor and looks for beans of type
Stringto pass, finding none. Remove@Componentand create the object in a@Beanmethod that passes the values. @Configuration: a class holding bean definitions.@Bean: a method whose return value Spring manages as a bean. The method name becomes the bean name.- Two (
userandcreateUserBean).myUserfails: a single bean was required but 2 were found.userworks: with several candidates, Spring falls back to the bean whose name matches the field name. - With
@ComponentScan, included in@SpringBootApplication, which scans a package and its sub-packages for components;@Beanmethods inside@Configurationclasses are registered too. By default from the main class's package. - Singleton beans (the default) eagerly, at startup.
@Lazybeans and prototype beans when first needed. - An eager bean (
User) depends on it, so the container must create it to inject it. Lazy means "not before it is needed". - Field injection happens after the constructor runs, so the field is still
nullthere. Put the code in a@PostConstructmethod. - Construct, inject dependencies,
@PostConstruct, use,@PreDestroy.