CodeGym /Kurse /Spring Core /Lifecycle-verwaltete Beans in ContextFlow

Lifecycle-verwaltete Beans in ContextFlow

Spring Core
Level 10, Lektion 4
Verfügbar

1. Lifecycle in einzelnen Beans

Wenn Sie gerade erst anfangen, Anwendungen zu schreiben, ist es sehr leicht, in die Falle zu tappen: „Ach, ich rufe einfach prepare() vor der Arbeit auf und close() danach.“ In der ersten Woche funktioniert das sogar. In der zweiten kommt ein zweiter Service hinzu, der ebenfalls „einfach prepare aufruft“. In der dritten vergisst jemand, close() aufzurufen, und Sie erleben merkwürdige Effekte: Dateien werden nicht fertig geschrieben, das Verzeichnis wird nicht erstellt, eine Vorlage ist „irgendwie null“ – und schuld ist natürlich Spring (Spoiler: nein).

In der Containerwelt gibt es eine einfache Philosophie: Der Container ist für die Bereitschaft der Abhängigkeiten verantwortlich. Ein Service sollte sich nicht merken müssen, dass seine Abhängigkeit ein „geheimes Aufweckritual“ hat, sonst wird aus DI „DI + Schamanismus“. Ein Lifecycle-verwalteter Bean ist wie ein Mitarbeiter, der etwas früher zur Arbeit kommt, das Licht einschaltet, die Tür öffnet und überprüft, ob die Kaffeemaschine nicht explodiert ist. Den Services bleibt es, die Arbeit zu erledigen, anstatt sich um infrastrukturelle Aufgaben zu kümmern.

Um den Kontrast zu verdeutlichen, sehen wir uns ein Mini-Gegenbeispiel an (machen Sie das nicht, auch wenn Sie es „schnell“ wollen):

class ReportingService {

    void generate() {
        var output = new ReportOutputManager("build/reports");
        output.prepare();  // "nicht vergessen aufzurufen"
        output.writeReport("daily.txt", "...");
        output.close();    // "nicht vergessen aufzurufen"
    }
}

Das Problem ist nicht einmal, dass es „ohne Spring“ ist. Das Problem ist, dass Bereitschaft und Beendigung zur Aufgabe des Business-Service wurden. Sobald ein zweites Szenario zur Berichtserstellung hinzukommt, beginnt sich das „nicht vergessen, prepare/close aufzurufen“ wie Gremlins nach Mitternacht zu vermehren.

Und hier ist es sinnvoll, die Wahl zwischen eager und lazy gleich zu verankern. Für NotificationTemplateCatalog und ReportOutputManager behalten wir bewusst den eager-Standard bei: Wenn der Vorlagenkatalog nicht geladen oder das Ausgabeverzeichnis nicht vorbereitet werden kann, soll ContextFlow beim Start abstürzen, nicht beim ersten Auftrag. Lazy wäre nur sinnvoll für ein wirklich optionales Unterszenario, das in diesem Durchlauf möglicherweise gar nicht benötigt wird.

Unser Ziel heute ist es, das Gegenteil zu tun: Die Services erhalten bereits fertige Abhängigkeiten, und Spring garantiert, dass init und destroy zum richtigen Zeitpunkt stattfinden.

2. NotificationTemplateCatalog: Vorlagenkatalog

Der Benachrichtigungsvorlagenkatalog ist ein hervorragendes Beispiel für einen Bean, der vor der ersten Nutzung „fertig“ sein muss. Selbst wenn die Vorlagen derzeit einfach sind und direkt im Code leben, werden sie in einem echten Projekt fast immer zu einer Sammlung von Schlüsseln, Texten und Parametern. Und wenn Sie diese Vorlagen jedes Mal beim Senden einer Benachrichtigung laden/erstellen, erhalten Sie schnell doppelte Logik und unnötige Berechnungen, manchmal sogar unterschiedliche Versionen von Vorlagen an verschiedenen Stellen.

Die Hauptidee von NotificationTemplateCatalog ist sehr menschlich: „Ich bin ein Nachschlagewerk. Man füllt mich einmal, dann kommt man nur noch zu mir, um eine Antwort zu holen.“ Daher sollte der Konstruktor des Katalogs leicht sein (Objekt erstellen), und die eigentliche Vorbereitung findet in der init-Phase statt. Wir wählen @PostConstruct, weil die Klasse uns gehört und es praktisch ist, die init-Logik direkt neben dem Zustand zu halten.

