Neue Features des Spring-Frameworks

Spring 3.1 – Cache Abstraction

Tobias Flohre und Pascal Czollmann

Spring 3.1 steht vor der Tür – und mit ihm eine Reihe neuer, interessanter Features. Nachdem wir vor kurzem Environment Abstraction vorgestellt hatten, die mit ihren Bean Definition Profiles umgebungsspezifische Bean-Definitionen ermöglicht, geht es heute um die Cache Abstraction, die ein allgemeines API für Cache-Funktionalitäten mit Unterstützung verschiedener Implementierungen anbietet.

Der Einsatz eines Caches lohnt vor allem dann, wenn Daten häufig angefragt werden, sich jedoch selten ändern. Wiederholte Anfragen werden dann aus dem Cache bedient und können somit schneller beantwortet werden. Beispiele hierfür sind aufwändige Datenbankabfragen, Anfragen an langsame Drittsysteme, zeitintensive Berechnungen etc.

Im Java-Umfeld gibt es verschiedene Cache-Implementierungen, z. B. EhCache, JBoss Cache und dessen Nachfolger Infinispan. Der Einsatz von Caches ist bislang codeinvasiv, d. h. die Anbindung des Caches erfolgt programmatisch durch Nutzung des jeweiligen Cache-API. Die daraus folgende Vermischung von Business- und Infrastrukturcode resultiert in schlechterer Test- und Lesbarkeit des Codes.

Die neue Cache Abstraction in Spring 3.1 implementiert Caching als Aspekt. An welchen Stellen Caching zum Einsatz kommen soll, wird auf Methodenebene deklarativ mittels Annotations bestimmt, analog zu deklarativem Transaktionsmanagement. Spring fängt die Methodenaufrufe mittels AOP ab und fügt das Caching-Verhalten hinzu. Für den Businesscode ist das völlig transparent – weder der aufrufende Code einer Methode noch die Methode selbst enthalten Cache-spezifischen Code.

@Cacheable

Das Caching wird mittels der Annotation @Cacheable an Methoden deklariert (Listing 5). Die Annotation markiert, dass der Rückgabewert der Methode gecached werden soll und kann daher logischerweise nicht an void-Methoden eingesetzt werden. Der Schlüssel, unter dem der Wert im Cache abgelegt wird, wird per Default aus den hashCode()-Werten der Parameter der Methode gebildet. Bei einem erneuten Aufruf prüft die Cache Abstraction, ob es zu den Parameterwerten bereits einen Eintrag im Cache gibt. Falls ja, wird direkt der Wert aus dem Cache zurückgegeben, ohne dass die Methode erneut aufgerufen wird.

@Cacheable("books")
public Book findBook(ISBN isbn) { ... }

@Cacheable({"media", "dvds"})
public DVD findDVD(String title) { ... }

Das Beispiel in Listing 5 zeigt die Anwendung der Annotation. In dem gezeigten Fall werden die Rückgabewerte der Methode findBook in dem Cache books abgelegt – in einem Cache liegen in der Regel gleichartige Daten. Es ist auch möglich, mehrere Caches anzugeben. Sie werden dann der Reihe nach geprüft, und sobald ein Cache einen Treffer liefert, wird dieser zurückgegeben. Aktualisierungen werden hierbei in allen angegebenen Caches durchgeführt.

Das Ergebnis der hashCode()-Methode für zwei unterschiedliche Objekte muss laut der JDK-Dokumentation aber nicht unbedingt unterschiedliche Werte ergeben. In einem solchen Fall ist der per Default aus den hashCode()-Werten der Parameter gebildete Schlüssel unbrauchbar, da es dann zu Kollisionen der Schlüssel kommt, d. h. es würde für unterschiedliche Parameter der gleiche Schlüssel erzeugt. Aus diesem Grund bietet die Annotation @Cacheable das Attribut key, mit dem sich die Schlüsselerzeugung steuern lässt (Listing 6). Es erlaubt die Verwendung von Ausdrücken der Spring Expression Language (SpEL) und ist somit sehr flexibel einsetzbar. Beispielsweise können bestimmte Methodenparameter anhand ihres Namens oder per Index – z. B. #p0 für den ersten Parameter – für die Schlüsselerzeugung ausgewählt werden. Letzteres ist sinnvoll, da die Parameternamen nur genutzt werden können, wenn entsprechende Debug-Informationen in den kompilierten Klassen vorliegen. Abweichend von der Referenzdokumentation muss den Parametern ein #-Zeichen vorangestellt werden. Mittels Punktnotation ist es möglich, auf Properties der ausgewählten Parameter zuzugreifen. Komplexere Schlüsselberechnungen, auch über mehrere Parameter, lassen sich in eine statische Methode auslagern, die dann mittels SpEL in der@Cacheable-Annotation eingebunden werden kann.

