web-dev-qa-db-ja.com

SpringプロトタイプBeanは手動で破棄する必要がありますか?

プロトタイプスコープのSpring Beanの@PreDestroyフックが実行されていないことに気付きました。

それ以来、私は here を読んでいますが、これは実際には設計によるものです。 SpringコンテナはシングルトンBeanを破棄しますが、プロトタイプBeanは破棄しません。理由ははっきりしていません。 SpringコンテナがプロトタイプBeanを作成し、その@PostConstructフックを実行する場合、コンテナが閉じられたときに、Beanも破壊しないのはなぜですか? Springコンテナを閉じた後、そのBeanのいずれかを使用し続けることは理にかなっていますか? Beanの処理を完了する前にコンテナを閉じたいシナリオはありません。コンテナが閉じられた後、プロトタイプのSpring Beanを使用し続けることさえ可能ですか?

上記は私の主要な質問の不可解な背景について説明しています。SpringコンテナがプロトタイプBeanを破壊していない場合、メモリリークが発生する可能性がありますか?または、プロトタイプBeanはある時点でガベージコレクションされますか?

Springドキュメントには次のように記載されています。

クライアントコードは、プロトタイプスコープのオブジェクトをクリーンアップし、プロトタイプBeanが保持している高価なリソースを解放する必要があります。プロトタイプスコープのBeanが保持するリソースをSpringコンテナに解放させるには、クリーンアップが必要なBeanへの参照を保持するカスタムBeanポストプロセッサを使用してみてください。

どういう意味ですか?テキストは、私が、プログラマーとして、プロトタイプBeanを明示的に(手動で)破棄する責任があることを示唆しています。これは正しいです?もしそうなら、どうすればいいですか?

14
IqbalHamid

他の人々の利益のために、調査から収集したものを以下に示します。

プロトタイプBean自体がデータベース接続やセッションオブジェクトなどの別のリソースへの参照を保持していない限り、オブジェクトへのすべての参照が削除されるか、オブジェクトがスコープ外になるとすぐにガベージコレクションが行われます。したがって、通常、プロトタイプBeanを明示的に破棄する必要はありません。

ただし、上記のようにメモリリークが発生する可能性がある場合は、破壊メソッドがプロトタイプBeanの破壊フックを明示的に呼び出すシングルトンBeanポストプロセッサを作成することにより、プロトタイプBeanを破壊できます。ポストプロセッサ自体がシングルトンスコープであるため、その破壊フックはSpringによって呼び出されます

  1. Beanポストプロセッサを作成して、すべてのプロトタイプBeanの破壊を処理します。これは、SpringがプロトタイプBeanを破棄せず、コード内の@PreDestroyフックがコンテナーによって呼び出されないために必要です。

  2. 以下のインターフェースを実装します。

    1。BeanFactoryAware
    このインターフェイスは、Beanfactoryオブジェクトを受け取るコールバックメソッドを提供します。このBeanFactoryオブジェクトは、BeanFactory.isPrototype(String beanName)メソッドを介してすべてのプロトタイプBeanを識別するために、ポストプロセッサクラスで使用されます。

    2。 DisposableBean
    このインターフェイスは、Springコンテナによって呼び出されるDestroy()コールバックメソッドを提供します。このメソッド内からすべてのプロトタイプBeanのDestroy()メソッドを呼び出します。

    3。 BeanPostProcessor
    このインターフェイスを実装すると、内部からポストプロセスコールバックへのアクセスが提供され、Springコンテナによってインスタンス化されたすべてのプロトタイプオブジェクトの内部List <>が準備されます。後でこのList <>をループして、プロトタイプBeanをそれぞれ破棄します。


3。最後に、各プロトタイプBeanにDisposableBeanインターフェースを実装し、このコントラクトに必要なDestroy()メソッドを提供します。

このロジックを説明するために、この article から抜粋したコードを以下に示します。

/**
* Bean PostProcessor that handles destruction of prototype beans
*/
@Component
public class DestroyPrototypeBeansPostProcessor implements BeanPostProcessor, BeanFactoryAware, DisposableBean {

    private BeanFactory beanFactory;

    private final List<Object> prototypeBeans = new LinkedList<>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanFactory.isPrototype(beanName)) {
            synchronized (prototypeBeans) {
                prototypeBeans.add(bean);
            }
        }
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void destroy() throws Exception {
        synchronized (prototypeBeans) {
            for (Object bean : prototypeBeans) {
                if (bean instanceof DisposableBean) {
                    DisposableBean disposable = (DisposableBean)bean;
                    try {
                        disposable.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            prototypeBeans.clear();
        }
    }
}
15
IqbalHamid

あなたの答えは素晴らしいです。また、内部Beanを使用してSpring IoCコンテナーライフサイクルによってネイティブに管理されるプロトタイプメンバーを許可する代替ソリューションに関するメモを共有したいと思います。

私は最近 answer をインナーBeanに関する別の質問に書きました。内部Beanは、Beanプロパティ値をBeanDefinitionオブジェクトとして割り当てることによって作成されます。 Bean定義プロパティ値は、それらが定義するBeanの(inner)インスタンス(マネージドシングルトンBean)に自動的に解決されます。

次のXMLコンテキスト構成要素を使用して、管理される参照ごとに個別の自動配線可能なForkJoinPool Beanを作成できます(コンテキストのシャットダウン時に@PreDestroyが呼び出されます)。

<!-- Prototype-scoped bean for creating distinct FJPs within the application -->
<bean id="forkJoinPool" class="org.springframework.beans.factory.support.GenericBeanDefinition" scope="prototype">
    <property name="beanClass" value="org.springframework.scheduling.concurrent.ForkJoinPoolFactoryBean" />
</bean>

ただし、この動作は、参照がBean定義のプロパティ値として割り当てられることを条件とします。つまり、これらの自動配線メソッドは @Autowired でプロパティ値の解決を使用するのではなく、すぐに値を解決するため、AbstractAutowireCapableBeanFactory#applyPropertyValues-およびconstructor-injectionはデフォルトでは動作しません。型解決は、生成された型を見つけるためのBeanDefinitionsであるBeanを介して伝播されないため、型による自動配線も機能しません。

この方法は、2つの条件のいずれかが真である場合にのみ機能します:

  • 依存するBeanはalsoXMLで定義されています
  • または自動配線モードがAutowireCapableBeanFactory#AUTOWIRE_BY_NAMEに設定されている場合
<!-- Setting bean references through XML -->
<beans ...>
    <bean id="myOtherBean" class="com.example.demo.ForkJoinPoolContainer">
        <property name="forkJoinPool" ref="forkJoinPool" />
    </bean>
</beans>

<!-- Or setting the default autowire mode -->
<beans default-autowire="byName" ...>
    ...
</beans>

Constructor-injectionおよび@Autowired- injectionを有効にするために、2つの追加変更が行われる可能性があります。

  • コンストラクターインジェクション:

    Beanファクトリーは、コンストラクター注入のためにAutowireCandidateResolverを割り当てます。デフォルト値(ContextAnnotationAutowireCandidateResolver)をオーバーライド(DefaultListableBeanFactory#setAutowireCandidateResolver)して、タイプBeanDefinitionの適格なBeanをシークする候補リゾルバーを適用できます。

  • @Autowired- injection:

    AutowiredAnnotationBeanPostProcessor Beanポストプロセッサは、BeanDefinition内部Beanを解決せずにBean値を直接設定します。このポストプロセッサをオーバーライドするか、マネージプロトタイプBeanのカスタムアノテーションを処理するために別のBeanポストプロセッサを作成できます(例:@AutowiredManagedPrototype)。

0
Mike Hill