Welcome to this lesson on Bean Scopes and Lifecycle! So far, we've covered what Spring and Spring Boot are, delved into the project structure, and understood the importance of files like application.properties
. We've also explored concepts such as Inversion of Control (IoC) and Dependency Injection (DI). We learned how to create simple beans without dependencies and, in the previous lesson, learned to create beans with dependencies. This lesson will explore the concepts of bean scopes and bean lifecycle, which are crucial for managing the state and lifecycle of beans in a Spring application.
In Spring, all beans are by default singletons, meaning only one instance of each bean exists in the application context. This single instance is reused wherever the bean is injected, making it efficient for stateless beans that can be used multiple times across the application without side effects. By ensuring that there is only one instance, Spring makes it possible to wire beans by type, as there won't be any ambiguity.
However, not all beans should be singletons. In scenarios where a class maintains some state and hence isn't safe for reuse, a different scope might be needed. Here are the main bean scopes in Spring:
- Singleton: One instance of the bean is created for the entire application.
- Prototype: A new instance is created each time the bean is requested.
- Session: In a web application, one instance is created for each session.
- Request: In a web application, one instance is created for each HTTP request.
You can declare a different scope using the @Scope
annotation alongside the @Component
or @Bean
annotation.
To define a prototype-scoped bean using the @Component
annotation, you need to utilize the @Scope
annotation. Here's how you can do it:
Kotlin1package com.example 2 3import org.springframework.context.annotation.Scope 4import org.springframework.stereotype.Component 5 6@Component 7@Scope("prototype") 8class PrototypeComponentBean { 9 init { 10 println("PrototypeComponentBean instance created.") 11 } 12}
In this example, each time a PrototypeComponentBean
is requested from the application context, a new instance is created. This approach is particularly useful for stateful beans that should not share the state with other instances.
Another way to create a prototype-scoped bean is by using the @Bean
annotation within a configuration class. This method allows for more customization during bean creation. Here's an example:
Kotlin1package com.example 2 3import org.springframework.context.annotation.Bean 4import org.springframework.context.annotation.Configuration 5import org.springframework.context.annotation.Scope 6 7@Configuration 8class BeanConfig { 9 10 @Bean 11 @Scope("prototype") 12 fun prototypeBean(): PrototypeBean { 13 return PrototypeBean() 14 } 15}
In this scenario, the prototypeBean
method is annotated with @Scope("prototype")
, ensuring that a new instance of PrototypeBean
is created each time this method is invoked. This approach is beneficial when you have more complex creation logic or configuration needs for your beans.
By understanding both methods, you can choose the one that best fits your specific requirements, whether it's the simplicity of @Component
or the flexibility of @Bean
.
To simplify understanding, let's compare the lifecycle of a Spring bean to the stages of a person's life:
- Bean Creation (Birth): Think of this as when a person is born. In Spring, a bean is created when the application starts up. Spring looks at the configuration (like
@Component
or@Bean
) and creates an instance of the bean. This is when the bean is "born." - Dependency Injection (Growing Up): As a person grows up, they learn and acquire things they need to live, like food, clothes, and education. After the bean is created, Spring injects all the necessary dependencies into it. These are the other objects (beans) that the bean needs to function properly. This process is like the bean "growing up" and getting what it needs to work.
- Initialization (Starting Life): Once a person is grown up, they start their adult life, doing their job or daily activities. After dependency injection, the bean goes through an initialization phase where it gets ready to do its work. If there’s any setup needed (like opening a file or establishing a database connection), this is when it happens. In Spring, you can do this setup using methods like
@PostConstruct
. - Bean Usage (Living Life): Now the person is living their life, doing their job, helping others, etc. The bean is now ready to be used by the application. It performs its tasks, interacts with other beans, and does whatever it’s designed to do as part of the application.
- Destruction (End of Life): Just like a person’s life comes to an end, a bean’s life also ends when it’s no longer needed. When the application shuts down, Spring will clean up the beans. If the bean needs to do any final tasks (like closing a file or releasing resources), it happens in this phase. You can use methods like
@PreDestroy
for this cleanup. This is the bean's "end of life."
Let's look at how to implement the bean lifecycle stages in code:
Kotlin1package com.example 2 3import org.springframework.beans.factory.DisposableBean 4import org.springframework.beans.factory.InitializingBean 5import org.springframework.stereotype.Component 6import jakarta.annotation.PostConstruct 7import jakarta.annotation.PreDestroy 8 9@Component 10class MyBean : InitializingBean, DisposableBean { 11 12 init { 13 println("Bean is being created (Constructor).") 14 } 15 16 @PostConstruct 17 fun postConstruct() { 18 println("Bean is being initialized (@PostConstruct).") 19 } 20 21 override fun afterPropertiesSet() { 22 println("Bean is fully initialized (afterPropertiesSet from InitializingBean).") 23 } 24 25 fun performTask() { 26 println("Bean is being used (performTask method).") 27 } 28 29 @PreDestroy 30 fun preDestroy() { 31 println("Bean is about to be destroyed (@PreDestroy).") 32 } 33 34 override fun destroy() { 35 println("Bean is destroyed (destroy from DisposableBean).") 36 } 37}
In this code:
- Birth: The bean is created using the constructor.
- Growing Up: Dependencies are injected.
- Starting Life:
@PostConstruct
andafterPropertiesSet
initialize the bean. - Living Life: The
performTask
method simulates the bean's activity. - End of Life:
@PreDestroy
anddestroy
handle bean destruction.
In simple applications, you don't need to use all these lifecycle methods. However, understanding them gives you comprehensive control over bean behavior.
In this lesson, we explored bean scopes and lifecycle in Spring, understanding the default singleton scope, when to use different scopes, and how to manage the lifecycle stages of a bean. Next, you'll get hands-on practice to reinforce these concepts. Happy coding!