为什么我的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,所以不会调用autowireByNameautowireByType中注入MySessionFactory属性

4.假设我们通过某种手段,使其autowireMode值为1,就会调用autowireByName方法,会获取到MySessionFactory属性,并通过getBean()方法获取MySessionFactory实例。通过registerDependentBean(propertyName, beanName)MyBaseDaoMySessionFactory之间的依赖关系加入到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

解决问题

我们既然已定位到问题的所在,那么要从以下几个角度去解决问题:

  • 我们怎么样才可以修改MyBaseDaoRootBeanDefinition中的autowireMode属性

  • Spring是从哪一时刻扫描所有的类并注册BeanDefinition

  • Spring提供了哪些入口可以让我们修改BeanDefinition

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

AbstractApplicationContext类.png

  1. 调用invokeBeanFactoryPostProcessors中处理触发所有的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口回调。
AbstractApplicationContext类.png

3.在PostProcessorRegistrationDelegate中,获取实现PriorityOrdered接口的BeanDefinitionRegistryPostProcessor。在这里就回调了ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry方法去扫描所有的类,并注册BeanDefinition,最后把BeanDefinition信息放入到mergedBeanDefinitionsbeanDefinitionMapbeanDefinitionNames中维护。

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.这样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);更新beanDefinitionNamesbeanDefinitionMapmergedBeanDefinitions

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。自己业务中继承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其初始化以后无法享受到PriorityOrderedOrdered、和nonOrderedBeanPostProcessor的服务。而被OrderedBeanPostProcessor所依赖的Bean无法享受Ordered、和nonOrderedBeanPostProcessor的服务。最后被nonOrderedBeanPostProcessor所依赖的Bean无法享受到nonOrderedBeanPostProcessor的服务

2.在postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法中不要使用beanFactory.getBean()会造成类性早熟,最终的后果就是类中的一些属性没有成功注入。因为这时候的AutowiredAnnotationBeanPostProcessor都没有被注册。


2019-04-17更新

在写这篇文章时xxl-job中关于quartz中的配置详解留意到了AutowireCapableBeanFactory中的autowire(Class<?> beanClass, int autowireMode, boolean dependencyCheck)方法,可以给实例化后的bean对象指定它填充内部属性时的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());
    }

尾言

我们要知其然知其所以然。遇到类似的问题,就可以站在源码的角度去定位和解决问题,有利于在团队中塑造自己的形象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容