webentwicklung-frage-antwort-db.com.de

Müssen Spring-Prototyp-Beans manuell zerstört werden?

Ich habe festgestellt, dass die @PreDestroy-Haken meiner Spring-Beans mit Prototyp-Umfang nicht ausgeführt wurden.

Ich habe hier gelesen, dass dies eigentlich beabsichtigt ist. Der Spring-Container zerstört Singleton-Beans, Prototyp-Beans jedoch nicht. Mir ist unklar warum. Wenn der Spring-Container mein Prototyp-Bean erstellt und seinen @PostConstruct-Hook ausführt, warum wird er dann auch nicht mein Bean zerstören, wenn der Container geschlossen ist? Ist es nach dem Schließen meines Spring-Containers überhaupt sinnvoll, die Bohnen weiter zu verwenden? Ich kann kein Szenario sehen, in dem Sie einen Container schließen möchten, bevor Sie mit seinen Beans fertig sind. Ist es sogar möglich, eine Prototyp-Springbohne weiter zu verwenden, nachdem der Behälter geschlossen wurde?

Das Obige beschreibt den rätselhaften Hintergrund meiner Hauptfrage: Wenn der Spring-Container keine Prototyp-Beans zerstört, bedeutet dies, dass ein Speicherverlust auftreten könnte? Oder wird die Prototyp-Bohne irgendwann Müll gesammelt?

In den Frühlingsdokumentationen heißt es:

Der Clientcode muss Objekte im Prototypbereich bereinigen und .__ freigeben. teure Ressourcen, die die Prototyp-Bean (s) halten. Um das zu bekommen Frühlingscontainer zum Freigeben von Ressourcen, die von Beans aus Prototypen gespeichert werden. Versuchen Sie es mit einem benutzerdefinierten Bean-Post-Prozessor, der eine Referenz auf .__ enthält. Bohnen, die aufgeräumt werden müssen.

Was bedeutet das? Der Text legt nahe, dass ich als Programmierer dafür verantwortlich bin, meine Prototyp-Beans explizit (manuell) zu zerstören. Ist das richtig? Wenn ja, wie mache ich das?

6
IqbalHamid

Im Interesse anderer werde ich im Folgenden erläutern, was ich aus meinen Ermittlungen gezogen habe:

Solange die Prototyp-Bean selbst keinen Verweis auf eine andere Ressource wie eine Datenbankverbindung oder ein Sitzungsobjekt enthält, wird Müll aufgesammelt, sobald alle Verweise auf das Objekt entfernt wurden oder das Objekt den Gültigkeitsbereich verlässt. Es ist daher in der Regel nicht notwendig, eine Prototyp-Bean explizit zu zerstören.

Wenn jedoch, wie oben beschrieben, ein Speicherverlust auftreten kann, können Prototyp-Beans zerstört werden, indem ein Post-Prozessor für Singleton-Beans erstellt wird, dessen Zerstörungsmethode explizit die Vernichtungs-Hooks Ihrer Prototyp-Beans aufruft. Da der Postprozessor selbst im Gültigkeitsbereich von Singleton ist, wird sein Zerstörungshaken will von Spring aufgerufen:

  1. Erstellen Sie einen Bean-Post-Prozessor, um die Zerstörung aller Prototyp-Beans zu bewältigen. Dies ist erforderlich, da Spring keine Prototyp-Beans zerstört. Daher werden @PreDestroy-Hooks in Ihrem Code niemals vom Container aufgerufen. 

  2. Implementieren Sie die folgenden Schnittstellen:

    1 .BeanFactoryAware
    Diese Schnittstelle stellt eine Rückrufmethode bereit, die ein Beanfactory-Objekt empfängt. Dieses BeanFactory-Objekt wird in der Postprozessor-Klasse verwendet, um alle Prototyp-Beans über ihre BeanFactory.isPrototype-Methode (String beanName) zu identifizieren .

    2. DisposableBean
    Diese Schnittstelle stellt eine Destroy () - Rückrufmethode bereit, die vom Spring-Container aufgerufen wird. Wir werden die Destroy () -Methoden aller unserer Prototyp-Beans innerhalb dieser Methode aufrufen.

    3. BeanPostProcessor
    Die Implementierung dieser Schnittstelle ermöglicht den Zugriff auf Nachbearbeitungs-Callbacks, von denen aus eine interne Liste <> aller Prototypobjekte erstellt wird, die vom Spring-Container instanziiert werden. Wir werden diese Liste später durchlaufen, um jeden unserer Prototyp-Beans zu zerstören.