Implementierung des Katalogs

package com.example.contextflow.infrastructure.notification;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class NotificationTemplateCatalog {

    // Interner Zustand des Katalogs: einmal füllen, viele Male lesen
    private final Map<String, String> templates = new HashMap<>();

    @PostConstruct
    void loadDefaults() {
        // Init-Phase: Vorbereitung der Katalogdaten VOR der ersten Verwendung
        templates.put("order-created", "Order %s created");
        templates.put("order-cancelled", "Order %s cancelled");
        System.out.println("templates loaded: " + templates.size()); // templates loaded: 2
    }

    public String templateFor(String key) {
        // In der Arbeitsphase lesen wir nur die fertigen Daten (ohne "lass uns nochmal laden")
        return templates.get(key);
    }

    @PreDestroy
    void clear() {
        // Destroy-Phase: Ressourcen/Speicher freigeben und planmäßig beenden
        templates.clear();
        System.out.println("templates cleared"); // templates cleared
    }
}

Einige wichtige methodische Punkte sind hier nicht in den Annotationen versteckt, sondern in der Logik.

loadDefaults ist kein Business-Szenario. Es erstellt keine Bestellung, sendet keine Benachrichtigung, schreibt kein Audit. Es macht genau eine Sache: das interne Vorbereiten des Zustands. Es führt die Arbeit aus, die nötig ist, damit das Objekt „bereit“ wird.

clear sollte auch nicht zu einer Serie werden wie „und zum Abschied...“. Es beendet einfach die Arbeit des Katalogs: gibt Speicher frei und hinterlässt eine nachvollziehbare Spur in der Konsole.

Wenn Ihr Projekt aufgrund von jakarta.annotation.* nicht kompiliert, ist das nicht „Spring ist kaputt“, sondern die Abhängigkeit wurde einfach nicht eingebunden. Im Übungs-Baseline wird sie normalerweise explizit hinzugefügt:

Datei: build.gradle.kts

dependencies {
    // Benötigt für @PostConstruct/@PreDestroy (das ist Jakarta API, nicht "eingebautes Java")
    implementation("jakarta.annotation:jakarta.annotation-api")
}

(Die Version wird über BOM bezogen, falls Sie es bereits wie im Baseline des Kurses eingebunden haben.)

Einbinden des Katalogs in den Benachrichtigungsversand-Service

Jetzt machen wir etwas Wichtiges: Der Service sollte nicht „prüfen, ob Vorlagen geladen sind“. Er sollte einfach die Vorlage nehmen und den Text erstellen.

package com.example.contextflow.application.service;

import com.example.contextflow.domain.ports.NotificationSender;
import com.example.contextflow.infrastructure.notification.NotificationTemplateCatalog;
import org.springframework.stereotype.Service;

@Service
public class NotificationDispatchService {

    private final NotificationSender sender;
    private final NotificationTemplateCatalog templates;

    public NotificationDispatchService(NotificationSender sender,
                                       NotificationTemplateCatalog templates) {
        this.sender = sender;
        this.templates = templates;
    }

    public void notifyOrderCreated(String orderId) {
        // Zu diesem Zeitpunkt hat der Container bereits die Init-Phase des Katalogs garantiert
        String text = templates.templateFor("order-created").formatted(orderId);
        // Business-Logik des Services: Benachrichtigung senden, nicht "Abhängigkeit initialisieren"
        sender.send(text);
    }
}

Beachten Sie, was hier nicht vorhanden ist. Kein templates.loadDefaults(). Kein if (templates.isReady()). Kein „sicherheitshalber neu laden“. Das ist der Hauptgewinn: Der Container garantiert, dass der Katalog zum Zeitpunkt des Aufrufs von notifyOrderCreated() bereits die Init-Phase durchlaufen hat.

3. ReportOutputManager: Berichte und Dateien

Berichte sind der Teil der Anwendung, der fast immer das Dateisystem betrifft. Und genau hier entsteht am häufigsten „Magie in den Services“: Jemand erstellt ein Verzeichnis, jemand baut einen Pfad zusammen, jemand öffnet einen Writer, jemand vergisst, den Writer zu schließen, und dann rätseln wir, warum der Bericht leer ist. Das ähnelt der Situation, in der jeder Entwickler im Team einen Schraubenzieher mit sich führt und selbst entscheidet, wie die Steckdose repariert wird: Es geht zwar irgendwie, aber besser nicht.

