Spring Boot小组件 - SmartInitializingSingleton

前言

我们经常会看到或使用InitializingBean(或@PostConstruct)进行Bean的一个初始化过程,但是有时候会发现InitializingBean存在一些不太适用的场景。

比如我们有以下一个Dog

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

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

这个Dog是一个prototype的Bean,每次我们从BeanFactory中获取这个类时都会创建一个新的类。

然后我们有一个Person类,初始化的时候会需要一只狗叫一下

@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); } } 

到这还没啥事,运行一下可以正常地找到“bark!”的输出。

这时候突然来了一个针对DogBeanPostProcessor,好巧不巧还依赖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("注意了狗狗要开始叫了!"); } return method.invoke(bean, args); }); Dog proxyDog = (Dog) enhancer.create(); person.addDog(proxyDog); return proxyDog; } } 

在这个DogPostProcessor里对makeSound方法做了一个前置处理:预告要狗狗要开始叫了。最后还会把狗狗给到Person类进行管理。

这时候我们再次运行程序,发现并没有"注意了狗狗要开始叫了!"的输出,同时person中也没有发现新增的Dog实例。为什么会这样呢?

我们可以发现,在程序启动过程中,出现了一个信息

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

这表示dog没有经过所有的BeanPostProcessor就被创建了。造成这种局面的原因主要是DogPostProcessor的创建依赖Person的创建,而在Person创建的过程中有一个调用初始化方法的子过程,在这个子过程里需要从BeanFactory获得一个Dog的实例。这时DogPostProcessor都没初始化完呢,自然Dog也无法被处理了。

说来说去,还是这个调用初始化方法的时机不太合适。能不能有一种办法,提供一个在所有Bean都创建后才调用的初始化方法呢?没错,SmartInitializingSingleton正是因此而生。

本文所使用的源码版本为 2.2.2.RELEASE,如有出入请检查版本是否不一致。

从哪开始

我们来到org.springframework.context.support.AbstractApplicationContext#refresh方法,这是上下文创建时调用的方法,里面会一步一步构建好整个上下文。当所有的前置工作都做好时,会调用到finishBeanFactoryInitialization(beanFactory)进行所有在前置工作时还没初始化的(也是绝大多数的)Singleton Bean的初始化工作。

最关键的部分是DefaultListableBeanFactory#preInstantiateSingletons方法,精简后如下所示:

    // 请注意以下代码有大量删改,只留下了关键示意代码,请自行查看源代码。 @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(); } } } 

可以看到在所有Bean都初始完后,遍历判断了每个Singleton Bean是否实现了SmartInitializingSingleton接口,然后对实现此接口的实例调用afterSingletonsInstantiated方法。自然这个初始化方法被调用时,所有的Bean都创建好了。也就是说这个接口将初始化方法的调用和Bean的创建过程分开了。

其实在前言中提到的这个场景,你可能会想到另一种办法处理,那就是通过实现ApplicationListener<ContextRefreshedEvent>接口。上下文都刷新完成后,自然会通知到这个接口,当然这样稍显复杂,而且看起来也不太像一个Bean的初始化了。

重新出发

我们把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();
    }
}

然后再运行一下

注意了狗狗要开始叫了! bark! 

嗯,一切都正常了。