前言
很早之前,就打算写这一篇文章了(其实有很多源码分析的文章打算写,但是自己太拖延了导致很多文章搁浅了)。我为什么要写这一文章呢?事情的缘由是同事在
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注入进来,可是并没有。
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());
}
定位问题
1.在AbstractAutowireCapableBeanFactory
类中的populateBean方法中,会获取MyBaseDao的RootBeanDefinition中的autowireMode属性。
2.autowireMode等于0时为不注入;等于1时为通过属性名注入;等于2时为通过属性类型注入。
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
/** 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未开始实例之前)
- 调用
invokeBeanFactoryPostProcessors
中处理触发所有的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口回调。
3.在PostProcessorRegistrationDelegate
中,获取实现PriorityOrdered
接口的BeanDefinitionRegistryPostProcessor
。在这里就回调了ConfigurationClassPostProcessor
中的postProcessBeanDefinitionRegistry
方法去扫描所有的类,并注册BeanDefinition
,最后把BeanDefinition
信息放入到mergedBeanDefinitions
、beanDefinitionMap
、beanDefinitionNames
中维护。
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
。
6.如果我们在postProcessBeanDefinitionRegistry
方法注册扫描某一个包下的类并且注册BeanDenifition
。这些新的BeanDenifition
会在beanFactory.getBeanNamesForType
中的RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
更新beanDefinitionNames
、beanDefinitionMap
、mergedBeanDefinitions
。
7.从Spring容器中获取对象时,会执行AbstractBeanFactory
中的doGetBean
方法。markBeanAsCreated
方法中会清除MyBaseDao
旧的mergeBeanDefinition
,并把MyBaseDao
加入到alreadyCreated
集合中,标志着MyBaseDao
已经创建。
接着调用getMergedLocalBeanDefinition(beanName)
从beanDefinitionMap
中获取修改后的beanDefinition
中将其包装成RootBeanDefinition
。
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
都没有被注册。
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());
}
尾言
我们要知其然知其所以然。遇到类似的问题,就可以站在源码的角度去定位和解决问题,有利于在团队中塑造自己的形象。