Daher führen wir den ReportOutputManager als infrastrukturelles Objekt ein, das weiß, wo das Ausgabeverzeichnis liegt, es vorbereiten und die Arbeit korrekt beenden kann. Und hier wählen wir bewusst den Stil initMethod/destroyMethod in @Bean, damit die Klasse selbst ein gewöhnliches Java-Klasse ohne Spring-Annotationen bleibt.

Die Klasse ReportOutputManager selbst

package com.example.contextflow.support.lifecycle;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class ReportOutputManager {

    // Wohin wir die Berichtsartefakte schreiben (im Übungsprojekt nur in build/)
    private final Path outputDir;

    // Ressource, die beim Shutdown korrekt geschlossen werden muss
    private BufferedWriter indexWriter;

    public ReportOutputManager(Path outputDir) {
        // Leichter Konstruktor: speichert nur die Konfiguration
        this.outputDir = outputDir;
    }

    public void prepare() {
        // Init-Phase: Dateisystem vorbereiten und Writer einmal öffnen
        try {
            Files.createDirectories(outputDir);
            indexWriter = Files.newBufferedWriter(outputDir.resolve("index.txt"));
            System.out.println("report output ready: " + outputDir); // report output ready: build/reports
        } catch (IOException e) {
            // Fail-fast: Besser beim Start abstürzen als beim ersten Benutzerszenario
            throw new IllegalStateException("Cannot prepare report output: " + outputDir, e);
        }
    }
}

Wir sehen bereits, warum das praktisch ist: prepare() erledigt die „infrastrukturelle Drecksarbeit“ einmal. Wenn das Verzeichnis nicht erstellt werden kann, stürzen wir beim Start ab (Fail-Fast), nicht erst nach 15 Minuten, wenn der Benutzer zum ersten Mal einen Bericht anfordert.

Jetzt fügen wir in dieselbe Klasse eine Arbeitsmethode zum Schreiben hinzu:

public Path writeReport(String fileName, String content) {
    // Arbeitsphase: Berichtsdatei schreiben und Eintrag in index.txt hinzufügen
    try {
        Path reportFile = outputDir.resolve(fileName);
        Files.writeString(reportFile, content);
        indexWriter.write(fileName + System.lineSeparator());
        indexWriter.flush();
        return reportFile;
    } catch (IOException e) {
        throw new IllegalStateException("Cannot write report: " + fileName, e);
    }
}

Und hier fügen wir auch die Destroy-Logik hinzu:

public void close() {
    // Destroy-Phase: Ressource freigeben, die in prepare() geöffnet wurde
    try {
        if (indexWriter != null) {
            indexWriter.close();
        }
        System.out.println("report output closed"); // report output closed
    } catch (IOException e) {
        System.out.println("report output close failed: " + e.getMessage());
    }
}

Um nicht die Fragmente im Kopf zusammenkleben zu müssen, hier die endgültige Version der gesamten Klasse:

package com.example.contextflow.support.lifecycle;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class ReportOutputManager {

    // Wohin wir die Berichtsartefakte schreiben (im Übungsprojekt nur in build/)
    private final Path outputDir;

    // Ressource, die beim Shutdown korrekt geschlossen werden muss
    private BufferedWriter indexWriter;

    public ReportOutputManager(Path outputDir) {
        // Leichter Konstruktor: speichert nur die Konfiguration
        this.outputDir = outputDir;
    }

    public void prepare() {
        // Init-Phase: Dateisystem vorbereiten und Writer einmal öffnen
        try {
            Files.createDirectories(outputDir);
            indexWriter = Files.newBufferedWriter(outputDir.resolve("index.txt"));
            System.out.println("report output ready: " + outputDir); // report output ready: build/reports
        } catch (IOException e) {
            // Fail-fast: Besser beim Start abstürzen als beim ersten Benutzerszenario
            throw new IllegalStateException("Cannot prepare report output: " + outputDir, e);
        }
    }

    public Path writeReport(String fileName, String content) {
        // Arbeitsphase: Berichtsdatei schreiben und Eintrag in index.txt hinzufügen
        try {
            Path reportFile = outputDir.resolve(fileName);
            Files.writeString(reportFile, content);
            indexWriter.write(fileName + System.lineSeparator());
            indexWriter.flush();
            return reportFile;
        } catch (IOException e) {
            throw new IllegalStateException("Cannot write report: " + fileName, e);
        }
    }

    public void close() {
        // Destroy-Phase: Ressource freigeben, die in prepare() geöffnet wurde
        try {
            if (indexWriter != null) {
                indexWriter.close();
            }
            System.out.println("report output closed"); // report output closed
        } catch (IOException e) {
            System.out.println("report output close failed: " + e.getMessage());
        }
    }
}

