文章示例环境配置信息
jdk版本:1.8
开发工具:Intellij iDEA 2020.1
springboot:2.3.9.RELEASE
写这篇文章的起因,是因为我想写篇文章来分享一下:Spring是如何解决循环依赖的?然后在分析的时候,我发现如果要想说清楚Spring是如何解决循环依赖的,那么就必须得先说清楚什么是循环依赖?从字面理解,循环依赖肯定是发生在依赖注入的时候,那么Spring依赖注入的方式有三种注入方式,有的注入是能解决的,有的是解决不了的,那么可以解决循环依赖的注入方式的解决方法是什么?不能解决循环依赖的注入方式的原因又是什么?一连串的问题刺激到了我敏感的神经,因此,就这些疑问我打算用四篇文章分别分析它们。
IOC(控制反转)是Spring的重要核心,即不需要手动去new一个对象,而是把对象的控制权转移给Spring容器,通常Spring容器最终创建出来的对象称为bean。在Spring容器创建bean的过程中,如果bean内属性引用了另外的bean,这里当前bean暂时称为目标bean,被引用bean简称为引用bean,那么目标bean与引用bean之间的关系就是依赖关系,即目标bean依赖引用bean,还记得UML类图中依赖关系是如何图形化表示吗?可以移步设计模式之基础:UML类图怎么看?在Spring bean生命周期中,目标bean实例化后,还不能直接使用,会接着里进行属性的注入,其中的容器内其他bean作为引用bean注入到目标bean的过程,称之为依赖注入。
从注入的根本原理上来讲,依赖注入的方式通常是有三种分别是字段(Field)注入、setter方法注入、构造方法注入,而这三种方法最终都用到了java的反射技术,具体如下:
1、字段注入:最终调jField#set(Object obj, Object value)方法进行依赖属性的注入;
2、setter方法注入:最终调用Method#invoke(Object obj, Object... args)进行依赖属性的注入;
3、构造方法注入:最终调用jConstructor#newInstance(Object ... initargs)进行依赖属性的注入;
唯一差异比较大的就是,采用这些不同依赖注入方式的bean创建过程不同,而这正是我想分析清楚的东西。
以@Autowired注解为例,即把@Autowired注解标记在目标bean的引用bean字段上;
下面用一个示例,演示使用@Autowired注解,按字段注入的方式进行依赖注入,并分析注入的过程,这里想和大家分享一个经验,任何事其实都是一个方法的问题, 方法通常是解决某一个或某一类问题的,而总结后的方法是为了解决一类问题的,所以这里学会了@Autowired注解进行属性注入的工作原理的分析方法,再换成其他的注解如@Resource注解,具体内容会不一样了,但是方法是相同的。
示例内容:1、定义Teacher类; 2、定义Student类;3、在Student类依赖Teacher;4、使用@Autowired注解标记在Student类的teacher字段上,即在Student对象中以字段注入的方式注入Teacher对象;
@Slf4j
@Component
public class Teacher {
private String name="李老师";
private Student student;
public Teacher() {
log.info("----teacher的无参数构造方法被执行");
}
public Teacher(Student student) {
this.student = student;
log.info("----teacher的有参数构造方法被执行");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
log.info("----teacher中的setStudent方法被调用");
this.student = student;
}
}
@Slf4j
@Component
public class Student {
private String name="小明";
@Autowired
private Teacher teacher;
public Student() {
log.info("----student的无参数构造方法被执行");
}
public Student(Teacher teacher) {
this.teacher = teacher;
log.info("----student的有参数构造方法(teacher)被执行");
}
public Student(String name, Teacher teacher) {
this.name = name;
this.teacher = teacher;
log.info("----student的有参数构造方法(name,teacher)被执行");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
log.info("----student中的setTeacher方法被调用");
}
}
@Component
@Slf4j
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if (beanName.equals("student")||beanName.equals("teacher")) {
log.info("----bean实例化完成,beanName:" + beanName);
}
return true;
}
}
@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("student")) {
Student student = (Student) bean;
log.info("----student属性注入完成,student.name:" + student.getName());
}
return bean;
}
}
@Test
public void test4() {
log.info("----单元测试执行开始");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
Student bean = context.getBean(Student.class);
log.info("----bean:"+bean.getName());
log.info("----单元测试执行完毕");
}
单元测试执行日志
从单元测试的执行日志来可以了解到bean从实例化到属性注入的大概过程:student对象先开始实例化,然后开始student对象依赖属性teacher的注入,但这时teacher对象还未实例化,所以又开始进行teacher对象的实例化和依赖属性注入;最后把实例化好的teacher对象注入到student对够中;
注:1、InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()执行时机是在bean实例化以后,属性注入以前;
2、BeanPostProcessor#postProcessBeforeInitialization()的执行时机是在bean属性注入完成以后;详细可以移步这里了解:Springboot扩展点之InstantiationAwareBeanPostProcessor和Springboot扩展点之BeanPostProcessor)
从上面的示例的单元测试结果,其实大概已经了解到Spring bean依赖注入-属性注入的工作过程,下面通过debug了解一下每个步骤的详细过程:
1、Spring中bean创建核心逻辑都在AbstractAutowireCapableBeanFactory#doCreateBean()中,有兴趣可以从Springboot启动开始一步步debug看看。那么这个方法主要干了哪些事呢?第一,bean的实例化;第二,bean的后置处理方法执行;第三,把实例化完成、未完成属性注入的bean提前暴露到Spring的第三级缓存中去;第四,bean依赖属性注入;第五,bean的初始化;student对象在完成这几步后,就可以作为Spring容器中正式的bean开始被使用了。
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// ------start-----bean实例化-------------------
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// ------end-----bean实例化-------------------
// ------start----bean后置处理器-------------------
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
//------end----bean后置处理器-------------------
//------start----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//------end----完成实例化、未完成属性注入的bean提前暴露到Spring的第三级缓存中-------------------
Object exposedObject = bean;
try {
//bean的属性注入
populateBean(beanName, mbd, instanceWrapper);
//bean的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
2、关于bean依赖属性的注入入口在上面AbstractAutowireCapableBeanFactory#doCreateBean()的第46行,即调用 populateBean()开始属性的注入,示例中是使用@Autowired注解,这个注解的原理就是Springboot扩展点之InstantiationAwareBeanPostProcessor,而具体实现类就是AutowiredAnnotationBeanPostProcessor;
3、进入到AutowiredAnnotationBeanPostProcessor#postProcessProperties(),可以发现就做了两件事:第一、把student对象内需要注入的teacher属性的类型、注入方式、具体执行属性注入的工具类(AutowiredFieldElement)包装成一个InjectionMetadata对象;第二,包装好的InjectionMetadata对象调用自身的inject()完成student对象依赖属性teacher的注入;
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
//把目标bean依赖需要注入的对象类型、注入方式、执行注入的对象(AutowiredFieldElement)封装成了一个InjectionMetadata对象
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
//依赖注入的入口
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
4、进入到InjectionMetadata#inject()后,实际上调用 了AutowiredAnnotationBeanPostProcessor的内部类AutowiredFieldElement的inject(),AutowiredFieldElement#inject()内逻辑也相对比较简单:先判断一下student对象依赖的teacher对象有没有缓存,第一次创建肯定是没有的,然后就开始解析注入到student对象内的teacher字段的值,解析的过程基本上student对象的创建过程类似,先获取teacher对象,如果获取不到,就开始teacher对象的实例化、属性注入、初始化;teacher对象创建完在后,继续往下使用java反射技术对student对象内的teacher字段进行赋值;
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
//判断依赖对象是否缓存
if (this.cached) {
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
value = resolveFieldValue(field, bean, beanName);
}
}
else {
//实例化依赖对象的并缓存
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
//使用java反射技术进行属性字段的赋值
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
5、继续往下执行,上面AbstractAutowireCapableBeanFactory#doCreateBean()的第46行,开始调用initializeBean(...)的时候,观察一下目标bean,发现student内已经完成了依赖属性teacher对象的注入;
使用@Autowired注解,采用通字段注入方式,进行Spring bean依赖注入,是利用了Spring的扩展点InstantiationAwareBeanPostProcessor,具体的实现是AutowiredAnnotationBeanPostProcessor,最终执行注入操作的是其内部类AutowiredFieldElement的inject()。这里对AutowiredFieldElement要加深一下印象,AutowiredFieldElement和AutowiredMethodElement都是AutowiredAnnotationBeanPostProcessor的内部类,AutowiredFieldElement的作用通过前面的分析已经知道了,那么AutowiredMethodElement的作用是什么呢?评论区来猜一下吧,哈哈
文末彩蛋:上面分析了字段注入方式的过程,但是被@Autowired注解标记到的Teacher字段,AutowiredAnnotationBeanPostProcessor是怎么识别到的呢?
答案就在AutowiredAnnotationBeanPostProcessor的构造方法内,不光@Autowired注解,实际上@Value从配置文件中取值赋给bean,也是在这个类处理的。
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
try {
this.autowiredAnnotationTypes.add((Class extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}