@Cacheable(value="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWareHouse) { ... }

// Schlüssel wird mittels statischer Methode aus beiden Parametern gebildet
@Cacheable(value={"media", "dvds"},
key="T(my.media.KeyGen).createKey(#p0, #p1)")
public DVD findDVD(String title, int year) { ... }

Ein weiteres nützliches Feature ist das Conditional Caching. Dabei wird nur dann das Caching aktiviert, falls eine definierte Bedingung erfüllt ist, die mittels SpEL im Attribut condition der Annotation angegeben werden kann (Listing 7).

@Cacheable(value="books", condition="#name.length < 32")
public Book findBook(String name) { ... }

[ header = Seite 2: @CacheEvict ]

@CacheEvict

Die zweite Annotation der Cache Abstraction ist @CacheEvict. Sie wird an Methoden eingesetzt, die dazu führen, dass Informationen im Cache nicht mehr aktuell sind und daher aus dem Cache entfernt (evicted) werden müssen. Die Annotation bietet die gleichen Attribute wie @Cacheable, und darüber hinaus lässt sich mittels allEntries=true angeben, dass alle Daten aus dem spezifizierten Cache verworfen werden sollen, nicht nur der zu dem Schlüssel passende Eintrag. Im Gegensatz zu @Cacheable kann @CacheEvict auch an void-Methoden eingesetzt werden, und im Fall allEntries=true benötigt die Methode keine Parameter.

@CacheEvict(value="books", key="#p0.rawNumber")
public void deleteBook(ISBN isbn, Book book) { ... }

@CacheEvict(value="books", allEntries=true)
public void dropAndReloadAllBooks() { ... }

Konfiguration

Spring bietet derzeit Unterstützung zur Einbindung von zwei Cache-Implementierungen: Den Concurrent Cache, basierend auf der JDK Klasse ConcurrentHashMap, sowie die Open Source Library Ehcache. Concurrent Cache lässt sich einfach einbinden, erfordert keinen zusätzlichen Konfigurationsaufwand und ist dank ConcurrentHashMap auch in Multithreading-Umgebungen sehr performant. Andererseits werden wichtige Enterprise-Anforderungen nicht abgedeckt, beispielsweise Verteilung und Transaktionssicherheit. Daher bietet sich Concurrent Cache vor allem für einfache Anwendungsfälle wie Standalone-Anwendungen oder zur lokalen Entwicklung und zum Unit-Testing an. Ehcache deckt hingegen Anforderungen ab, die im produktiven Java-Enterprise-Umfeld wichtig sind, wie verteiltes Caching, Transaktionssicherheit, Persistenz, Eviction Policies uvm.

Die Konfiguration der Cache Abstraction erfolgt in der applicationContext.xml unter Verwendung des neuen XML Namespace http://www.springframework.org/schema/cache. Das Element<cache:annotation-driven/> aktiviert die Auswertung der Caching-Annotationen. Über Attribute kann hierbei festgelegt werden, ob Spring AOP oder AspectJ zum Einsatz kommt und ob im Fall Spring AOP die Proxies auf Interface- oder Klassenebene erzeugt werden. Die Standardeinstellung ist Spring AOP und die Nutzung Interface-basierter Proxies.

Die Einbindung eines Caches erfolgt nun durch Konfiguration einer CacheManager-Instanz. Dieses Spring-Interface abstrahiert von konkreten Cache-Implementierungen und bietet Zugriff auf die konfigurierten Caches. <cache:annotation-driven/> erwartet den CacheManager per Default unter der Bean-ID cacheManager.

Die Concurrent-Cache-Implementierung wird über die Klasse SimpleCacheManager eingebunden. Sie dient lediglich als Container für ein oder mehrere Caches, die in der Property caches definiert werden. Instanzen des Concurrent Cache werden mittels der Factory Bean ConcurrentCacheFactoryBean konfiguriert. Der Name des Caches wird über das Property name der Factory Bean gesetzt. Unsere Konfiguration sieht nun aus wie in Listing 9.

<beans ...
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="...
http: //www.springframework.org/schema/cache
http: //www.springframework.org/schema/cache/spring-cache-3.1.xsd">

<cache:annotation-driven/>

<bean id="cacheManager" class=
"org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class=
"org.springframework.cache.concurrent.ConcurrentCacheFactoryBean">
<property name="name" value="books"/>
</bean>
<bean class=
"org.springframework.cache.concurrent.ConcurrentCacheFactoryBean">
<property name="name" value="media"/>
</bean>
</set>
</property>
</bean>

Der Einsatz von Ehcache erfordert nur wenige Zeilen in der applicationContext.xml (Listing 10). Als Erstes erstellen wir die Ehcache-Instanz unter Verwendung der Spring Factory-BeanEhCacheManagerFactoryBean, die einen Ehcache-Manager erzeugt. Die Ehcache-Konfigurationsdatei wird hierbei über die Property configLocation der Factory Bean gesetzt. Die in den vorherigen Beispielen verwendeten Caches media und books werden nun in der Konfigurationsdatei von Ehcache definiert. Anstelle des SimpleCacheManager aus Listing 9 verwenden wir die Spring-KlasseEhCacheCacheManager und geben ihr in der Property cacheManager die Referenz auf den zuvor konfigurierten Ehcache-Manager mit.

<bean id="cacheManager" class=
"org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcache"/>
</bean>

<bean id="ehcache" class=
"org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="ehcache.xml"/>
</bean>

Fazit

Mit der Cache Abstraction führt Spring 3.1 eine wichtige Abstraktion für Enterprise-Java-Anwendungen ein. Caches haben eine hohe Relevanz für Clustering und Failover und können – richtig eingesetzt – den Datendurchsatz vieler Anwendung massiv steigern. Analog zu deklarativem Transaktionsmanagement etabliert die Cache Abstraction deklaratives Caching mittels Annotationen und ermöglicht damit eine effektive Trennung von Infrastruktur- und Businesscode. Die Cache Abstraction ist so gestaltet, dass Adapter für weitere Caches mit überschaubarem Aufwand entwickelt werden können. Derzeit werden nur zwei Cache-Implementierungen angeboten, doch dabei wird es in Zukunft nicht bleiben. Im Rahmen des Spring-GemFire-Projekts ist für das nächste Major-Release 1.1 bereits die Integration des SpringSource GemFire Caches geplant.

Tobias Flohre arbeitet als Senior Software Engineer beim IT-Dienstleistungs- und Beratungsunternehmen adesso AG. Seine Schwerpunkte sind Java-Enterprise-Anwendungen und Architekturen mit JE /Spring.
Pascal Czollmann
 ist als Senior Software Engineer bei dem IT-Dienstleistungs- und Beratungsunternehmen adesso AG beschäftigt. Er arbeitet dort seit mehreren Jahren in unterschiedlichen Kundenprojekten im Java-Enterprise-Umfeld.

Geschrieben von
Tobias Flohre und Pascal Czollmann
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.