Bean-Registrierung mit initMethod/destroyMethod

Hier ist der eager-Standard genau richtig: prepare() sollte während des Start des Kontextes ausgeführt werden. Wenn build/reports nicht vorbereitet werden kann, ist es besser, dies vor dem Start des Szenarios zu erfahren, als beim ersten Aufruf von writeReport().

package com.example.contextflow.config.reporting;

import com.example.contextflow.support.lifecycle.ReportOutputManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.file.Path;

@Configuration
public class ReportingConfig {

    // Container ruft prepare() bei Initialisierung und close() beim Schließen des Kontextes auf
    @Bean(initMethod = "prepare", destroyMethod = "close")
    public ReportOutputManager reportOutputManager() {
        return new ReportOutputManager(Path.of("build/reports"));
    }
}

Hier gibt es einen sehr wichtigen praktischen Effekt: ReportOutputManager weiß gar nicht, dass er ein Bean ist. Er ist nicht von Spring abhängig und kann bei Bedarf sowohl in Plain Java als auch in Tests ohne Container verwendet werden. Und der Lebenszyklus wird dort beschrieben, wo er hingehört: in der Konfiguration.

Verwendung des Output Managers im ReportingService

package com.example.contextflow.application.reporting;

import com.example.contextflow.support.lifecycle.ReportOutputManager;
import org.springframework.stereotype.Service;

@Service
public class ReportingService {

    private final ReportOutputManager output;

    public ReportingService(ReportOutputManager output) {
        this.output = output;
    }

    public void writeDailyReport(String text) {
        output.writeReport("daily-report.txt", text);
    }
}

Wieder: Der Service ruft weder prepare() noch close() auf. Seine Aufgabe ist es, den Inhalt zu erstellen und den Manager um das Schreiben zu bitten. Die Bereitschaft wird vom Container garantiert.

4. Einbindung in Spring und Start

Bisher haben wir Klassen geschrieben, die „zwar schön aussehen“, aber im Herzen jedes Juniors gibt es die berechtigte Frage: „Okay… und wie kann ich sicher sein, dass das alles wirklich aufgerufen wurde und nicht tot herumliegt?“ Und das ist eine ausgezeichnete Frage – denn der Lebenszyklus wird am besten mit den Augen verstanden: anhand der Reihenfolge der Ausgaben auf der Konsole, der erstellten Dateien und der Tatsache, dass die Destroy-Phase genau beim Schließen des Kontextes ausgeführt wurde.

Wir brauchen einen minimalen Anwendungsstart, der den ApplicationContext startet, ein Szenario ausführt und den Kontext korrekt schließt. Dazu erstellen wir die Konfiguration und schreiben eine einfache main()-Methode.

Anwendungskonfiguration

Hier reicht die normale Paketscanning: ReportingConfig liegt innerhalb von com.example.contextflow und wird zusammen mit den anderen Komponenten als Konfigurationsklasse gefunden.

package com.example.contextflow.config.core;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example.contextflow")
public class AppConfig {
}

ScenarioRunner

package com.example.contextflow.application.scenario;

import com.example.contextflow.application.reporting.ReportingService;
import com.example.contextflow.application.service.NotificationDispatchService;
import org.springframework.stereotype.Component;

@Component
public class ScenarioRunner {

    private final NotificationDispatchService notifications;
    private final ReportingService reporting;

    public ScenarioRunner(NotificationDispatchService notifications,
                          ReportingService reporting) {
        this.notifications = notifications;
        this.reporting = reporting;
    }

    public void run() {
        notifications.notifyOrderCreated("A-100");
        reporting.writeDailyReport("orders today: 1\n");
    }
}

main() und Schließen des Kontextes

package com.example.contextflow;

import com.example.contextflow.application.scenario.ScenarioRunner;
import com.example.contextflow.config.core.AppConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ContextFlowApp {
    public static void main(String[] args) {
        // try-with-resources garantiert das Schließen des Kontextes und die Ausführung der Destroy-Phase
        try (var ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
            ctx.getBean(ScenarioRunner.class).run();
        }
    }
}

