循环依赖所注入的实例竟不是容器中的实例之一,依赖关系

432 阅读3分钟

写在前面

私以为自己对于spring的循环依赖理解的已经很不错了,直到前段时间遇到了一个让我思考了良久的问题,即“循环依赖所注入的实例竟不是容器中的实例”。于是又从头仔细撸了一遍源码,才最终找到原因。因为我觉的整个过程涉及到的知识点还蛮多的,所以准备用6-7篇来针对“循环依赖所注入的实例竟不是容器中的实例”这个问题进行剖析。

问题引出

废话不多说,先来看看这个问题是什么? 有两个Service具有循环引用关系,分别是ServiceA和ServiceB,如下。 ServiceA.class

@Service
public class ServiceA {
    @Autowired
    // 可能有人会疑问,为何定义成public还要再写get方法,这个我们后面的文章会说,先卖个关子
    public ServiceB serviceB;

    @Async
    public String process() {
        return "success";
    }

    public ServiceB getServiceB() {
        return serviceB;
    }
}

ServiceB.class

@Service
public class ServiceB implements ApplicationContextAware {

    public ServiceA serviceA;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, ServiceA> beansOfType = applicationContext.getBeansOfType(ServiceA.class);
        serviceA = beansOfType.get("serviceA");
    }

    public ServiceA getServiceA() {
        return serviceA;
    }
}

配置类

@EnableAsync
@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.xyh.study.provider.spring.AOP.第六小节.第四章A")
public class AppConfig {
}

可以看到,ServiceA持有ServiceB的引用,同时ServiceB也持有ServiceA的引用。两个Service相互持有对方的引用。ServiceA通过@Autowired注入ServiceB,ServiceB通过ApplicationContextAware来注入ServiceA。

有以下几点需要特别注意一下
1.我的表述是ServiceB持有ServiceA的引用,而不是ServiceB依赖ServiceA的。我们当然可以认为ServiceB依赖了ServiceA,但是我用“引用”的原因是想与spring中对于依赖的定义进行区分。以上例子,spring认为ServiceA依赖ServiceB,而并不认为ServiceB依赖ServiceA。
2.注意下,我们的ServiceA中有一个异步方法。

以上就是我们整个系列要分析的。例子虽然简单,但是对应的知识点可以不少的。 到此我们先思考下,ServiceA实例所持有的ServiceB实例与容器中的ServiceB是同一个bean吗?以及ServiceB实例所持有的ServiceA实例与容器中的ServiceA是同一个bean吗? 也就是以下代码输出是什么?

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        System.out.println(((ServiceA) (applicationContext.getBean("serviceA"))).getServiceB() == applicationContext.getBean("serviceB"));
        System.out.println(((ServiceB) (applicationContext.getBean("serviceB"))).getServiceA() == applicationContext.getBean("serviceA"));
    }
}

暂停思考一下

暂停思考一下

暂停思考一下

------------------------------------分隔线------------------------------------

如果你能很清晰的分辨出,输出结果是true、false的话,那么你就不需要后续了,如果你有些疑问的话,不妨我们一起探究这其中的奥秘。

Spring中的依赖关系

作为以上问题解析的第一篇,本篇我准备先说一下spring中的“依赖”。 @Autowired是spring中依赖关系的载体。当A通过@Autowired注入了B,spring就认为A依赖了B,B被A依赖了。spring中的DefaultSingletonBeanRegistry会记录这种关系,是通过两个类型为Map<String, Set<String>>的变量进行依赖关系存储的。上源码(隐去了无关的部分):

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

	/** Map between dependent bean names: bean name to Set of dependent bean names. */
	// 该map记录了被依赖关系,即key对应被依赖的bean name,value对应依赖了key的bean name集合,可以理解为“谁依赖我”
	private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

	/** Map between depending bean names: bean name to Set of bean names for the bean's dependencies. */
	// 改map记录了依赖关系,与dependentBeanMap正好相反,可以理解为“我依赖谁”
	private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

	/**
	 * Register a dependent bean for the given bean,
	 * to be destroyed before the given bean is destroyed.
	 * @param beanName the name of the bean
	 * @param dependentBeanName the name of the dependent bean
	 */
	 /**
	 * 注册依赖关系
	 * baneName:需要记录依赖关系的beanName
	 * dependentBeanName:依赖的bean的name
	 * 
	 **/
	public void registerDependentBean(String beanName, String dependentBeanName) {
		String canonicalName = canonicalName(beanName);

		synchronized (this.dependentBeanMap) {
			Set<String> dependentBeans =
					this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
			if (!dependentBeans.add(dependentBeanName)) {
				return;
			}
		}

		synchronized (this.dependenciesForBeanMap) {
			Set<String> dependenciesForBean =
					this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
			dependenciesForBean.add(canonicalName);
		}
	}
}

举个例子大家应该能很好理解,假如A通过@Autowired依赖了B,B通过@Autowired依赖了C,A通过@Autowired依赖了C,那么,A、B、C对应的依赖关系可以表示为如下:

dependentBeanMap={ "b": ["a"], "c": ["b", "a"] } dependenciesForBeanMap={ "a": ["b", "c"], "b": ["c"] }

既然有建立依赖关系,那必然有销毁依赖关系,对应的逻辑比较简单,我就不再赘述了。

总结

本文引出了“循环依赖所注入的实例竟不是容器中的实例”问题,并简要介绍了一下spring中的依赖关系,作为系列的开篇,后续我们将分析:

1.spring中三级缓存解决循环依赖

2.循环依赖于AOP的关系

3.@Async对循环依赖造成了怎样的影响。