為什么我的HibernateDaoSupport沒有注入SessionFactory

前言

很早之前,就打算寫這一篇文章了(其實有很多源碼分析的文章打算寫,但是自己太拖延了導致很多文章擱淺了)。我為什么要寫這一文章呢?事情的緣由是同事在SpringBoot項目中有一個A類繼承HibernateDaoSupport,但是程序運行總是拋出沒有成功注入SessionFactory的錯誤,后來我debug Spring源碼解決了這個問題。這個錯誤的原因是A類的RootBeanDefinition中的autowireMode的值為0,在AbstractAutowireCapableBeanFactory類中的populateBean方法中沒有執(zhí)行到autowireByName(beanName, mbd, bw, newPvs),導致SessionFactory的屬性沒有注入成功。在XML配置中,可以通過配置default-autowire="byName"解決問題。而我會通過這篇文章,從學習Spring源碼的角度來分析并解決這個問題。

系列文章:
通過循環(huán)引用問題來分析Spring源碼


問題復現(xiàn)

1.按理來說Spring應該會通過setSessionFactory方法將SessionFactory注入進來,可是并沒有。


image.png

2.我們來寫一個有趣的例子,類似于HibernateDaoSupport類。

@Component
public class MySessionFactory {

    public String getName() {
        return "MySessionFactory";
    }
}
public class MyHibernateDaoSupport {

    private String template;

    /**
     * 描述: 設置 mySessionFactory</br>
     * @param mySessionFactory
     */

    public void setMySessionFactory(MySessionFactory mySessionFactory) {
        createTemplate(mySessionFactory);
    }

    public void createTemplate(MySessionFactory mySessionFactory) {
        this.template = mySessionFactory.getName();
    }

    public String getTemplate() {
        return this.template;
    }
}
@Component
public class MyBaseDao extends MyHibernateDaoSupport {

}

3.我們運行測試用例,發(fā)現(xiàn)template為空,很明顯成功注入MySessionFactory屬性。這和HibernateDaoSupport沒有成功注入sessionFactory屬性如出一轍。

    @Autowired
    private MyBaseDao myBaseDao;

    @Test
    public void test5() {
        System.out.println(myBaseDao.getTemplate());
    }
image.png

定位問題

1.在AbstractAutowireCapableBeanFactory類中的populateBean方法中,會獲取MyBaseDao的RootBeanDefinition中的autowireMode屬性。

image.png

2.autowireMode等于0時為不注入;等于1時為通過屬性名注入;等于2時為通過屬性類型注入。


image.png

3.此時MyBaseDao的RootBeanDefinition中的autowireMode屬性為0,所以不會調(diào)用autowireByNameautowireByType中注入MySessionFactory屬性

4.假設我們通過某種手段,使其autowireMode值為1,就會調(diào)用autowireByName方法,會獲取到MySessionFactory屬性,并通過getBean()方法獲取MySessionFactory實例。通過registerDependentBean(propertyName, beanName)MyBaseDaoMySessionFactory之間的依賴關(guān)系加入到dependentBeanMap(因為MyBaseDao依賴MySessionFactory,所以這里維護的是被依賴者和依賴者的關(guān)系,也就是MySessionFactory --》 MyBaseDao)和dependenciesForBeanMap(這里維護的是bean和bean依賴的對象之間的關(guān)系,也就是MyBaseDao --》 MySessionFactory)中。最后將MyBaseDao中的MySessionFactory屬性和MySessionFactory的實例中封裝成PropertyValue加入到MutablePropertyValues

image.png
    /** Map between dependent bean names: bean name --> Set of dependent bean names */
    private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<String, Set<String>>(64);

    /** Map between depending bean names: bean name --> Set of bean names for the bean's dependencies */
    private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<String, Set<String>>(64);

5.最后通過populateBean方法中的applyPropertyValues將屬性的值注入到MyBaseDao中。

執(zhí)行前.png

之前后.png

解決問題

我們既然已定位到問題的所在,那么要從以下幾個角度去解決問題:

  • 我們怎么樣才可以修改MyBaseDaoRootBeanDefinition中的autowireMode屬性

  • Spring是從哪一時刻掃描所有的類并注冊BeanDefinition

  • Spring提供了哪些入口可以讓我們修改BeanDefinition

1.在AbstractApplicationContext中的refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory)中提供BeanDefinition修改或者注冊的入口。(在Bean未開始實例之前)

AbstractApplicationContext類.png

  1. 調(diào)用invokeBeanFactoryPostProcessors中處理觸發(fā)所有的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口回調(diào)。
AbstractApplicationContext類.png

