首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为什么我的HibernateDaoSupport没有注入SessionFactory

为什么我的HibernateDaoSupport没有注入SessionFactory

作者头像
用户2032165
发布2019-04-09 16:17:52
3K0
发布2019-04-09 16:17:52
举报

前言

很早之前,就打算写这一篇文章了(其实有很多源码分析的文章打算写,但是自己太拖延了导致很多文章搁浅了)。我为什么要写这一文章呢?事情的缘由是同事在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.03.31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 问题复现
  • 定位问题
  • 解决问题
  • SpringBoot中配置HibernateDaoSupport
  • 避免使用BeanPostProcessor和BeanDefinitionRegistryPostProcessor的"误伤"陷阱。
  • 尾言
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档