Automatisches Aufräumen von Ressourcen in Spring

16.11.2018

Eine der vielen Funktionen des Spring-Frameworks ist die Fähigkeit, Ressourcen beim Herunterfahren der Anwendung automatisch aufräumen zu lassen. Dieser Artikel verschafft einen Überblick über die Möglichkeiten die es gibt, und wie man diese nutzt.

Der Lebenszyklus von Spring-Beans

Die Verwaltung aller Objektinstanzen einer Spring-Anwendung übernimmt der sogenanten IoC-Container. Im Spring-Jargon werden die vom Container verwalteten Anwendungsobjekte „Beans“ genannt. Der Container stellt einen Lebenszyklus bereit, über den die Beans instanziiert, initialisiert und auch wieder „zerstört“ werden. Der Entwickler liefert hierfür lediglich eine Konfiguration, um alles weitere kümmert sich der Container.

Beispiel DbRepository

Angenommen wir haben eine Spring-Anwendung, die ein DbRepository für Datenbankabfragen nutzt. Fährt die Anwendung herunter sollte sie sicherstellen, dass das Repository hinter sich aufräumt und beispielsweise die Connections im Connection Pool schließt. Zu diesem Zweck hat das DbRepository eine Methode, die z.B. close() oder destroy() heißt. Nun gibt es mehrere Möglichkeiten dafür zu sorgen, dass der Spring-Container diese Methode beim Beenden der Anwendung aufruft:

Via XML

Seit jeher unterstützt Spring die Bean-Konfiguration in XML. Hier gibt man im XML-Attribut destroy-method den Namen der Methode an, die das Aufräumen übernimmt:

<bean id="dbRepository" class="de.doubleslash.blog.spring.DbRepository" destroy-method="cleanUp" />

Diese Konfiguration bewirkt, dass beim Herunterfahren der Anwendung die Methode cleanUp() der DbRepository-Instanz aufgerufen wird.

Via Interface

Stattdessen ist es auch möglich, die Klasse ein entsprechendes Interface implementieren zu lassen. Hier gibt es zwei Möglichkeiten:

Interface DisposableBean

Dies ist ein Spring-spezifisches Interface, das nur eine Methode namens void destroy() besitzt. Die Definition unserer DbRepository-Klasse sähe damit so aus:

public class DbRepository implements DisposableBean {

    @Override
    public void destroy() {
        // hier wird aufgeräumt
    }
    // ...
}

Weitere Konfigurationen sind nicht notwendig. Der Spring-Container erkennt automatisch das implementierte Interface, und ruft die destroy()-Methode beim Herunterfahren auf.

Interface (Auto)Closeable

Denselben Effekt hat es, wenn die Bean-Klasse das Interface Closeable oder AutoCloseable implementiert. Beiden Interfaces gemein ist die Methode void close(), die sich nur im throws-Statement ihrer Signatur unterscheidet. Auch hier erkennt der Spring-Container das implementierte Interface, und ruft entsprechend beim Shutdown die close()-Methode auf.

Hier unser DbRepository nochmal mit implementiertem AutoCloseable-Interface:

public class DbRepository implements AutoCloseable {

    @Override
    public void close() {
        // hier wird aufgeräumt
    }
    // ...
}

Diese beiden Interfaces sind nicht von Spring abhängig, da sie zur Java-Standardbibliothek gehören. Implementierungen des AutoCloseable-Interfaces können zudem im try-with-resources-Statement von Java verwendet werden.

Via Annotation @PreDestroy

Diese Variante benötigt weder XML-Konfiguration noch Interface-Implementierung. Stattdessen wird die Aufräum-Methode einfach mit der Annotation @PreDestroy markiert:

import javax.annotation.PreDestroy;

public class DbRepository {

    @PreDestroy
    public void cleanUp_1() {
        // hier wird aufgeräumt
    }

    @PreDestroy
    public void cleanUp_2() {
        // hier wird weiter aufgeräumt
    }
    // ...
}

Wie das Beispiel zeigt können auch mehrere Methoden annotiert werden. In diesem Fall werden alle mit @PreDestroy versehenen Methoden aufgerufen. Eine spezielle Ausführungsreihenfolge wird nicht garantiert.

Anmerkung: @PreDestroy stammt aus der JEE/Jakarta EE-Spezifikation, wird von Spring aber ebenfalls unterstützt.

Via @Bean-Konfiguration

Alle zuvor beschriebenen Varianten funktionieren sowohl in Objekten von @Component-Klassen, als auch in Instanzen die mittels einer @Bean-annotierten Factorymethode erzeugt werden.

Im folgenden werden die Möglichkeiten beschrieben, die sich auf die Konfiguration mit @Bean beschränken.

Angabe der Methode in der Annotation