Ja, try-with-resources ist hier nicht „nur für die Optik“. Es ist Ihre garantierte Methode, die Destroy-Phase in Aktion zu sehen. Wenn Sie vergessen, den Kontext zu schließen, werden @PreDestroy und destroyMethod möglicherweise nicht ausgeführt, und Sie suchen das Problem an der falschen Stelle.

5. Reihenfolge von init/destroy

In Worten ist es leicht zu sagen „init wird vor dem ready-Zustand aufgerufen“. Aber das Gehirn glaubt den Augen mehr, besonders wenn Sie gerade erst anfangen. Deshalb haben wir bewusst System.out.println() in init/destroy eingefügt, um die Reihenfolge zu sehen: was beim Start ausgeführt wird, was während des Szenarios passiert und was beim Shutdown geschlossen wird. Diese Zeilen sind nicht als Ersatz für ein normales Logging gedacht, sondern um mit den Augen die Grenze zu sehen: init findet vor dem Szenario statt, destroy nach dem Schließen des Kontextes.

Bei einem normalen Start kann man etwa folgende Sequenz sehen:

templates loaded: 2
report output ready: build/reports
... (dann das Szenario)
report output closed
templates cleared

Bei unabhängigen Singleton-Beans kann der Container seine eigene Reihenfolge der Erstellung und Schließung wählen, daher ist hier nicht die genaue erste Zeile wichtig, sondern die Grenze selbst: Beide Init-Phasen fanden vor run() statt, und beide Destroy-Phasen nach dem Schließen des Kontextes.

Um dies noch deutlicher zu machen, hier ein Schema der Sequenz in Lifecycle-Begriffen. Es zeigt nur den Teil des Graphen, der für das aktuelle Szenario benötigt wird:

sequenceDiagram
    participant C as ApplicationContext
    participant T as NotificationTemplateCatalog
    participant O as ReportOutputManager
    participant S as ScenarioRunner

    C->>T: "create bean"
    C->>T: "@PostConstruct loadDefaults()"
    C->>O: "create bean"
    C->>O: "initMethod prepare()"

    C->>S: "getBean + run()"
    S->>T: "templateFor(...)"
    S->>O: "writeReport(...)"

    C->>O: "destroyMethod close()"
    C->>T: "@PreDestroy clear()"

Hier ist eine psychologische Falle zu beachten: Anfänger denken oft, dass @PostConstruct aufgerufen wird, „wenn ich die Methode zum ersten Mal aufrufe“. Nein. Bei einem eager Singleton ist es Teil des Kontextstarts. Daher wird der Katalog noch vor dem Szenario geladen, und der Output Manager bereitet das Verzeichnis vor der ersten Berichtsschreibung vor.

6. Platzierung der Lifecycle-Logik

Nach dem heutigen Code könnten Sie ein berechtigtes Gefühl haben: „Stopp. Wir haben für den Katalog @PostConstruct/@PreDestroy verwendet, und für den Output Manager initMethod/destroyMethod. Was ist denn nun richtig?“ Die richtige Antwort ist etwas langweiliger als gewünscht: „Beide Ansätze sind in Ordnung, solange Sie bewusst wählen und nicht alles wild mischen.“ Die Wahl ist im Wesentlichen die Wahl des Ortes, an dem der Lebenszyklus des Objekts lesbar ist.

Vergleichen wir die Ansätze anhand unserer zwei Beans, ohne philosophische Kriege „Annotationen gegen XML“ (wir schreiben das Jahr 2026, lasst uns für etwas Nützlicheres kämpfen, zum Beispiel für Einrückungen im Code).

Frage @PostConstruct/@PreDestroy innerhalb der Klasse initMethod/destroyMethod in @Bean
Wo ist sichtbar, dass das Objekt einen Lifecycle hat? Direkt in der Klasse, neben den Feldern In der Konfiguration, neben dem Wiring
Müssen Jakarta-Annotationen in die Klasse gezogen werden? Ja Nein
Gut für „eigene“ Komponenten? Meistens ja Auch ja, wenn man die Klasse „sauber“ halten möchte
Gut für externe Klassen? Nein (man kann den Quellcode nicht ändern) Ja (man kann den Lifecycle von außen konfigurieren)
Risiko des „Annotationssuppe“ Ja, wenn man es übertreibt Ja, wenn die Konfiguration zur Müllkippe wird

