为什么我的HibernateDaoSupport没有注入SessionFactory

1字数 1340阅读 364

前言

很早之前,就打算写这一篇文章了(其实有很多源码分析的文章打算写,但是自己太拖延了导致很多文章搁浅了)。我为什么要写这一文章呢?事情的缘由是同事在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());
    }

尾言

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

推荐阅读更多精彩内容