piątek, 1 lipca 2011

Poznań JUG - Warsztaty z języka Scala

Dzięki uprzejmości Grzegorza Balcerka, który zgłosił pomysł oraz siebie jako ochotnika do poprowadzenia warsztatów, mam przyjemność zaprosić wszystkich na otwarte warsztaty z języka Scala! 
Scala ostatnio robi się dość popularna, więc warto przyjść i "liznąć" trochę z tego języka. Bez przedłużania dane szczegółowe:

Data: 09.07.2011 (sobota)
Godzina: 10:00
Miejsce: Siedziba Cognifide (uwaga zmiana adresu!)  ul. Murawa 12-18
Rejestracja: tutaj (wymagana, liczba miejsc ograniczona!)
Wymagane jest posiadanie własnego laptopa (prosimy zabrać listwy zasilające;) z JDK 6.

Opis warsztatów nadesłany od autora:
Zakres: od podstaw do .... - nie wiem, zależy na ile czas 
pozwoli.Poniżej kolejne tematy które mogłyby być omawiane, ale nie 
wydaje mi się żeby było możliwe omówienie wszystkiego - ze względów 
czasowych: wprowadzenie, podstawy składni, podstawowe typy danych, 
wyrażenia sterujące, klasy, cechy, metody, obiekty, typy ogólne, 
standardowe typy ogólne, funkcje, dopasowywanie wzorców, klasy i 
obiekty przypadków, pakiety, klauzule importu, uprawnienia, wyjątki i 
wyrażenie return, modyfikator implicit, kolekcje, pętla for, XML, 
programowanie interfejsów graficznych, programowanie współbieżne, 
parsowanie, adnotacje, języki dostosowane do dziedziny, kompilator
Przewidziana jest przerwa obiadowa z pizzą i napojami :-)


Zapraszam!

piątek, 24 czerwca 2011

(Prawdopodobnie ostatnie) Podsumowanie nowości w Java 7

Wpisów na temat rzeczy, które mają się pojawić w Java 7 już kilka u mnie powstało (3). Ale ten chyba będzie ostatnim, bo termin wydania jest już znany i bliski - 28 lipca 2011! Z tej okazji postanowiłem przejrzeć ten brzydki dokument JSR 336.

Żeby nie zanudzać i nie rozwodzić się za bardzo (większość rzeczy już jak wspomniałem opisałem), lista zwycięzców tej długiej bitwy:

Bytecode
Lepsze wsparcie dla języków dynamicznych opartych na JVM - dodanie instrukcji invokedynamic.

Java Beans
Tutaj głównie poprawki w API. Dodano adnotację @Transient i oznaczono nią trochę getterów, poza tym poprawki w API ;-)

Kolekcje
Tutaj podobnie dodatki w API. Głównie chwalą się dodaniem metod Collections.emptyIterator() oraz Collections.emptyEnumeration() oraz zaktualizowaniem dokumentacji.

I/O
Nowe IO! NIO.2 opisane w JSR 203 :-) Wsparcie dla POSIX i ACL, nowy pakiet java.nio.file zawierający stuff do obsługi metadanych, dowiązań, ułatwienia przeglądania katalogów (interface FileVisitor)  i jeszcze trochę tego, ale za bardzo się nie zagłębiałem akurat w ten temat.

java.lang
Tutaj tzw. project coin (JSR 334) z zestawem kilku zmian do samego języka: switch po stringu, blok try dla zasobów wymagających zamykania, multi-catch, rozszerzona składnia dla liczb, dedukcja typów (dla generyków). Poza tym wiadomo dodatki/usprawnienia w API (w tym już chyba sławny java.util.Objects z metodami do porównywania obiektów, obliczania hashcode itp.).

Class loader
Wsparcie dla współbieżnych class loaderów! :-)

Współbieżność
Dodano nowy framework ForkJoin przeznaczony dla aplikacji z intensywnymi obliczeniami. Posiada dobre wsparcie dla maszyn wieloprocesorowych i jest niezależny od platformy. + dodatki w standardowym API.

I18N
Wsparcie dla Unicode 6.0.0


To były główne zmiany, poza tym różnorakie zmiany w: java.util.loggingjava.util.regex, java.net, JMX, java.util.zip, bezpieczeństwie (obsługa TLS 1.2) oraz dodatki do Swinga i AWT.

Jak widać ostatecznie domknięcia nie zmieściły się w wersji finalnej. To samo tyczy się projektu Jigsaw czyli modularyzacji JDK (co mnie bardzo zdziwiło, bo myślałem, że to pewniak). Jestem z tego powodu bardzo zawiedziony, bo to dla mnie były właśnie te killing-features, które wprowadziłyby nowy blask językowi.

No cóż... z nadzieją musimy obserwować rozwój specyfikacji Javy 8... :-/

