为什么我的HibernateDaoSupport没有注入Sess
前言
很早之前,就打算写这一篇文章了(其实有很多源码分析的文章打算写,但是自己太拖延了导致很多文章搁浅了)。我为什么要写这一文章呢?事情的缘由是同事在
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都没有被注册。
尾言
我们要知其然知其所以然。遇到类似的问题,就可以站在源码的角度去定位和解决问题,有利于在团队中塑造自己的形象。