Skip to content

Spring Bean Scope and Lifecycle

Published: at 03:31 PM
Loading...

The default scope of Spring Beans is Singleton, but it also supports modification to Prototype. So how do we implement this specifically?

Singleton

Let’s first understand what singleton means.

Let me give a somewhat inappropriate example. We’ll write a configuration class ArrayListConfig to put ArrayList under IOC container management, as follows:

@Configuration
public class ArrayListConfig {

    @Bean
    public ArrayList<String> arrayList() {
        return new ArrayList<String>();
    }

}

Alright, now that we’ve created this configuration class, let’s write a unit test:

@Autowired
private ArrayList<String> arrayList1;
@Autowired
private ArrayList<String> arrayList2;
@Test
void test1(){
    arrayList1.add("1");
    arrayList2.add("2");
    System.out.println(arrayList1);
    System.out.println(arrayList2);
}

So, what do you think the output will be?

image.png

Both output [1, 2]. This is Singleton. When the IOC container starts, it creates a Bean instance, and after that, all the objects we get through autowiring point to the same Bean instance, which is why they output the same content.

So how can we make these two ArrayLists point to different Bean instances? This requires us to use Prototype.

Prototype

Implementing prototype is simple. We just need to use the @Scope annotation, for example:

@Configuration
public class ArrayListConfig {

    @Bean
    @Scope("prototype")
    public ArrayList<String> arrayList() {
        return new ArrayList<String>();
    }

}

image.png

As shown above, we’ve now achieved the desired effect. This is prototype scope.

In fact, singleton is @Scope("singleton"), but we usually omit it.

@Scope can also be combined with @Component and other annotations to directly implement singleton or prototype when defining a class. I won’t demonstrate this here.

Bean Lifecycle

I wonder if you’ve ever thought about the lifecycle of Beans.

For example, when are the Beans managed by the IOC container created? Like the ArrayList above, is it created when we first use it, or is it created as soon as the container starts?

To facilitate testing, let me manually create a class and have IOC manage it, as follows:

@Component
public class Kunkun {
    public Kunkun() {
        System.out.println("Kunkun was born!");
    }
}

Then let’s write a unit test:

@Test
void test2(){
    
}

image.png

As shown above, we find that even without using Kunkun (we didn’t @Autowired a Kunkun), the Kunkun constructor was still called. This means that by default, the container initializes singleton Beans when it starts.

Sometimes, creating all Bean objects in advance can be a waste of resources. Can we do this: only create the Bean when we first use it, and then just get it from the IOC container for subsequent uses? Yes, we can. We just need to add the @Lazy annotation, for example:

@Component
@Lazy
public class Kunkun {
    public Kunkun() {
        System.out.println("Kunkun was born!");
    }
}

Now if we run the unit test Test2 again, there won’t be any output.

Now let’s modify the unit test as follows:

@Autowired
private Kunkun kunkun;

@Test
void test2(){
    
}

When we run it now, we find:

image.png

This shows that the Kunkun Bean object was created only when it was used.

Please note that everything we’ve discussed above is for singleton Beans. What about prototype Beans?

Let’s add @Scope("prototype") to the class, as follows:

package top.mygld.demo.test;

import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class Kunkun {
    public Kunkun() {
        System.out.println("Kunkun was born!");
    }
}

Now let’s not declare and create Kunkun, and see if the constructor appears when starting.

After testing, we find that there is indeed no output related to Kunkun’s constructor. This means that for prototype Beans, they are not initialized and created when the IOC container starts.

So let’s @Autowired two Kunkuns, as follows:

@Autowired
private Kunkun kunkun1;
@Autowired
private Kunkun kunkun2;
@Test
void test3(){

}

After running, the output is:

image.png

As you can see, the constructor was called successfully. We can easily conclude that for prototype Beans, lazy loading is the default. If you still want to add @Lazy, it won’t cause an error, but adding it has no real effect.

Now that we’ve discussed the creation of singleton and prototype Beans, let’s talk about destruction.

First, let’s look at singleton Bean destruction:

There’s an annotation @PreDestroy. Methods annotated with it will be automatically executed when the Bean is destroyed, for example:

@Component
public class Kunkun {
    public Kunkun() {
        System.out.println("Kunkun was born!");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("Kunkun is being destroyed~");
    }
}

Let’s write a unit test and @Autowired a Kunkun to see the effect:

image.png

As we can see, for singleton Beans, when the container is destroyed, the Bean instance is also destroyed.

Now let’s turn it into a prototype Bean and experiment:

package top.mygld.demo.test;

import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;

@Component
@Scope("prototype")
public class Kunkun {
    public Kunkun() {
        System.out.println("Kunkun was born!");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("Kunkun is being destroyed~");
    }
}

In the unit test, @Autowired two Kunkuns and test:

image.png

We find that only the constructor was called, but after the container was destroyed, the Bean objects were not destroyed. This is because for prototype Beans, the container doesn’t manage their destruction. You must manually force destruction, as follows:

@Autowired
private Kunkun kunkun1;
@Autowired
private Kunkun kunkun2;
@Test
void test3(){
    kunkun1.preDestroy();
    kunkun2.preDestroy();
}

Summary

  1. Default Scope

    • Spring Beans are singleton (Singleton) by default, sharing one instance across the entire container.
    • You can use @Scope("prototype") for “prototype (Prototype)” to create a new instance each time one is obtained.
  2. Creation Timing

    • Singleton Bean: Created when the container starts (when not @Lazy)
    • Prototype Bean: Created each time it’s obtained, lazy loading by default
    • @Lazy: Used to delay singleton Bean initialization, has no practical meaning for prototype Beans
  3. Destruction Mechanism

    • Singleton Bean: Automatically destroyed when the container closes, supports @PreDestroy or DisposableBean.destroy()
    • Prototype Bean: Not managed by the container for destruction, requires manual invocation of destruction methods
  4. Use Cases

    • Singleton: Globally shared objects, service classes, configuration classes
    • Prototype: Temporary objects, short-term use objects, frequently created computation or task objects
  5. Key Points

    • Singleton Beans have a long lifecycle, fully managed by the container
    • Prototype Beans have a short lifecycle, developers are responsible for resource release
    • @Lazy mainly optimizes singleton Bean initialization, reducing startup overhead

Previous Post
A Brief Exploration of Spring Transactions
Next Post
Injecting Third-Party Beans into IoC Container