niedziela, 19 czerwca 2011

Pierwszy Job z Hadoop

Zanim napiszemy pierwszego joba trzeba przygotować sobie środowisko. Obsługa wszystkiego z konsoli może z czasem być nużąca (chociaż ja tam lubię;).

Ja do dewelopmentu używam NetBeansa i polecam wtyczkę Karmaspehere Community Edition. Pozwala ona w łatwy sposób definiować i odpalać sobie zadania Hadoopa.
Aby zainstalować tę wtyczkę należy dodać do listy 'update sites' adres: http://hadoopstudio.org/updates/updates.xml. Ja zainstalowałem wszystkie 4 dostępne pluginy od Karmasphere. Polecam też się zarejestrować i aktywować plugin, bo będą się pojawiać komunikaty o aktywacji (ale chyba można pracować bez niej).

Ok, możemy zacząć pisać :-) Projekt oparłem o maven, pom.xml nie jest zbyt rozdbudowany:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>pl.lstachowiak</groupId>
    <artifactId>HadoopSamples</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>HadoopSamples</name>
    <url>http://blog.lstachowiak.pl</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.apache.mahout.hadoop</groupId>
            <artifactId>hadoop-core</artifactId>
            <version>0.20.1</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
        </dependency>
    </dependencies>
</project>

Problem jaki wybrałem dla pierwszego przykładu to analiza statystyk serwera Apache. Bierzemy plik access.log i analizujemy typy żądań do serwera. Na końcu chcemy mieć liczbę żądań każdego typu.
Mój pomysł na Map i Reduce jest następujący:
Czyli:
- Hadoop wczytuje i dzieli plik na tzw 'input splits'
- przekazuje każdy split do funkcji map, gdzie kluczem jest numer linii w pliku a wartością sama linia
- implementowana przez nas funkcja map sprawdza jaki tym żądania HTTP znajduje się w linii i zwraca jako klucz typ żądania a jako wartość liczbę 1 (umownie sobie tak robimy)
- Hadoop wykonuje tzw. shuffle grupując nam wyniki z map w pary [TYP] -> [KOLEKCJA JEDYNEK]
- nasza funkcja reduce jedyne co robi to zlicza liczbę elementów w kolekcji i zwraca jako klucz typ żądania a jako wartość liczbę elementów w kolekcji co jest zarazem wynikiem końcowym
- Hadoop zapisuje dane na dysk

(Bardzo) Możliwe, że nie jest to idealne rozwiązanie, ale nie mam jeszcze wielkiego doświadczenia z Hadoop ;)

Przejdźmy do implementacji. Funkcja map:
HttpMethodMapper.java:
package pl.lstachowiak.hadoop.sample1;

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;


public class HttpMethodMapper extends Mapper<LongWritable, Text, Text, IntWritable> { // 1

    @Override
    public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        HttpMethod method = getMethodFromLine(value); // 2
        context.write(new Text(method.name()), new IntWritable(1)); // 3
    }

    HttpMethod getMethodFromLine(Text value) {

        if (value.find(HttpMethod.CONNECT.name()) != -1) {
            return HttpMethod.CONNECT;
        }

        if (value.find(HttpMethod.DELETE.name()) != -1) {
            return HttpMethod.DELETE;
        }

        if (value.find(HttpMethod.GET.name()) != -1) {
            return HttpMethod.GET;
        }

        if (value.find(HttpMethod.HEAD.name()) != -1) {
            return HttpMethod.HEAD;
        }

        if (value.find(HttpMethod.OPTIONS.name()) != -1) {
            return HttpMethod.OPTIONS;
        }

        if (value.find(HttpMethod.PATH.name()) != -1) {
            return HttpMethod.PATH;
        }

        if (value.find(HttpMethod.POST.name()) != -1) {
            return HttpMethod.POST;
        }

        if (value.find(HttpMethod.PUT.name()) != -1) {
            return HttpMethod.PUT;
        }

        if (value.find(HttpMethod.TRACE.name()) != -1) {
            return HttpMethod.TRACE;
        }

        return HttpMethod.UNKNOWN;
    }
}
Krótki komentarz do tego co się dzieje w kodzie:
1 - klasa mappera musi (od wersji API 0.20) rozszerzać klasę Mapper. Parametrami są kolejno:
- typ klucza (numer linii)
- typ wartości (linia w pliku)
- typ klucza wynikowego (typ żądania http)
- typ wartości wynikowe (liczba 1)
2 - pobranie typu żądania z linii, wiem, że rozwiązanie jest dalekie od idealnego, ale nie o to chodzi w przykładzie ;)
3 - zapis wyniku