3.在PostProcessorRegistrationDelegate中,獲取實現(xiàn)PriorityOrdered接口的BeanDefinitionRegistryPostProcessor。在這里就回調(diào)了ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry方法去掃描所有的類,并注冊BeanDefinition,最后把BeanDefinition信息放入到mergedBeanDefinitions、beanDefinitionMapbeanDefinitionNames中維護。

PostProcessorRegistrationDelegate類.png

ConfigurationClassPostProcessor類.png

4.我們可以去實現(xiàn)BeanDefinitionRegistryPostProcessor接口,把MyBaseDao的BeanDefinition中的autowireMode屬性修改成1。

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String[] beanDefinitionNames = registry.getBeanDefinitionNames();

        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionName);

            if (beanDefinition instanceof AbstractBeanDefinition) {
                AbstractBeanDefinition hibernateDaoSupportBeanDefinition = (AbstractBeanDefinition)
                        beanDefinition;

                if (beanDefinitionName.contains("Dao")) {
                    if (hibernateDaoSupportBeanDefinition.getAutowireMode()
                            == AbstractBeanDefinition.AUTOWIRE_NO) {
                        hibernateDaoSupportBeanDefinition.setAutowireMode(AUTOWIRE_BY_NAME);
                    }
                }
            }
        }
    }

5.這樣MyBaseDaoRootBeanDefinitionautowireMode屬性會被修改成1。其實我們在postProcessBeanDefinitionRegistry方法中通過registry獲取的BeanDefinition是從DefaultListableBeanFactory中的beanDefinitionMap得到。這里的BeanDefinitionpopulateBean方法中的RootBeanDefinition是不一樣的。
populateBean方法中的RootBeanDefinition是出自于AbstractBeanFactory中的mergedBeanDefinitions

在AbstractBeanFactory類中.png

在`DefaultListableBeanFactory`.png

6.如果我們在postProcessBeanDefinitionRegistry方法注冊掃描某一個包下的類并且注冊BeanDenifition。這些新的BeanDenifition會在beanFactory.getBeanNamesForType中的RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);更新beanDefinitionNamesbeanDefinitionMap、mergedBeanDefinitions。

image.png

7.從Spring容器中獲取對象時,會執(zhí)行AbstractBeanFactory中的doGetBean方法。markBeanAsCreated方法中會清除MyBaseDao舊的mergeBeanDefinition,并把MyBaseDao加入到alreadyCreated集合中,標志著MyBaseDao已經(jīng)創(chuàng)建。
接著調(diào)用getMergedLocalBeanDefinition(beanName)beanDefinitionMap中獲取修改后的beanDefinition中將其包裝成RootBeanDefinition

image.png
image.png

SpringBoot中配置HibernateDaoSupport

1.問題終于明了,接下來我們來配置好SessionFactory。自己業(yè)務中繼承HibernateDaoSupportBaseDao就不會再拋出錯誤了。

@Configuration
@EnableAutoConfiguration
@EnableTransactionManagement
public class HibernateConfig {

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Bean(name = "sessionFactory")
    public SessionFactory sessionFactory() {
        if (entityManagerFactory.unwrap(SessionFactory.class) == null) {
            throw new NullPointerException("factory is not hibernate factory");
        }
        return entityManagerFactory.unwrap(SessionFactory.class);
    }
}

避免使用BeanPostProcessor和BeanDefinitionRegistryPostProcessor的"誤傷"陷阱。

1.PriorityOrderedBeanPostProcessor所依賴的Bean其初始化以后無法享受到PriorityOrdered、Ordered、和nonOrderedBeanPostProcessor的服務。而被OrderedBeanPostProcessor所依賴的Bean無法享受Ordered、和nonOrderedBeanPostProcessor的服務。最后被nonOrderedBeanPostProcessor所依賴的Bean無法享受到nonOrderedBeanPostProcessor的服務

2.在postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法中不要使用beanFactory.getBean()會造成類性早熟,最終的后果就是類中的一些屬性沒有成功注入。因為這時候的AutowiredAnnotationBeanPostProcessor都沒有被注冊。


2019-04-17更新

在寫這篇文章時xxl-job中關(guān)于quartz中的配置詳解留意到了AutowireCapableBeanFactory中的autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck)方法,可以給實例化后的bean對象指定它填充內(nèi)部屬性時的autowireMode。

    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    @Test
    public void test6() {
        MyBaseDao myBaseDao = (MyBaseDao) autowireCapableBeanFactory
                .autowire(MyBaseDao.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME,
                        true);
        System.out.println(myBaseDao.getTemplate());
    }

尾言

我們要知其然知其所以然。遇到類似的問題,就可以站在源碼的角度去定位和解決問題,有利于在團隊中塑造自己的形象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容