Spring Boot widget - SmartInitializingSingleton

Posted by SeenGee on Tue, 14 Apr 2020 21:20:41 +0200

Preface

We often see or use InitializingBean (or @PostConstruct) for an initialization process of beans, but sometimes we find that InitializingBean has some scenarios that are not applicable.

For example, we have the following Dog class

@Service
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Dog {

    public void makeSound() {
        System.out.println("bark!");
    }
}

This Dog is a prototype Bean, and each time we get this class from BeanFactory, a new class is created.

Then we have a Person class that needs a dog to bark for initialization

@Service
public class Person implements InitializingBean, BeanFactoryAware {

    private BeanFactory beanFactory;
    private List<Dog> managedDogs = new LinkedList<>();

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Dog dog = beanFactory.getBean(Dog.class);
        dog.makeSound();
    }

    public void addDog(Dog dog) {
        managedDogs.add(dog);
    }
}

There's nothing to do so far. Run it and you'll find "bark!" normally.Output of.

There was a BeanPostProcessor for Dog that happened to depend on Person

@Component
public class DogPostProcessor implements BeanPostProcessor {

    @Autowired
    private Person person;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!(bean instanceof Dog)) {
            return null;
        }
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dog.class);
        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
            if (method.getName().equals("makeSound")) {
                System.out.println("Notice that the dog is about to start barking!");
            }
            return method.invoke(bean, args);
        });
        Dog proxyDog = (Dog) enhancer.create();
        person.addDog(proxyDog);
        return proxyDog;
    }
}

In this DogPostProcessor, a precondition is made to the makeSound method, which predicts that the dog will start barking.Finally, the dog is managed by the Person class.

At this time, we run the program again and found that there was no output "noticed the dog is about to start barking!" and no new instance of Dog was found in person.Why is that so?

We can see that a message appears during program startup

Bean 'dog' of type [com.example.Dog] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

This means that the dog was created without going through all BeanPostProcessor s.The main reason for this is that the creation of DogPostProcessor relies on the creation of Person, and there is a sub-process called Initialization Method during the creation of Person that requires an instance of Dog from BeanFactory.DogPostProcessor has not been initialized at this time, and natural Dog cannot be processed.

In other words, this is not the right time to call the initialization method.Can there be a way to provide an initialization method that is called after all beans have been created?Yes, that's why SmartInitializingSingleton was born.

The source version used in this article is 2.2.2.RELEASE. Check for inconsistencies if there are any discrepancies.

Where to start

Let's go to the org.springframework.context.support.AbstractApplicationContext#refresh method, which is called when the context is created and builds the entire context step by step.When all the pre-work is done, the finishBeanFactoryInitialization(beanFactory) is invoked to initialize all (and most) Singleton Bean s that have not been initialized at the pre-work.

The most critical part is the DefaultListableBeanFactory#preInstantiateSingletons method, which is streamlined as follows:

    // Please note that the following code has been significantly modified, leaving only key schematic code. Please check the source code yourself.
	@Override
	public void preInstantiateSingletons() throws BeansException {
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		for (String beanName : beanNames) {
			getBean(beanName);
		}

		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				smartSingleton.afterSingletonsInstantiated();
			}
		}
	}

You can see that after all beans are initially complete, traversal determines whether each Singleton Bean implements the SmartInitializingSingleton interface, and then calls the afterSingletonsInstantiated method on the instance that implements the interface.Naturally, when this initialization method is called, all beans are created.That is, this interface separates the call to initialization methods from the creation of beans.

In fact, for this scenario mentioned in the previous section, you might think of another way to handle it, by implementing the ApplicationListener <ContextRefreshedEvent>interface.When the context is refreshed, the interface is naturally notified, which is slightly more complex and does not look much like the initialization of a Bean.

Re-start

We'll change Person

@Service
public class Person implements SmartInitializingSingleton, BeanFactoryAware {

    private BeanFactory beanFactory;
    private List<Dog> managedDog = new LinkedList<>();

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    public void addDog(Dog dog) {
        managedDog.add(dog);
    }

    @Override
    public void afterSingletonsInstantiated() {
        Dog dog = beanFactory.getBean(Dog.class);
        dog.makeSound();
    }
}

Then run it again

Notice that the dog is about to start barking!
bark!

Well, everything is fine.

Topics: Java