środa, 23 lutego 2011

Caching Abstract w Spring Framework 3.1 M1

Po krótkiej przerwie wracamy do pisania ;)
Przyszło mi pójść do pracy i trochę blog się zakurzył, ale mam zamiar go systematycznie przecierać ściereczką, żeby raz na jakiś czas coś dało się przeczytać.

Całkiem niedawno SpringSource wydał milestone pierwszy nowego Springa 3.1. Przeglądając listę zmian napotkałem zupełnie nowy komponent służący do cachowania. Caching Abstract - bo tak został nazwany, jest warstwą abstrakcji (samym API) wspierającym programistę podczas cachowania pobieranych danych z dowolnych źródeł. Z techniczego punktu widzenia (na tę chwilę) zostały wprowadzone dwie nowe adnotacje: @Cacheable oraz @CacheEvict.

Za pomocją @Cacheable adnotujemy metody których zwracane wartości mają trafiać do cache:
@Cacheable("something")
public List<Object> getMeSomething(String param) {
 // ...
}
Jako parametr podajemy nazwę cache do którego mają trafić wyniki. Kluczem dla zapamiętywanych wyników staje się parametr metody. Jeśli metoda posiada kilka parametrów lub przyjmuje jako taki np jakieś DTO, można wyspecyfikować klucz za pomocą atrybutów,przykłady:
@Cacheable(value="magic", key="magicDto.id")
public List<Object> getSomethingMore(MagicDto magicDto) {
 // ..
}
Dla wielu parametrów:
@Cacheable(value="magic", key="param2")
public List&ls;Object> getSomething(String param1, int param2, boolean param3) {
 // ..
}
Możliwe jest także wykorzystanie dowolnej metody do wyliczenia klucza:
@Cacheable(value="magic", key="T(myKeyGenerator).getKey(param2)")
public List&ls;Object> getSomething(String param1, int param2, boolean param3) {
 // ..
}
Domyślnie Spring korzysta z metody hashCode dla wygenerowania klucza z podanej wartości. Można to zmienić podmieniając implementację klasy org.springframework.cache.KeyGenerator (więcej w dokumentacji).

Za pomocą atrybutu condition można zdefiniować warunek, kiedy wyniki mają zostać cachowane:
@Cacheable(value="something", condition="param.length > 1024")
public List<Object> getSomething(String param) {
 return Collections.emptyList();
}
Jako wartość można wstawić dla niego dowolne wyrażenie w SpEL które zwróci wartość boolowską.

Jak już uzbieramy trochę obiektów to możemy się ich pozbyć za pomocą @CacheEvict.
@CacheEvict(value="magicCache", allEntries=true)
public void clearMyCache(int param1) {
        //...
}
Jako value podajemy cache (lub ich wiele) z którego mają zostać usunięte obiekty. Parametr allEntries jest opcjonalny i służy do usuwania wszystkich przechowywanych obiektów. Jeśli nie zostanie użyty obiekty będą usuwane wg klucza, zasady podobne jak w przypadku @Cacheable.

Tyle szybko jeśli chodzi o API. Aby zacząć go używać trzeba jeszcze zrobić mały update konfiguracji o następującą treść:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

   <cache:annotation-driven />

</beans>
Domyślnie w Springa wbudowana jest obsługa dwóch "fizycznych" cache: ConcurrentMap oraz ehcache. Jęsli ktoś używa/potrzebuje czegoś innego musi jak na razie pokusić się o napisanie własnej implementacji. 
Aby skorzystać z ehcache we własnej aplikacji trzeba dopisać jeszcze dwie linie:
<!-- konfiguracja springowego CacheManagera -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhcacheCacheManager" p:cache-manager="ehcache"/>
<!-- konfiguracja ehcache -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>

Fajnie, że nareszcie tego typu komponent trafił do Springa. Możliwe będzie cachowanie dowolnych wartości niezależnie czy pobieramy jest przez WebService, bazę danych, z pliku czy czegokolwiek. Posługując się tylko API jednej biblioteki! Nie powinno być także żadnych kolizji jeśli chodzi o cache drugiego poziomu JPA. W tej wersji brakuje mi jeszcze jakiejkolwiek konfiguracji. Nie można (przynajmniej nie znalazłem nigdzie w dokumentacji ani w API) zdefiniować czasu życia dla obiektów w cache co jest kwestią dość podstawową. Ale poczekam jeszcze na wersję finalną....