In der DbRepository-Klasse ist in diesem Fall nichts zu tun. Stattdessen erfolgt die Konfiguration innerhalb einer Konfigurationsklasse, die mit @Configuration annotiert ist:

@Configuration
public class AppConfig {

    @Bean(destroyMethod = "closeConnectionPool")
    DbRepository dbRepository() {
        return new DbRepository();
    }
}

Im Attribut destroyMethod der @Bean-Annotation wird der Name der Aufräum-Methode angegeben, im obigen Beispiel ist dies die Methode closeConnectionPool().

Automatische Ermittlung der Methode

Das destroyMethod-Attribut in @Beans ist nicht zwingend erforderlich, man kann es daher auch weglassen:

    @Bean
    DbRepository dbRepository() {
        return new DbRepository();
    }

In diesem Fall versucht Spring, die Aufräum-Methode automatisch zu ermitteln. Existiert in DbRepository eine parameterlose Methode mit Namen close() oder shutdown() die public ist, wird diese automatisch als Aufräum-Methode verwendet.

Existiert eine solche Methode, die allerdings nicht automatisch von Spring aufgerufen werden soll, kann dies wie folgt per Leerstring konfiguriert werden:

    @Bean(destroyMethod = "")
    DbRepository dbRepository() {
        return new DbRepository();
    }

Hinweis: Hiermit wird nur das automatische Aufrufen der shutdown()- bzw. close()-Methode unterbunden, unabhängig von einer Implementierung des (Auto)Closeable-Interfaces. Implementiert die Klasse das DisposableBean-Interface, wird die destroy()-Methode dennoch ausgeführt.

Die Qual der Wahl

Für welche der Möglichkeiten sollte man sich nun entscheiden? Mitunter ist das sicherlich Geschmackssache, bzw. abhängig von den Vorgaben im Projekt.

Auf jeden Fall rate ich davon ab, den Methodennamen String-basiert zu konfigurieren, wie es in der XML-Konfiguration oder im destroyMethod-Attribut von @Bean der Fall ist. Denn das ist weder typsicher noch Refactoring-resistent. Wird die Methode umbenannt und in der Konfiguration nicht nachgezogen, kommt es zu einem Laufzeitfehler.

Auch die automatische Ermittlung über @Bean ohne Angabe eines Methodennamens würde ich persönlich nicht einsetzen. Denn wird die close()– oder shutdown()-Methode später umbenannt, wird keine Aufräum-Methode mehr aufgerufen, möglicherweise ohne dass es auffällt, was ggf. ein Ressourcen-Leck zur Folge haben kann. Dasselbe passiert wenn die Klasse nicht mehr über eine @Bean-Methode instanziert wird sondern als @Component über den Komponenten-Scan. Auch in diesem Fall wird die close()– bzw. shutdown()-Methode nach dem Refactoring nicht mehr aufgerufen.

Diese Probleme hat man in der Interface-Variante nicht. Ein Umbenennen der Methode würde sowohl bei DisposableBean als auch bei (Auto)Closeable sofort durch einen Kompilierfehler auffallen, da die Klasse das Interface dann nicht mehr implementiert. Zudem wird die Aufräum-Methode bei Implementierung von einem der Interfaces in jedem Fall ausgeführt. Muss ich zwischen den Interfaces wählen, wird meine Wahl auf AutoCloseable fallen. Dadurch ist meine Klasse weniger vom Spring-Framework abhängig, zudem kann sie außerhalb des Spring-Kontexts im try-with-resources-Statement verwendet werden. Nachteil dieser Variante ist die recht versteckte Dokumentation dieses Features im Javadoc von DisposableBean.

Die Nutzung von DisposableBean macht die Klasse zwar abhängig vom Spring-Framework, ist dafür aber explizieter und förderlicher für das Verstehen des Codes. Ähnliches gilt für die Nutzung der @PreDestroy-Annotation (wobei diese nicht Spring-spezifisch ist sondern aus der JEE/Jakarta EE Spezifikation stammt und auch in einer CDI-Anwendung genutzt werden kann). Hier gilt es im Einzelfall abzuwägen, ob man dafür eine hohe Kopplung des Anwendungscodes an ein Framework in Kauf nehmen will, oder nicht.

Zurück zur Übersicht

2 Kommentare zu “Automatisches Aufräumen von Ressourcen in Spring

  1. Hallo Jakob (ich hoffe, das „Du“ unter Entwicklern ist für dich ok),

    vielen Dank für deine Anmerkung. Ich habe den Artikel dahingehend angepasst.

    Viele Grüße
    Stefan

  2. Super Artikel, schön erklärt.
    Einzige Anmerkung: @PreDestroy ist keine spezielle Spring-Annotation sondern wird von CDI-Frameworks aus dem JEE-Umfeld weithin unterstützt.

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*