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! 

嗯,一切都正常了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM