前言
很早之前,就打算寫這一篇文章了(其實有很多源碼分析的文章打算寫,但是自己太拖延了導致很多文章擱淺了)。我為什么要寫這一文章呢?事情的緣由是同事在
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注入進來,可是并沒有。

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

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

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

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

/** 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中。


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

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

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


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


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

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


SpringBoot中配置HibernateDaoSupport
1.問題終于明了,接下來我們來配置好SessionFactory。自己業(yè)務中繼承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都沒有被注冊。
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());
}
尾言
我們要知其然知其所以然。遇到類似的問題,就可以站在源碼的角度去定位和解決問題,有利于在團隊中塑造自己的形象。