ホームページ > Java > &#&面接の質問 > Java のインタビューの質問: 循環依存関係とは何か知っていますか? Spring は循環依存関係をどのように解決するのでしょうか?

Java のインタビューの質問: 循環依存関係とは何か知っていますか? Spring は循環依存関係をどのように解決するのでしょうか?

王林
リリース: 2021-01-08 10:33:06
転載
3494 人が閲覧しました

Java のインタビューの質問: 循環依存関係とは何か知っていますか? Spring は循環依存関係をどのように解決するのでしょうか?

まず、循環依存関係とは何かについてご紹介します。

(学習ビデオ共有: java ビデオ チュートリアル)

いわゆる循環依存関係とは、A が B に依存し、同時に B が A に依存することです。 2 つの間の依存関係が形成され、リングが形成されます。これは、通常、不適切なエンコードによって発生します。 Spring はプロパティの循環依存性の問題のみを解決できますが、コンストラクターの循環依存性の問題は解決できません。この問題には解決策がないためです。

次に、最初にデモを作成して、Spring がプロパティの循環依存関係の問題をどのように処理するかをデモンストレーションします。

トークは安いです。コードを見せてください。

ステップ 1: プライベート プロパティ コンポーネント B を持つクラス ComponentA を定義します。

package com.tech.ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author 君战
 * **/
@Component
public class ComponentA {

	@Autowired
	private ComponentB componentB;

	public void say(){
		componentB.say();
	}

}
ログイン後にコピー

ステップ 2: ComponentA に依存するクラス ComponentB を定義します。そして、データの印刷を容易にするためのsayメソッドを定義します。

package com.tech.ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @author 君战
 * **/
@Component
public class ComponentB {

	@Autowired
	private ComponentA componentA;

	public void say(){
		System.out.println("componentA field " + componentA);
		System.out.println(this.getClass().getName() + " -----> say()");
	}

}
ログイン後にコピー

3 番目のステップ: 焦点を当て、Spring の循環依存関係の基礎となる処理を模倣するクラス -SimpleContainer を作成します。このコードを理解していれば、循環依存関係を処理する Spring のロジックを理解するのが非常に簡単になります。

package com.tech.ioc;

import java.beans.Introspector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 演示Spring中循环依赖是如何处理的,只是个简版,真实的Spring依赖处理远比这个复杂。
 * 但大体思路都相同。另外这个Demo很多情况都未考虑,例如线程安全问题,仅供参考。
 * @author 君战
 *
 * **/
public class SimpleContainer {

	/***
	 * 用于存放完全初始化好的Bean,Bean处于就绪状态
	 * 这个Map定义和Spring中一级缓存命名一致
	 * */
	private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

	/***
	 * 用于存放刚创建出来的Bean,其属性还没有处理,因此存放在该缓存中的Bean还不可用。
	 * 这个Map定义和Spring中三级缓存命名一致
	 * */
	private final Map<String, Object> singletonFactories = new HashMap<>(16);


	public static void main(String[] args) {
		SimpleContainer container = new SimpleContainer();
		ComponentA componentA = container.getBean(ComponentA.class);
		componentA.say();
	}

	public <T> T getBean(Class<T> beanClass) {
		String beanName = this.getBeanName(beanClass);
		// 首先根据beanName从缓存中获取Bean实例
		Object bean = this.getSingleton(beanName);
		if (bean == null) {
			// 如果未获取到Bean实例,则创建Bean实例
			return createBean(beanClass, beanName);
		}
		return (T) bean;
	}
	/***
	 * 从一级缓存和二级缓存中根据beanName来获取Bean实例,可能为空
	 * */
	private Object getSingleton(String beanName) {
		// 首先尝试从一级缓存中获取
		Object instance = singletonObjects.get(beanName);
		if (instance == null) { // Spring 之所以能解决循环依赖问题,也是靠着这个三级缓存--singletonFactories
			instance = singletonFactories.get(beanName);
		}
		return instance;
	}

	/***
	 * 创建指定Class的实例,返回完全状态的Bean(属性可用)
	 *
	 * */
	private <T> T createBean(Class<T> beanClass, String beanName) {
		try {
			Constructor<T> constructor = beanClass.getDeclaredConstructor();
			T instance = constructor.newInstance();
			// 先将刚创建好的实例存放到三级缓存中,如果没有这一步,Spring 也无法解决三级缓存
			singletonFactories.put(beanName, instance);
			Field[] fields = beanClass.getDeclaredFields();
			for (Field field : fields) {
				Class<?> fieldType = field.getType();
				field.setAccessible(true); 
				// 精髓是这里又调用了getBean方法,例如正在处理ComponentA.componentB属性,
				// 执行到这里时就会去实例化ComponentB。因为在getBean方法首先去查缓存,
				// 而一级缓存和三级缓存中没有ComponentB实例数据,所以又会调用到当前方法,
				// 而在处理ComponentB.componentA属性时,又去调用getBean方法去缓存中查找,
				// 因为在前面我们将ComponentA实例放入到了三级缓存,因此可以找到。
				// 所以ComponentB的实例化结束,方法出栈,返回到实例化ComponentA的方法栈中,
				// 这时ComponentB已经初始化完成,因此ComponentA.componentB属性赋值成功!
				field.set(instance, this.getBean(fieldType));
			}
			// 最后再将初始化好的Bean设置到一级缓存中。
			singletonObjects.put(beanName, instance);
			return instance;
		} catch (Exception e) {
			e.printStackTrace();
		}
		throw new IllegalArgumentException();
	}

	/**
	 * 将类名小写作为beanName,Spring底层实现和这个差不多,也是使用javaBeans的
	 * {@linkplain Introspector#decapitalize(String)}
	 **/
	private String getBeanName(Class<?> clazz) {
		String clazzName = clazz.getName();
		int index = clazzName.lastIndexOf(".");
		String className = clazzName.substring(index);
		return Introspector.decapitalize(className);
	}
}
ログイン後にコピー

生徒全員が上記のコードを読んで理解できたら、Spring の循環依存問題の実際のソース コード分析を実行します。もう一度読むのは簡単だと思います。

基礎となるソース コードの分析

分析は、AbstractBeanFactory の doGetBean メソッドから始まります。ご覧のとおり、transformedBeanName はこのメソッドで最初に呼び出されます (実際には BeanName の問題に対処するため)。これは私たち自身で作成した getBeanName メソッドと同じ効果がありますが、FactoryBean とエイリアスの問題があるため、Spring はこれをこれよりもはるかに複雑だと考えています。 。

// AbstractBeanFactory#doGetBean
protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object bean;

		// !!!重点是这里,首先从缓存中beanName来获取对应的Bean。
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			// 执行到这里说明缓存中存在指定beanName的Bean实例,getObjectForBeanInstance是用来处理获取到的Bean是FactoryBean问题
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		else {
			try {
				// 删除与本次分析无关代码....
				// 如果是单例Bean,则通过调用createBean方法进行创建
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						} catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
				
				}	
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}
		return (T) bean;
	}
ログイン後にコピー

getSingleton メソッドにはオーバーロードされたメソッドがあります。オーバーロードされた getSingleton メソッドがここで呼び出されます。この値により初期 Bean の公開が許可されるかどうかが決定されるため、ここに渡されるブール パラメーター値は true であることに注意してください。

// DefaultSingletonBeanRegistry#getSingleton
public Object getSingleton(String beanName) {
	return getSingleton(beanName, true);
}
ログイン後にコピー
// DefaultSingletonBeanRegistry#getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 首先从一级缓存中获取
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 如果一级缓存中未获取到,再从二级缓存中获取
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 如果未从二级缓存中获取到并且allowEarlyReference值为true(前面传的为true)
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
				   //Double Check 
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							// 最后尝试去三级缓存中获取
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								singletonObject = singletonFactory.getObject();
								// 保存到二级缓存
								this.earlySingletonObjects.put(beanName, singletonObject);
								// 从三级缓存中移除
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}
ログイン後にコピー

(面接の質問の詳細については、java 面接の質問と回答 をご覧ください)

わかりました。Spring がキャッシュから Bean インスタンスを取得する方法を読んだ後、 creatBean メソッドが Bean を作成する方法を見てください

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	// 删除与本次分析无关的代码...
	try {// createBean方法底层是通过调用doCreateBean来完成Bean创建的。
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isTraceEnabled()) {
			logger.trace("Finished creating instance of bean &#39;" + beanName + "&#39;");
		}
		return beanInstance;
	} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
		throw ex;
	} catch (Throwable ex) {
		throw new BeanCreationException(
				mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
	}
}
ログイン後にコピー
// AbstractAutowireCapableBeanFactory#doCreateBean
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			// 创建Bean实例
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		// 如果允许当前Bean早期曝光。只要Bean是单例的并且allowCircularReferences 属性为true(默认为true)
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			// 这里调用了addSingletonFactory方法将刚创建好的Bean保存到了三级缓存中。
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// 删除与本次分析无关的代码.....
		Object exposedObject = bean;
		try {// Bean属性填充
			populateBean(beanName, mbd, instanceWrapper);
			// 初始化Bean,熟知的Aware接口、InitializingBean接口.....都是在这里调用
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		} catch (Throwable ex) {
			
		}
		// 删除与本次分析无关的代码.....
		return exposedObject;
	}
ログイン後にコピー

まず、addSingletonFactory メソッドを分析します。これは、このメソッドでは Bean が 3 次キャッシュに保存されるためです。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		// 如果一级缓存中不存在指定beanName的key
		if (!this.singletonObjects.containsKey(beanName)) {
			// 将刚创建好的Bean实例保存到三级缓存中
			this.singletonFactories.put(beanName, singletonFactory);
			// 从二级缓存中移除。
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}
ログイン後にコピー

処理 Bean の依存関係の注入は、populateBean メソッドによって完了しますが、実行リンク全体が長すぎるため、ここでは説明しません。IoC コンテナーがそれをステップで呼び出す方法についてのみ説明します。依存関係を処理するときに getBean メソッドを段階的に実行するため、フィールド インジェクションを処理するために独自に作成したロジックと一致します。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	// 删除与本次分析无关代码...
	PropertyDescriptor[] filteredPds = null;
	if (hasInstAwareBpps) {
		if (pvs == null) {
			pvs = mbd.getPropertyValues();
		}
		// 遍历所有已注册的BeanPostProcessor接口实现类,如果实现类是InstantiationAwareBeanPostProcessor接口类型的,调用其postProcessProperties方法。
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
				// 删除与本次分析无关代码...
				pvs = pvsToUse;
			}
		}
		// 删除与本次分析无关代码...
	}
	
}
ログイン後にコピー

Spring では、@Autowired アノテーションは AutowiredAnnotationBeanPostProcessor クラスによって処理され、@Resource アノテーションは CommonAnnotationBeanPostProcessor クラスによって処理されます。どちらのクラスも InstantiationAwareBeanPostProcessor インターフェイスを実装し、オーバーライドされた postProcessProperties メソッドで完了します。 。ここでは @Autowired アノテーションの処理を分析します。

// AutowiredAnnotationBeanPostProcessor#postProcessProperties
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		// 根据beanName以及bean的class去查找Bean的依赖元数据-InjectionMetadata 
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {// 调用inject方法
			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;
	}
ログイン後にコピー

InjectionMetadata の inject メソッドで、現在の Bean の処理が必要な依存要素 (InjectedElement) をすべて取得します。これはコレクションであり、コレクションを走査して、各依存関係注入の inject メソッドを呼び出します。要素。

// InjectionMetadata#inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	// 获取当前Bean所有的依赖注入元素(可能是方法,也可能是字段)
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
		// 如果当前Bean的依赖注入项不为空,遍历该依赖注入元素
		for (InjectedElement element : elementsToIterate) {
			// 调用每一个依赖注入元素的inject方法。
			element.inject(target, beanName, pvs);
		}
	}
}
ログイン後にコピー

AutowiredAnnotationBeanPostProcessor クラスには 2 つの内部クラスが定義されています。AutowiredFieldElement と AutowiredMethodElement は InjectedElement から継承され、それぞれフィールド インジェクションとメソッド インジェクションに対応します。

Java のインタビューの質問: 循環依存関係とは何か知っていますか? Spring は循環依存関係をどのように解決するのでしょうか?

一般的に使用されるフィールド インジェクションを例に挙げます。AutowiredFieldElement の inject メソッドでは、まず現在のフィールドが処理済みかどうかを確認します。処理済みの場合は、それ以外の場合は、BeanFactory のsolveDependency メソッドを呼び出して依存関係を処理します。

// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Field field = (Field) this.member;
		Object value;
		if (this.cached) {// 如果当前字段已经被处理过,直接从缓存中获取
			value = resolvedCachedArgument(beanName, this.cachedFieldValue);
		} else {
			// 构建依赖描述符
			DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
			desc.setContainingClass(bean.getClass());
			Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
			Assert.state(beanFactory != null, "No BeanFactory available");
			TypeConverter typeConverter = beanFactory.getTypeConverter();
			try {// 调用BeanFactory的resolveDependency来解析依赖
				value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
			} catch (BeansException ex) {
				throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
			}
			// 删除与本次分析无关代码....
		}
		if (value != null) {
			// 通过反射来对属性进行赋值
			ReflectionUtils.makeAccessible(field);
			field.set(bean, value);
		}
	}
}
ログイン後にコピー

DefaultListableBeanFactory に実装されたsolveDependency メソッドは、最終的に doResolveDependency メソッドを呼び出して依存関係解決関数を完了します。 Spring ソース コードに do メソッドがある場合、このメソッドが実際に作業を実行する方法になります。

// DefaultListableBeanFactory#resolveDependency
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
		// .....
		// 如果在字段(方法)上添加了@Lazy注解,那么在这里将不会真正的去解析依赖
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
				descriptor, requestingBeanName);
		if (result == null) {
			// 如果未添加@Lazy注解,那么则调用doResolveDependency方法来解析依赖
			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		}
		return result;
}
ログイン後にコピー
// DefaultListableBeanFactory#doResolveDependency
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

	//.....
	try {
		// 根据名称以及类型查找合适的依赖
		Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
		if (matchingBeans.isEmpty()) {// 如果未找到相关依赖
			if (isRequired(descriptor)) { // 如果该依赖是必须的(例如@Autowired的required属性),直接抛出异常
				raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
			}
			return null;
		}

		String autowiredBeanName;
		Object instanceCandidate;
		// 如果查找到的依赖多于一个,例如某个接口存在多个实现类,并且多个实现类都注册到IoC容器中。
		if (matchingBeans.size() > 1) {// 决定使用哪一个实现类,@Primary等方式都是在这里完成
			autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
			if (autowiredBeanName == null) {
				if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
					return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
				} else { 
					return null;
				}
			}
			instanceCandidate = matchingBeans.get(autowiredBeanName);
		} else {
			// We have exactly one match.
			Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
			autowiredBeanName = entry.getKey();
			instanceCandidate = entry.getValue();
		}

		if (autowiredBeanNames != null) {
			autowiredBeanNames.add(autowiredBeanName);
		}
		// 如果查找到的依赖是某个类的Class(通常如此),而不是实例,
		//调用描述符的方法来根据类型resolveCandidate方法来获取该类型的实例。
		if (instanceCandidate instanceof Class) {
			instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
		}
		//...
}
ログイン後にコピー

依存関係記述子のresolveCandidateメソッドでは、BeanFactoryのgetBeanメソッドを呼び出すことで依存Beanインスタンスの取得が完了します。

// DependencyDescriptor#resolveCandidate
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
			throws BeansException {

	return beanFactory.getBean(beanName);
}
ログイン後にコピー

getBean メソッドの実装では、やはり doGetBean メソッドを呼び出すことで完了します。これは、私たちが自分で書いた依存関係の処理と基本的に同じですが、自分で書いたものは比較的単純で、Spring が考慮して処理しなければならないシナリオが複雑であるため、コードはより複雑になりますが、一般的な考え方は同じです。 。

// AbstractBeanFactory#getBean
public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}
ログイン後にコピー

重要な点は、循環依存関係を処理するために以前に作成したデモです。そのコードを理解し、Spring の循環依存関係の処理を見ると、それが非常に単純であることがわかります。

要約:

循環依存関係とは、2 つの Bean 間に相互参照関係があることを意味します。たとえば、A は B に依存し、B は A に依存します。ただし、Spring はプロパティの循環依存関係のみを解決でき、コンストラクターの循環依存関係は解決できません。 . このシナリオは解決できません。

Spring の循環依存関係に対するソリューションの鍵は、Bean の属性依存関係を処理するときに、まず Bean を第 3 レベルのキャッシュに保存することです。循環依存関係がある場合は、関連する Bean を第 3 レベルのキャッシュから取得します。一次キャッシュから削除され、二次キャッシュに格納され、初期化後に最終的に一次キャッシュに格納されます。

関連する推奨事項: Java 入門チュートリアル

以上がJava のインタビューの質問: 循環依存関係とは何か知っていますか? Spring は循環依存関係をどのように解決するのでしょうか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:csdn.net
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート