質問による分析: Spring Bean のライフサイクル | JD Logistics Technology Team

1: Spring コンテナで Bean がどのように保存および定義されるか

Spring の Bean の定義はorg.springframework.beans.factory.config.BeanDefinitionインターフェイスです。BeanDefinition には、Spring で作成した Java クラスのメタデータが格納されます。これには、次の主要なメタデータ情報が含まれます。

1:スコープ(Bean タイプ): 単一インスタンス Bean (Singleton) と複数インスタンス Bean (Prototype) が含まれます。

2: BeanClass : Beanのクラスタイプ

3: LazyInit : Bean を遅延ロードする必要があるかどうか

4: AutowireMode : 自動注入タイプ

5: dependsOn : Bean が依存する他の Bean の名前 Spring は最初に依存する Bean を初期化します。

6: PropertyValues : Beanのメンバー変数のプロパティ値

7: InitMethodName : Beanの初期化メソッド名

8: DestroyMethodName : Beanの破棄メソッド名

同時に、org.springframework.beans.factory.support.DefaultListableBeanFactoryクラスで保持されるBeanDefinitionMapにBeanDefinitionが格納され、ソースコードは次のようになります。

Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

BeanDefinitionの基本情報と格納場所を理解したところで、作成したBeanインスタンスがどこに格納されているか見てみましょう 作成したBeanは、 _ org.springframework.beans.factory.support.DefaultSingletonBeanRegistry _ クラスに格納されています。

_singletonObjects_では、Key は Bean の名前、Value は作成された Bean インスタンスです。

Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

基本情報を理解したら、次の 2 つの重要な質問に基づいてSpring Bean のライフサイクルを分析できます。

1: Java クラスは Spring によってどのようにスキャンされ、BeanDefinition に変換されるのですか?

2: BeanDefinition は Spring によってどのように処理され、直接使用できる Bean インスタンスに作成されるのでしょうか?

2: Java クラスは Spring によってどのように BeanDefinitions にスキャンされますか?

Spring で Bean を定義するには、XML ファイルや@Component@Service@Configurationなどのアノテーションの使用など、さまざまな方法があります。@Component アノテーションを例として、Spring が Bean をどのようにスキャンするかを調べてみましょう。@Componentアノテーションを使用して Bean をマークするには、 @ComponentScanアノテーションと組み合わせて使用​​する必要があることがわかっています。メインの起動クラスでマークされた@SpringBootApplicationアノテーションは、デフォルトで@ComponentScan アノテーションを継承します。

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

したがって、元の質問は、@ComponentScan アノテーションが Spring でどのように機能するかということに変わりました。

2.1 @ComponentScan アノテーションの仕組み

Spring フレームワークでは、このアノテーションに対応する処理クラスはComponentScanAnnotationParserであり、このクラスのparseメソッドがメインの処理ロジックとなります。このメソッドの簡単な処理ロジックは次のとおりです。

1: @ComponentScan アノテーションのBasePackage属性を取得します。そうでない場合は、アノテーションでマークされたクラスのパッケージ パスがデフォルトになります。

