為什麼我的HibernateDaoSupport沒有注入SessionFactory
前言
很早之前,就打算寫這一篇文章了(其實有很多原始碼分析的文章打算寫,但是自己太拖延了導致很多文章擱淺了)。我為什麼要寫這一文章呢?事情的緣由是同事在 SpringBoot
專案中有一個A類繼承 HibernateDaoSupport
,但是程式執行總是丟擲沒有成功注入 SessionFactory
的錯誤,後來我debug Spring原始碼解決了這個問題。這個錯誤的原因是A類的 RootBeanDefinition
中的 autowireMode
的值為0,在 AbstractAutowireCapableBeanFactory
類中的 populateBean
方法中沒有執行到 autowireByName(beanName, mbd, bw, newPvs)
,導致 SessionFactory
的屬性沒有注入成功。在XML配置中,可以通過配置 default-autowire="byName"
解決問題。而我會通過這篇文章,從學習Spring原始碼的角度來分析並解決這個問題。
系列文章:
通過迴圈引用問題來分析Spring原始碼問題復現
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.我們執行測試用例,發現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,所以不會呼叫 autowireByName
和 autowireByType
中注入 MySessionFactory
屬性
4.假設我們通過某種手段,使其autowireMode值為1,就會呼叫 autowireByName
方法,會獲取到 MySessionFactory
屬性,並通過 getBean()
方法獲取 MySessionFactory
例項。通過 registerDependentBean(propertyName, beanName)
將 MyBaseDao
和 MySessionFactory
之間的依賴關係加入到 dependentBeanMap
(因為 MyBaseDao
依賴 MySessionFactory
,所以這裡維護的是被依賴者和依賴者的關係,也就是 MySessionFactory --》 MyBaseDao
)和 dependenciesForBeanMap
(這裡維護的是bean和bean依賴的物件之間的關係,也就是 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
中。

執行前.png

之前後.png
解決問題
我們既然已定位到問題的所在,那麼要從以下幾個角度去解決問題:
-
我們怎麼樣才可以修改
MyBaseDao
的RootBeanDefinition
中的autowireMode屬性 -
Spring是從哪一時刻掃描所有的類並註冊BeanDefinition
-
Spring提供了哪些入口可以讓我們修改BeanDefinition
1.在 AbstractApplicationContext
中的 refresh()
方法中的 invokeBeanFactoryPostProcessors(beanFactory)
中提供BeanDefinition修改或者註冊的入口。(在Bean未開始例項之前)

AbstractApplicationContext類.png
- 呼叫
invokeBeanFactoryPostProcessors
中處理觸發所有的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor介面回撥。

AbstractApplicationContext類.png
3.在 PostProcessorRegistrationDelegate
中,獲取實現 PriorityOrdered
介面的 BeanDefinitionRegistryPostProcessor
。在這裡就回調了 ConfigurationClassPostProcessor
中的 postProcessBeanDefinitionRegistry
方法去掃描所有的類,並註冊 BeanDefinition
,最後把 BeanDefinition
資訊放入到 mergedBeanDefinitions
、 beanDefinitionMap
、 beanDefinitionNames
中維護。

PostProcessorRegistrationDelegate類.png

ConfigurationClassPostProcessor類.png
4.我們可以去實現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.這樣 MyBaseDao
的 RootBeanDefinition
的 autowireMode
屬性會被修改成1。其實我們在 postProcessBeanDefinitionRegistry
方法中通過 registry
獲取的 BeanDefinition
是從 DefaultListableBeanFactory
中的 beanDefinitionMap
得到。這裡的 BeanDefinition
和 populateBean
方法中的 RootBeanDefinition
是不一樣的。
populateBean
方法中的 RootBeanDefinition
是出自於 AbstractBeanFactory
中的 mergedBeanDefinitions
。

在AbstractBeanFactory類中.png

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

image.png
7.從Spring容器中獲取物件時,會執行 AbstractBeanFactory
中的 doGetBean
方法。 markBeanAsCreated
方法中會清除 MyBaseDao
舊的 mergeBeanDefinition
,並把 MyBaseDao
加入到 alreadyCreated
集合中,標誌著 MyBaseDao
已經建立。
接著呼叫 getMergedLocalBeanDefinition(beanName)
從 beanDefinitionMap
中獲取修改後的 beanDefinition
中將其包裝成 RootBeanDefinition
。

image.png

image.png
SpringBoot中配置HibernateDaoSupport
1.問題終於明瞭,接下來我們來配置好 SessionFactory
。自己業務中繼承 HibernateDaoSupport
的 BaseDao
就不會再丟擲錯誤了。
@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
、和 nonOrdered
的 BeanPostProcessor
的服務。而被 OrderedBeanPostProcessor
所依賴的 Bean
無法享受 Ordered
、和 nonOrdered
的 BeanPostProcessor
的服務。最後被 nonOrderedBeanPostProcessor
所依賴的 Bean
無法享受到 nonOrderedBeanPostProcessor
的服務
2.在 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
方法中不要使用 beanFactory.getBean()
會造成類性早熟,最終的後果就是類中的一些屬性沒有成功注入。因為這時候的 AutowiredAnnotationBeanPostProcessor
都沒有被註冊。
尾言
我們要知其然知其所以然。遇到類似的問題,就可以站在原始碼的角度去定位和解決問題,有利於在團隊中塑造自己的形象。