Jak widać Hadoop posiada własny zestaw typów danych. W większości są to obudowane typy podstawowe. Stworzone zostały one aby w troszkę lepszy sposób serializować dane i przesyłać przez sieć. Zaleca się ich używanie. Typ HttpRequest to zwykły enum z listą dostępnych typów żądań HTTP. Dostępny jest w źródłach.

Funkcja reduce:
HttpMethodReducer.java:
package pl.lstachowiak.hadoop.sample1;

import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Reducer.Context;


public class HttpMethodReducer extends Reducer<Text, IntWritable, Text, IntWritable> { // 1

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { // 2
        int counter = countValues(values); // 3
        context.write(key, new IntWritable(counter)); // 4
    }
    
    <T> int countValues(Iterable<T> values) {
        int counter = 0;
        for (T value : values) {
            counter++;
        }
        
        return counter;
    }
}
Ponownie krótki komentarz:
1 - musimy rozszerzać klasę Reducer, parametry kolejno to:
- typ klucza z Map
- typ wartości z Map
- typ klucza wynikowego
- typ wartości wynikowej
2 - tutaj widać efekt działania operacji shuffle. Zamiast otrzymywać tą jedynkę z funkcji Map otrzymujemy iterator po kolekcji jedynek (w naszym przykładzie).
3 - zliczamy elementy
4 - zapisujemy w wynikach :-)

No to jeszcze program do uruchomienia zadania:
HttpMethodJob.java:
package pl.lstachowiak.hadoop.sample1;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;


public class HttpMethodJob {
    
    public static void main(String[] args) throws Exception {
        
        if (args.length != 2) {
            System.err.println("Usage: HttpMethodJob <input path> <output path>");
            System.exit(-1);
        }
        
        Job job = new Job();
        job.setJarByClass(HttpMethodJob.class); // 1
        
        FileInputFormat.addInputPath(job, new Path(args[0])); // 2
        FileOutputFormat.setOutputPath(job, new Path(args[1])); // 3
        
        job.setMapperClass(HttpMethodMapper.class); // 4
        job.setReducerClass(HttpMethodReducer.class); // 5
        
        job.setOutputKeyClass(Text.class); // 6
        job.setOutputValueClass(IntWritable.class); // 7
        
        System.exit(job.waitForCompletion(true) ? 0 : 1); // gogogo!
        
    }
}

1 - tworzymy zadanie i podajemy klasę główna
2,3 - podajemy źródło danych oraz miejsce zapisu danych wynikowych (wczytywane z argumentów)
4,5 - podajemy klasę mappera i reducera
6,7 - konfiguracja typów danych wynikowych
I uruchomienie zadania :-)

Zanim jednak zaczniemy potrzebne są jeszcze jakieś dane do analizy. Ja wygooglałem coś tutaj (4,3MB).

Uruchomienie

Tutaj obrazkowo:) Przechodzimy do zakładki Services (NetBeans) -> Servers -> Hadoop Jobs -> New Job. Wybieramy nazwę dla Joba i typ 'Hadoop Job from pre-existing JAR file'.
Następnie wpisujemy ścieżkę do JARa (ja podałem z targeta) oraz klasę z funkcją main.
W następnym oknie wybieramy klaster dla wersji 0.20 oraz wpisujemy argumenty dla zadania. Kolejno plik access.log oraz katalog w którym mają być składowane wyniki końcowe.
I klikamy 'finish' :-)

Teraz możemy odpalać!
Prawy guzik na nowego Joba -> Run Job -> Run.

Wszystko powinno pójść dobrze, stan konsoli na końcu (statystyki):
11/06/19 11:53:26 INFO mapred.JobClient: Job complete: job_local_0001
11/06/19 11:53:26 INFO mapred.JobClient: Counters: 12
11/06/19 11:53:26 INFO mapred.JobClient:   FileSystemCounters
11/06/19 11:53:26 INFO mapred.JobClient:     FILE_BYTES_READ=18153346
11/06/19 11:53:26 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=9462349
11/06/19 11:53:26 INFO mapred.JobClient:   Map-Reduce Framework
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce input groups=4
11/06/19 11:53:26 INFO mapred.JobClient:     Combine output records=0
11/06/19 11:53:26 INFO mapred.JobClient:     Map input records=17484
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce shuffle bytes=0
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce output records=4
11/06/19 11:53:26 INFO mapred.JobClient:     Spilled Records=34968
11/06/19 11:53:26 INFO mapred.JobClient:     Map output bytes=142014
11/06/19 11:53:26 INFO mapred.JobClient:     Combine input records=0
11/06/19 11:53:26 INFO mapred.JobClient:     Map output records=17484
11/06/19 11:53:26 INFO mapred.JobClient:     Reduce input records=17484
W katalogu wynikowym powinien znajdować się także plik part-r-00000 z zawartością:
GET 15341
HEAD 1744
POST 398
PUT 1