2: ClassPathBeanDefinitionScannerscanCandidateComponentsメソッドを使用して、 classpath:+basePackage+ /*.class**の下にあるすべてのクラス リソース ファイルをスキャンします。

3: 最後に、スキャンされたすべてのクラス リソース ファイルをループして @Component アノテーションが含まれているかどうかを確認し、含まれている場合は、これらのクラスを beandefinitionMap に登録します。

それ以来、コードで記述された Java クラスは Spring によって BeanDefinitions にスキャンされ、BeanDefinitionMap に保存されています。スキャンの詳細については、このクラスのソース コードを確認してください。

3: Spring が Bean インスタンスを作成する方法

Spring は、BeanDefinition に書き込んだ Java クラスをスキャンした後、Bean インスタンスの作成を開始し、Bean を作成するメソッドを _ org.springframework.beans.factory.support.AbstractBeanFactory#getBean _ メソッドに渡します。次に、 getBeanメソッドが Bean を作成する方法を見てみましょう。

getBeanメソッドの呼び出しロジックは次のとおりです: getBean--> doGetBean --> createBean --> doCreateBean 最後に、Spring は org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory #doCreateBeanメソッドを使用して Bean を作成します。 Bean インスタンスを作成する主なロジックは次のとおりです。Beanインスタンスの作成、Bean プロパティの入力、Bean の初期化、Bean の破棄の4 つの部分については、個別に説明します。

3.1 Beanインスタンスの作成

Bean インスタンスを作成するためのメソッドのエントリは次のとおりです。

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory# __ createBeanInstance

if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
}

このメソッドの主なロジックは、Bean を作成するコンストラクター メソッドとパラメーターを推論し、Java リフレクションを使用して Bean インスタンスを作成することです。

3.2 PopulateBeanメソッドのpopulateBean属性値

このメソッドの主な目的は、Bean に注入する必要があるメンバー属性を解析し、これらの属性を Bean に注入することです。Bean に他の依存 Bean がある場合は、最初に依存 Bean を作成してから、戻り値を返します。ここで Bean 作成の循環依存の問題が発生することに注意してください。これについては、この記事のセクション 6 で詳しく説明します。

3.4: Beanの初期化(initializeBeanメソッド)

Bean の初期化には主に 4 つの部分が含まれます。

3.4.1:invokeAwareMethods

このメソッドでは、BeanNameAware.setBeanName、BeanClassLoaderAware.setBeanClassLoader、BeanFactoryAware.setBeanFactory など、実装された Aware インターフェースのメソッドが主に呼び出されます。

Awareインタフェースの機能: Awareインタフェースのsetメソッドを呼び出して、Springコンテナ内の対応するBeanを作成中のBeanに注入します。

3.4.2: 前処理メソッドの呼び出し: applyBeanPostProcessorsBeforeInitialization

このメソッドの主な目的は、org.springframework.beans.factory.config.BeanPostProcessorインターフェースを実装する Spring コンテナ内のすべての実装クラスを取得し、ループ内でpostProcessBeforeInitializationメソッドを呼び出して、作成される Bean を処理することです。

したがって、このメソッドでは _ BeanPostProcessor _ をカスタマイズして Bean の機能を拡張し、独自の処理ロジックを実装できます。

3.4.3: Bean 関連の初期化メソッドを呼び出す:

3.4.3.1 InitializingBeanの場合、afterPropertiesSetメソッドを呼び出す

このプロセスでは、Spring フレームワークは、作成中の Bean が InitializingBean インターフェースを実装しているかどうかを判断し、実装している場合は、afterPropertiesSet メソッドを呼び出してコード ロジックを実行します。

3.4.3.2 カスタム初期化メソッドの呼び出し: initMethod

このプロセスでは、_ XML ファイルで構成されたinit-method および destroy-method 、またはアノテーションを使用して構成された@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod") _ メソッドなど、カスタム初期化メソッドが主に呼び出されます。

3.4.3.3: 後処理メソッドの呼び出し: applyBeanPostProcessorsAfterInitialization

このメソッドでは、主にorg.springframework.beans.factory.config.BeanPostProcessorインターフェースを実装する Spring コンテナ内のすべての実装クラスを取得し、ループ内でpostProcessAfterInitializationを呼び出して、作成中の Bean を処理します。

このメソッドでは、_ BeanPostProcessor _ をカスタマイズして、Bean の機能を拡張し、独自の処理ロジックを実装できます。

4: 登録Beanの破棄方法

ここでは主に、登録された Bean が破棄されたときに Spring が返すメソッドを示します。

1: XMLファイルに設定されたdestroy-methodメソッド、または@Beanアノテーションに設定されたdestroyMethodメソッド

2: org.springframework.beans.factory.DisposableBeanインターフェースの_ destroy _ メソッド

5: まとめ

ここまでで、作成したJavaクラスからSpringコンテナで利用できるBeanインスタンスまでの作成手順が完全に整理されましたので、Beanの作成手順を理解することでBeanの使い方にさらに慣れることができます。同時に、プロセスに独自の処理ロジックを追加して Bean を作成し、独自のコンポーネントを Spring フレームワークに接続することもできます。

6: Spring の循環依存関係の解決策

Spring が Bean インスタンスを生成する際、記述した Java クラスが相互依存することは避けられないことがありますが、この相互依存に Spring が対応していないと、Bean インスタンス生成の無限ループ問題が発生するため、Spring ではこの状況に特別に対処する必要があります。 Spring が Bean 間の循環依存関係の問題をどのように巧みに処理するかを見てみましょう。

6.1 フックメソッドgetEarlyBeanReferenceの公開

まず、単一インスタンス型 Bean の場合、Spring は Bean を作成するときに、作成される Bean のアドレス参照を取得するためのフック メソッドを事前に公開します。コードは次のとおりです。

上記のコードにあるように、フックメソッドのsingletonFactory は、事前にsingletonFactories Mapに格納され、この Bean のアドレス参照を事前に公開できるようになりますが、なぜアドレス参照の取得を複雑なメソッドにパッケージ化する必要があるのでしょうか。 ? 以下で説明します

6.2 他のBeanは事前に公開されているBeanのアドレス参照を取得する

他の Bean が作成中の Bean に依存する必要がある場合、getSingleton メソッドが呼び出されて、必要な Bean のアドレス参照を取得します。

アピールコードにあるように、Beanを取得する際は3箇所から取得することになります。

1: singletonObjects : これは、完全に作成されたBean インスタンスを格納するマップです

2: earlySingletonObjects :事前に公開されているフック使用して作成されたBeanインスタンスを格納するMapです。

3: singletonFactories : これはフック メソッドを格納するために使用されるマップです

依存Beanを取得する場合、フックメソッドgetEarlyBeanReferenceを呼び出して、事前に公開されているBeanへの参照を取得します。このメソッドのソースコードは次のとおりです。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
      }
    return exposedObject;
}



上記のソースコードに示すように、このメソッドでは主にSmartInstantiationAwareBeanPostProcessorgetEarlyBeanReferenceメソッドを呼び出して事前に作成されていない Bean を処理する必要があり、getEarlyBeanReferenceメソッドには論理実装クラス **org.springframework.aop.framework が 1 つだけあります。 .autoproxy.AbstractAutoProxyCreator , **このクラスは、Aop プロキシを作成するクラスであり、そのコードは次のとおりです。

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    //提前标记这个bean已经创建过代理对象了
    this.earlyProxyReferences.put(cacheKey, bean);
    //按条件创建代理对象
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}



上記のコードに示すように、このコードの主な目的は、事前に公開されている Bean が動的プロキシである必要があるかどうかを判断することであり、必要な場合は、事前に公開されている Bean の動的プロキシ オブジェクトが返されます。

では、なぜここで動的プロキシが必要かどうかを判断する必要があるのでしょうか? 次の状況を考えてみましょう

1: この Bean の動的プロキシ オブジェクトがここで返されないが、この Bean は後続の初期化プロセスで動的プロキシを持つ場合:

例: ここで、A が B に依存し、B が A に依存しているとします。このとき、B は A が公開している参照を事前に取得しています。このとき、A 自身のアドレス参照を B に返すと、A の元のアドレス参照が返されます。 Bを作成した後、プログラムがAの作成に戻ると、初期化処理(initializingBean)で動的プロキシが発生しますが、このとき実際にSpringコンテナ内でAの動的プロキシオブジェクトが使用され、しかし、B が元の A 参照を保持している場合、コンテナ内には A の元の参照と A の動的プロキシの参照が存在し、あいまいさが生じます。そのため、動的プロキシを作成する必要があるかどうかを事前に判断する必要があります。この理由の問題点は、属性のpopulateBean処理が初期化処理(initializingBean)の前にあり、動的プロキシの作成処理が初期化処理中にあることです。

6.3 Beanのアドレスが変更されたかどうかを確認する

Bean が初期化された後、Spring は Bean 初期化後のアドレスが変更されたかどうかを判断します。コードロジックは次のとおりです。

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    //判断是否触发了提前创建bean的逻辑(getEarlyBeanReference)
    //如果有其他bean触发了提前创建bean的逻辑,那么这里就不为null
    if (earlySingletonReference != null) {
        //判断引用地址是否发生了变化
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
	}
    }
}



では、なぜ初期化後に Bean のアドレスが変更されたかどうかを判断し続ける必要があるのでしょうか?

これは、循環依存関係があり、 Bean の初期化プロセス (initializingBean) 中に追加の動的プロキシが発生する場合、たとえば **getEarlyBeanReference で発生する動的プロキシに加えて、追加の動的プロキシが発生するためです。 2 つの動的プロキシが発生した場合、この時点の Bean のアドレスは、getEarlyBeanReference プロセスで生成された Bean のアドレスとは異なります。 **この時点でこの状況が処理されない場合、Spring には 2 つの同時インスタンスが存在します。参照オブジェクトが異なるとあいまいさが生じるため、Spring はこの状況を回避する必要があります。

6.4 Beanアドレスが変化した場合、依存性の高いBeanが存在するかどうかを確認する

Spring が Bean 作成プロセス中にセクション 6.3 で説明されている状況に遭遇した場合、Spring は次の方法を採用してそれを処理します。

else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    //获取该Bean依赖的Bean
    String[] dependentBeans = getDependentBeans(beanName);
    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    //去除因为类型检查而创建的Bean(doGetBean方法typeCheckOnly参数来控制)
    for (String dependentBean : dependentBeans) {
        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
	    actualDependentBeans.add(dependentBean);
        }
    }
    //如果去除因为类型检查而创建的bean之外还存在依赖的bean
    //(这些剩下的bean就是spring实际需要使用的)那么就会抛出异常,阻止问题出现
    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.");
    }
}



上記のコードは、アピールを処理するための Spring のロジックです。まず、Spring がアピールの発生を許可していないことは明らかです。Spring の Bean の参照アドレスが変更されると、Spring はまず、依存する Bean が完全に作成されたかどうかを判断します。これらの完全に作成された依存 Bean に保持されている参照はすでに古いアドレス参照であるため、作成された Bean は直接例外をスローします。

具体的な処理ロジックは次のとおりです:まず、Bean が依存するすべての Bean を取得し、次にこれらの Bean から型チェック専用に作成された Bean を除外します。これらの Bean を除外した後も依存する Bean がまだ存在する場合、これらの Bean は存在する可能性があります。周期的に依存し、依存性が強い Bean (これらの Bean に保持されている参照アドレスは古いアドレスであるため、問題が発生します) の場合、Spring は問題を回避するために適時に例外をスローします。

著者: JD Logistics Zhong Lei

出典:JD Cloud Developer Community Ziyuanqishuo Tech 転載の際は出典を明記してください

Lei Jun氏はXiaomiのThePaper OSの完全なシステムアーキテクチャを発表し、最下層が完全に再構築されたと述べ、 Yuque氏は10月23日に障害の原因と修復プロセスを発表 Microsoft CEOのナデラ氏「Windows Phoneとモバイル事業を放棄したのは間違った決断だった」 . Java 11 と Java 17 の使用率は両方とも Java 8 を上回りました . Hugging Face はアクセスを制限されました. Yuque ネットワークの停止は約 10 時間続きましたが、現在は通常に戻っています. Oracle は Visual Studio Code 用の Java 開発拡張機能を開始しました. The National Data Administration マスク氏、ウィキペディアを「魏事百科事典」に改名したら10億寄付 USDMySQL 8.2.0 GAを正式発表
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10123189