3. Implementieren Sie schließlich die DisposableBean-Schnittstelle in jedem Ihrer Prototyp-Beans und stellen Sie die Destroy () - Methode bereit, die für diesen Vertrag erforderlich ist.

Um diese Logik zu veranschaulichen, gebe ich unten folgenden Code aus diesem 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();
        }
    }
}
7
IqbalHamid

Ihre Antwort ist großartig. Ich möchte auch einige Anmerkungen zu einer alternativen Lösung mitteilen, die es Prototypmitgliedern ermöglicht, die durch die Verwendung der inneren Beans nativ vom Lebenszyklus des Spring-IoC-Containers verwaltet werden.

Ich habe kürzlich eine Antwort auf eine separate Frage zu inneren Bohnen geschrieben. Innere Beans werden durch Zuweisen von Bean-Eigenschaftswerten als BeanDefinition -Objekte erstellt. Bean-Definitionseigenschaftswerte werden automatisch in (inner) - Instanzen (als verwaltete Singleton-Beans) der von ihnen definierten Bean aufgelöst.

Das folgende XML-Kontextkonfigurationselement kann verwendet werden, um für jede Referenz, die verwaltet werden soll, eigene autowireable ForkJoinPool -Beans zu erstellen (@PreDestroy Wird beim Herunterfahren des Kontexts aufgerufen):

<!-- 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>

Dieses Verhalten ist jedoch davon abhängig, dass die Referenz als Eigenschaftswert einer Bean-Definition zugewiesen wird. Dies bedeutet, dass @Autowired - und Konstruktorinjektion standardmäßig nicht damit funktionieren, da diese Methoden für das automatische Verdrahten den Wert sofort auflösen, anstatt die Eigenschaftswertauflösung in AbstractAutowireCapableBeanFactory#applyPropertyValues Zu verwenden =. Autowiring nach Typ funktioniert auch nicht, da sich die Typauflösung nicht über Beans ausbreitet, die BeanDefinitions sind, um den produzierten Typ zu finden.

Diese Methode funktioniert nur, wenn eine der beiden Bedingungen erfüllt ist:

  • Die abhängigen Beans sind also in XML definiert
  • oder wenn der Autowire-Modus auf AutowireCapableBeanFactory#AUTOWIRE_BY_NAME Eingestellt ist
<!-- 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>

Es könnten wahrscheinlich zwei zusätzliche Änderungen vorgenommen werden, um die Konstruktorinjektion und die @Autowired - Injektion zu ermöglichen.

  • Konstruktor-Injection:

    Die Bean-Factory weist ein AutowireCandidateResolver für die Konstruktorinjektion zu. Der Standardwert (ContextAnnotationAutowireCandidateResolver) kann überschrieben werden (DefaultListableBeanFactory#setAutowireCandidateResolver), Um einen Resolver-Kandidaten anzuwenden, der nach geeigneten Beans des Typs BeanDefinition für die Injektion sucht.

  • @Autowired - Injektion:

    Der AutowiredAnnotationBeanPostProcessor Bean-Postprozessor setzt Bean-Werte direkt, ohne BeanDefinition innere Beans aufzulösen. Dieser Postprozessor könnte überschrieben werden, oder es könnte ein separater Bean-Postprozessor erstellt werden, um eine benutzerdefinierte Anmerkung für verwaltete Prototyp-Beans zu verarbeiten (z. B. @AutowiredManagedPrototype).

0
Mike Hill