I mamy gotowe statystyki :-)
Pełne źródła dostępne są tutaj.

sobota, 18 czerwca 2011

Wideo z Poznańskiego Code Retreat

Trochę czasu minęło od pierwszego Poznańskiego Code Retreat, ale parę dni temu pojawił się w sieci krótki filmik z tej imprezy. Wg mnie jest świetny :) Dynamiczny i nawet gdzieś tam mnie widać! Brawa dla autora, którym jest Zbigniew Wantuch!

Zapraszam do obejrzenia:


Źródło: Poznań JUG

środa, 15 czerwca 2011

Szybka instalacja Apache Hadoop dzięki dystrybucji Cloudera

Mając w pamięci trudne doświadczenia z czasów studiów z frameworkiem Hadoop, postanowiłem wrócić do tematu dzięki prezentacji Eugiene Ciurana podczas tegorocznego GeeCONa. Eugienie opowiedział trochę o firmie Cloudera która odwaliła kawał dobrej roboty tworząc i udostępniając dla wszystkich łatwą w instalacji dystrybucję Hadoopa.

Oto jak w szybko zainstalować i uruchomić Hadoop na Ubuntu 11.04:

1. Dodanie nowego repozytorium
sudo touch /etc/apt/sources.list.d/cloudera.list
I dopisać do zawartości:
deb http://archive.cloudera.com/debian lucid-cdh3 contrib
deb-src http://archive.cloudera.com/debian lucid-cdh3 contrib
(Nie ma jeszcze dostępnej wersji dedykowane dla Natty :-/)

2. Instalacja!
sudo apt-get update
apt-cache search hadoop
sudo apt-get install hadoop-0.20

3. Odpalamy przykład :-)
hadoop jar /usr/lib/hadoop-0.20/hadoop-examples.jar pi 10 100

Jeśli wszystko przebiegło poprawnie na końcu powinniśmy otrzymać coś takiego w konsoli:
11/06/15 21:09:07 INFO mapred.JobClient: Job complete: job_local_0001
11/06/15 21:09:07 INFO mapred.JobClient: Counters: 14
11/06/15 21:09:07 INFO mapred.JobClient:   FileSystemCounters
11/06/15 21:09:07 INFO mapred.JobClient:     FILE_BYTES_READ=1642249
11/06/15 21:09:07 INFO mapred.JobClient:     FILE_BYTES_WRITTEN=2108417
11/06/15 21:09:07 INFO mapred.JobClient:   Map-Reduce Framework
11/06/15 21:09:07 INFO mapred.JobClient:     Reduce input groups=2
11/06/15 21:09:07 INFO mapred.JobClient:     Combine output records=0
11/06/15 21:09:07 INFO mapred.JobClient:     Map input records=10
11/06/15 21:09:07 INFO mapred.JobClient:     Reduce shuffle bytes=0
11/06/15 21:09:07 INFO mapred.JobClient:     Reduce output records=0
11/06/15 21:09:07 INFO mapred.JobClient:     Spilled Records=40
11/06/15 21:09:07 INFO mapred.JobClient:     Map output bytes=180
11/06/15 21:09:07 INFO mapred.JobClient:     Map input bytes=240
11/06/15 21:09:07 INFO mapred.JobClient:     Combine input records=0
11/06/15 21:09:07 INFO mapred.JobClient:     Map output records=20
11/06/15 21:09:07 INFO mapred.JobClient:     SPLIT_RAW_BYTES=1110
11/06/15 21:09:07 INFO mapred.JobClient:     Reduce input records=20
Job Finished in 2.361 seconds
Estimated value of Pi is 3.14800000000000000000

Oczywiście uruchomiony Hadoop działa w trybie standalone, który idealnie będzie się dla nas nadawać do deweloperskiej zabawy ;-)
W następnym odcinku trochę pokodzimy!

środa, 25 maja 2011

Eclipse DemoCamp Indigo 2011 - Poznań [02.06.2011]

Za tydzień w Poznaniu kolejne spotkanie z cyklu Eclipse DemoCamp! Tym razem świętujemy wydanie wersji Indigo.
Jak zwykle impreza oprócz charakteru naukowego (prezentacje) będzie miała także aspekt społeczny (dyskusje, piwo i pizza). Co najlepsze dzięki sponsorom piwo i pizza będzie dla uczestników darmowe! Poza tym przewidywane jest do wygrania trochę gadżetów.
...chyba na tym mógłbym już zakończyć tego newsa, bo już wystarczająco zachęciłem do przyjścia ;)

Poniżej plakat promujący wraz z agendą:


Miejsce i czas:
2 czerwca (czwartek) 2011
Pub Brogans, ul. Szewska 20a, Poznań
Start godzina 18:00

Linki:
Szczegółylink
Rejestracjalink

Do zobaczenia!

ś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ą....