Spring中的循环依赖解决详解

说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚。本文就试着尽自己所能,对此做出一个较详细的解读。另,需注意一点,下文中会出现类的实例化跟类的初始化两个短语,为怕园友迷惑,事先声明一下,本文的实例化是指刚执行完构造器将一个对象new出来,但还未填充属性值的状态,而初始化是指完成了属性的依赖注入。

一、先说说Spring解决的循环依赖是什么

Java中的循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖。

构造器的循环依赖就是在构造器中有属性循环依赖,如下所示的两个类就属于构造器循环依赖:

@Service
public class Student {
    @Autowired
    private Teacher teacher;

public Student (Teacher teacher) {
        System.out.println("Student init1:" + teacher);
    }

public void learn () {
        System.out.println("Student learn");
    }
}

@Service
public class Teacher {
    @Autowired
    private Student student;

public Teacher (Student student) {
        System.out.println("Teacher init1:" + student);

}

public void teach () {
        System.out.println("teach:");
        student.learn();
    }
}

这种循环依赖没有什么解决办法,因为JVM虚拟机在对类进行实例化的时候,需先实例化构造器的参数,而由于循环引用这个参数无法提前实例化,故只能抛出错误。

Spring解决的循环依赖就是指属性的循环依赖,如下所示:

@Service
public class Teacher {
    @Autowired
    private Student student;

public Teacher () {
        System.out.println("Teacher init1:" + student);

}

public void teach () {
        System.out.println("teach:");
        student.learn();
    }
   
}

@Service
public class Student {
    @Autowired
    private Teacher teacher;

public Student () {
        System.out.println("Student init:" + teacher);
    }

public void learn () {
        System.out.println("Student learn");
    }
}

测试扫描类:

1 @ComponentScan(value = "myPackage")
2 public class ScanConfig {
3   
4 }

测试启动类:

public class SpringTest {

public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanConfig.class);
        applicationContext.getBean(Teacher.class).teach();

}
}

测试类执行结果:

1 Student init:null
2 Teacher init:null
3 teach:
4 Student learn

可以看到,在构造器执行的时候未完成属性的注入,而在调用方法的时候已经完成了注入。下面就一起看看Spring内部是在何时完成的属性注入,又是如何解决的循环依赖。

二、循环依赖与属性注入

1、对于非懒加载的类,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包扫描以及bean的初始化,下面就一起追踪下去。

1 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
2        // 其他代码
3
4        // Instantiate all remaining (non-lazy-init) singletons.
5        beanFactory.preInstantiateSingletons();
6    }

可以看到调用了beanFactory的一个方法,此处的beanFactory就是指我们最常见的那个DefaultListableBeanFactory,下面看它里面的这个方法。

2、DefaultListableBeanFactory的preInstantiateSingletons方法

public void preInstantiateSingletons() throws BeansException {
       
        List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

// Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { // 判断为非抽象类、是单例、非懒加载 才给初始化
                if (isFactoryBean(beanName)) {
                    // 无关代码(针对FactoryBean的处理)
                }
                else {
                    // 重要!!!普通bean就是在这里初始化的
                    getBean(beanName);
                }
            }
        }

// 其他无关代码 
    }

可以看到,就是在此方法中循环Spring容器中所有的bean,依次对其进行初始化,初始化的入口就是getBean方法

3、AbstractBeanFactory的getBean跟doGetBean方法

追踪getBean方法:

1 public Object getBean(String name) throws BeansException {
2        return doGetBean(name, null, null, false);
3    }

可见引用了重载的doGetBean方法,继续追踪之:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/7d0b95e0035822ffae9b84633c5df7d4.html