Um ganz praktisch zu sprechen: NotificationTemplateCatalog ist eine sehr „eigenständige“ Komponente. Sie ähnelt einem Mini-Vorlagen-Repository, und es ist logisch, ihr init/destroy direkt in der Klasse zu lesen. ReportOutputManager hingegen ist ein infrastrukturelles Ding, bei dem es praktisch ist, eine reine Java-Klasse zu behalten und den Lifecycle in der Konfiguration zu beschreiben.

Die Hauptsache ist, keine Hybride zu erzeugen wie „sowohl @PostConstruct, als auch initMethod, und zusätzlich noch InitializingBean für alle Fälle“. Spring überlebt das zwar, der Leser des Codes aber nicht.

7. Typische Fehler im Lifecycle

In diesem Thema sind Fehler besonders tückisch: Die Anwendung startet „irgendwie“, verhält sich aber seltsam, und Sie werden lange in der Business-Logik nach dem Problem suchen, obwohl es im Lifecycle liegt. Daher sind die folgenden keine „Schauergeschichten“, sondern praktische Fallstricke, auf die man bei der Einführung von init/destroy in einem echten Projekt am häufigsten tritt.

Fehler Nr. 1: Schwere Arbeit im Konstruktor. Der Konstruktor ist die Phase der Objekterstellung, nicht die Phase der Arbeitsvorbereitung. Wenn Sie im Konstruktor Dateien lesen, Verzeichnisse erstellen und Writer öffnen, erschweren Sie Tests und Diagnose: Das Objekt kann „abstürzen“, bevor der Container den Abhängigkeitsgraphen richtig aufgelöst hat. Halten Sie den Konstruktor leicht und die schwere Arbeit in der init-Phase.

Fehler Nr. 2: Erwartung, dass @PostConstruct bei einem mit new erstellten Objekt funktioniert. Lifecycle ist ein Dienst des Containers. Wenn Sie NotificationTemplateCatalog irgendwo im Code mit new NotificationTemplateCatalog() erstellen, wird @PostConstruct nicht aufgerufen, und templates bleibt leer. Das ist kein Bug von Spring – Sie sind in diesem Moment aus der Containerwelt in Plain Java ausgestiegen. Wenn Sie Lifecycle möchten, muss das Objekt ein Bean sein.

Fehler Nr. 3: Fehlende Abhängigkeit jakarta.annotation-api bei Verwendung von @PostConstruct/@PreDestroy. @PostConstruct und @PreDestroy sind keine „eingebauten Java-Annotationen“. Sie gehören zur Jakarta API. Im Übungs-Baseline wird die Abhängigkeit explizit hinzugefügt, und das ist normal. Manchmal versuchen Studenten, das Problem zu „beheben“, indem sie auf InitializingBean umsteigen, nur weil es kompiliert. Das ist eine funktionierende Krücke, aber meistens ist es richtiger, die benötigte Abhängigkeit einzubinden und einen neutraleren Ansatz zu verwenden.

Fehler Nr. 4: Kontext wird nicht geschlossen, aber Destroy-Phase wird trotzdem erwartet. Wenn Sie den Kontext als gewöhnliches Objekt erstellen und nicht schließen, können die Destroy-Callbacks ausbleiben. Der zuverlässigste Stil für eine Konsolenanwendung ist try-with-resources um AnnotationConfigApplicationContext. Dann sehen Sie garantiert destroyMethod und @PreDestroy, und wissen, dass Ihr Lifecycle wirklich funktioniert.

Fehler Nr. 5: Umwandlung der Destroy-Methode in Geschäftslogik. Destroy ist nicht der Ort, um „in letzter Minute Berichte neu zu berechnen“ oder „Benachrichtigungen nachzusenden“. Es ist die Phase der Ressourcenfreigabe. Wenn Sie in der Destroy-Phase Geschäftsoperationen starten, erhalten Sie instabiles Verhalten: Manchmal wird die Anwendung korrekt geschlossen, manchmal nicht, und manchmal brechen Sie das Herunterfahren aufgrund einer Exception im „letzten Business-Szenario“.

1
Aufgabe
Spring Core, Level 10, Lektion 4
Gesperrt
Lifecycle-verwalteter Vorlagenkatalog
Lifecycle-verwalteter Vorlagenkatalog
1
Aufgabe
Spring Core, Level 10, Lektion 4
Gesperrt
Lebenszyklus-verwalteter Berichtsmanager
Lebenszyklus-verwalteter Berichtsmanager
1
Umfrage/Quiz
Lebenszyklus, Level 10, Lektion 4
Nicht verfügbar
Lebenszyklus
Erstellung und Abschluss von Beans
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION