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 '" + beanName + "'"); } 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 から継承され、それぞれフィールド インジェクションとメソッド インジェクションに対応します。
一般的に使用されるフィールド インジェクションを例に挙げます。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 サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

Java は、初心者と経験豊富な開発者の両方が学習できる人気のあるプログラミング言語です。このチュートリアルは基本的な概念から始まり、高度なトピックに進みます。 Java Development Kit をインストールしたら、簡単な「Hello, World!」プログラムを作成してプログラミングを練習できます。コードを理解したら、コマンド プロンプトを使用してプログラムをコンパイルして実行すると、コンソールに「Hello, World!」と出力されます。 Java の学習はプログラミングの旅の始まりであり、習熟が深まるにつれて、より複雑なアプリケーションを作成できるようになります。
