May 22, 2009

Skrypty Groovy na żądanie w Springu

W poprzednich artykułach udało nam się nakłonić Springa do załadowania skryptu w Groovim z bazy danych i uruchomienia go. Jednak to nie wszystko - chcielibyśmy również w łatwy sposób móc testować nasze skrypty przed umieszczeniem ich w bazie danych. Konkretnie dążymy do stworzenia webowego ekranu, w którym w przeglądarce będzie można wpisywać treść skryptu, uruchomić go na serwerze i obejrzeć wyniki. Zacznijmy od końca, czyli od uruchomienia:

public interface AutowiredScriptExecutor {

Object execute(String source) throws ScriptCompilationException, ScriptExecutionException;

<T> T execute(String source, Class<T> returnType) throws ScriptCompilationException, ScriptExecutionException;

}
Interfejs dość oczywisty: jako argument podajemy kod źródłowy np. w Groovy a zwracany jest rezultat wykonania skryptu (skrypt musi zapewnić jakiś mechanizm zwracania danych). Przeciążona wersja uchroni nas od rzutowania w dół z Object, jeśli skrypt zwraca znany, konkretny typ. Przejdźmy do nie do końca trywialnej implementacji:

public class GroovyScriptExecutor implements AutowiredScriptExecutor, BeanFactoryAware {

private AutowireCapableBeanFactory beanFactory;

@Override
@SuppressWarnings("unchecked")
public <T> T execute(String source, Class<T> returnType) throws ScriptCompilationException, ScriptExecutionException {
Callable<T> scriptedObject;
try {
StaticScriptSource scriptSource = new StaticScriptSource(source);
ScriptFactory scriptFactory = new GroovyScriptFactory(source);
scriptedObject = (Callable<T>) scriptFactory .getScriptedObject(scriptSource, null);
} catch (IOException ioEx) {
throw new ScriptCompilationException("Error loading script", ioEx);
}
beanFactory.autowireBeanProperties(scriptedObject, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
scriptedObject = (Callable<T>) beanFactory.initializeBean(scriptedObject, "script/" + scriptedObject.getClass().getName());
try {
return scriptedObject.call();
} catch (Exception e) {
throw new ScriptExecutionException(e);
}
}

@Override
public Object execute(String source) throws ScriptCompilationException, ScriptExecutionException {
return execute(source, Object.class);
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = ((AutowireCapableBeanFactory)beanFactory);
}

}


Na początek widać, że wstrzykujemy interfejs BeanFactory, który można bezpiecznie zrzutować do AutowireCapableBeanFactory. Samo uruchomienie skryptu należy podzielić na dwa etapy. Po pierwsze ładujemy skrypt i kompilujemy go (scriptFactory.getScriptedObject()), otrzymując w wyniku pełnoprawną klasę Java, a ściślej interfejs. Przyjąłem bowiem, że przekazana klasa w Groovy musi implementować interfejs java.util.concurrent.Callable<T>; java.lang.Runnable wydaje się gorszym wyborem, bowiem nie potrafi zwracać wyniku (patrz wyżej) oraz nie deklaruje rzucania żadnych sprawdzanych wyjątków.

Drugi etap to "usprignowienie" stworzonej klasy w języku Groovy. Dwa tajemnicze polecenia pozyskanego interfejsu AutowireCapableBeanFactory odpowiednio: wstrzykują zależności oraz wykonują post procesory (transakcje, adnotacje, security). Daje to niezwykłe możliwości; trzeba jedynie pamiętać, że ponieważ initializeBean() z dużym prawdopodobieństwem opakuje nasz obiekt nakładając nań aspekty, musimy użyć obiektu zwróconego przez tą metodę, a nie tego oryginalnego. Inaczej metoda się napracuje, a i tak nie nie skorzystamy z jej efektów. Na koniec uruchamiamy skrypt - ponieważ założyliśmy, że implementuje on interfejs Callable, wystarczy wywołać jego metodę call().

Mamy usługę, pora na widok. Przypominam, że celem jest webowy formularz w naszej aplikacji, umożliwiający wpisanie dowolnego skryptu i uruchomienie go. Zacznijmy od akcji Struts2:

public class ScriptTestAction extends ActionSupport {

private static final Logger log = Logger.getLogger(ScriptTestAction.class);

private AutowiredScriptExecutor scriptExecutor;

private String source;

@Override
public String execute() throws Exception {
try {
String result = scriptExecutor.execute(source).toString();
addActionMessage(result);
} catch (Exception e) {
log.warn(e, e);
}
return SUCCESS;
}

public void setScriptExecutor(AutowiredScriptExecutor scriptExecutor) {
this.scriptExecutor = scriptExecutor;
}

public void setSource(String source) {
this.source = source;
}

@RequiredStringValidator(message = "Source is requeired")
public String getSource() {
return source;
}

}


Nawet nie ma czego tłumaczyć: wstrzykujemy stworzony przed chwilą komponent implementujący AutowiredScriptExecutor , tworzymy pole source odpowiadające polu tekstowemu na formularzu i w execute() uruchamiamy skrypt. Efekty jego działania dodajemy jako komunikat. Skróciłem nieco kod na potrzeby artykułu, ale błędy również powinny w przystępny sposób trafiać do klienta. Na koniec szczypta konfiguracji:

<package name="admin" extends="default" namespace="/admin">
<action name="script/input">
<result>/WEB-INF/jsp/admin/scriptInput.jsp</result>
</action>
<action name="script/exec" class="admin.scriptTestAction">
<result>/WEB-INF/jsp/admin/scriptInput.jsp</result>
<result name="input">/WEB-INF/jsp/admin/scriptInput.jsp</result>
</action>
</package>


...i jesteśmy gotowi do testów. Poniżej kod w Groovy, jaki wpisałem w formularzu oraz efekt po uruchomieniu. Przyjrzyjcie się uważnie temu skryptowi... tak, wszystko co najlepsze w Springu! Wstrzykujemy usługi za pomocą @Autowired czy wręcz EntityManager z adnotacją PersistenceContext. Mało tego, możemy nawet włączyć deklaratywne transakcje - słowem, całe dobrodziejstwo Springa ma tutaj zastosowanie. Dlatego tyle pisałem o metodzie initializeBean(), to ona zdziałała te cuda :-). A jakby tego było mało, zobaczcie jak zwięzły jest kod w Groovim. Ile w "oldschoolowej" Javie zajęłoby Wam następujące zadanie: dla każdego numeru PESEL z kolekcji wywołaj metodę isBlackListed(), a jej wynik dodaj do listy wynikowej? Jeśli kod jest dla Was niezrozumiały, podpowiedź: isBlackListed() zwraca boolean, a nasza metoda call() powinna zwrócić List<Boolean> (dlaczego?)

import java.util.concurrent.Callable;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.blogspot.nurkiewicz.filmportal.customer.CustomerBlackList;

public class GroovyScript implements Callable<List<Boolean>> {

@Autowired
CustomerBlackList blackList;

@PersistenceContext
EntityManager em;

@Override
@Transactional
def call() {
def peselNumbers = em.createQuery("SELECT pesel FROM User WHERE pesel IS NOT NULL").resultList
return peselNumbers.collect{blackList.isBlackListed(it)}
}

}
Na zrzucie ekranu widać wynik już wykonanego skryptu: dwuelementowa lista wartości logicznych:



Na koniec mała perełka. Zapewne zauważyliście, że taki ekran w aplikacji to nie tyle wygodne miejsce do debugowania skryptów. W rzeczywistości z jego pomocą można zrobić w aplikacji WSZYSTKO - odwołać się do każdego komponentu, wstrzyknąć sobie EntityManager czy wręcz DataSource i grzebać w bazie do woli. Słowem - kolosalne ryzyko. Jak zabezpieczyć taki delikatny komponent? Oprócz zabezpieczenia URL proponuję:

@RolesAllowed("ROLE_ADMIN")
public class GroovyScriptExecutor implements AutowiredScriptExecutor, BeanFactoryAware {
//...
}


Tak, tak - jeśli teraz zaloguję się do aplikacji użytkownikiem nieposiadającym roli ROLE_ADMIN:



Żeby ta magia zadziałała (nie przypomina Wam to pewnej innej technologii?), wystarczy w springowym deskryptorze dodać (tag z przestrzeni nazw Spring Security):

<global-method-security secured-annotations="enabled" jsr250-annotations="enabled" />


JSR 250 definiuje kilkadziesiąt ciekawych adnotacji, zresztą Spring udostępnia własną @Secured o takiej samej semantyce. Prawda, że Groovy + Spring + Spring Security + Struts2 wybornie się komponują? :-)

May 10, 2009

Relacja z GeeCON 2009 w Krakowie

Właściwie mógłbym się ograniczyć do standardowego "było super, niech żałują Ci, co ich nie było" - i nie minąłbym się z prawdą. Ale po kolei…

Z voucherem otrzymanym od Warszawa JUG, za co bardzo dziękuję, wybrałem się do Krakowa. Na pierwszy ogień (no, na drugi, najpierw był szwedzki stół ;-)) padła JavaFX. Keynote Simona Rittera zostanie z pewnością zapamiętana dzięki "sztuczce z deską": oczywiście będę wredny i nie zdradzę na czym polegała, ale była imponująca :-). I tak, chociaż pokaz nie miał wiele wspólnego z Javą FX, wszyscy będą już kojarzyli tą technologię - i to jest sztuka prowadzenia prezentacji!

Potem wysłuchałem krótkiej prezentacji o Springu 3.0 w duecie Alef Arendsen i Arjen Poutsma (tego ostatniego miałem już przyjemność wysłuchać podczas Spring 3.0 w Warszawie). Zasadniczo zmiany można opisać jako "Tiger ready" - Spring czerpie wreszcie pełnymi garściami z dobrodziejstw Javy 5. I chyba najwyższy czas. Ja wiem, jest jeszcze mnóstwo starego kodu, ale Tygrysek ma już 5 lat i niedługo skończy się na niego wsparcie - może warto rozpocząć migracje? Zwłaszcza, że nadeszła propozycja ustandaryzownaia adnotacji @Inject.

Największe wrażenie zrobił na mnie Miško Hevery (prowadzi bardzo ciekawego bloga) ze swoim wykładem o testowaniu i testowalności kodu. (Re)prezentowane przez niego i przez firmę Google standardy dotyczące jakości kodu i jego testowania mogą budzić podziw. Podczas gdy wiele firm albo w ogóle nie testuje kodu albo ogranicza się "wyrobów testopodobnych", Miško przedstawił testowanie jednostkowe jako dziedzinę nie mniej ważną (i interesującą!) od samego programowania. Specjalne zespoły szkolące programistów, własne i gotowe narzędzia do tworzenia metryk, gry dla koderów, code review... Długo by wymieniać - i można pozazdrościć. Jednak najbardziej utkwiły mi w pamięci dwie tezy: po pierwsze, pisanie testów jednostkowych to sztuka, umiejętność. Można nie umieć pisać testów jednostkowych tak jak nie umie się programować w takim czy innym języku/frameworku. Ale co ważniejsze - nie należy się tej niewiedzy wstydzić lub jej tuszować, a trzeba się uczyć, by po pewnym czasie osiągnąć biegłość i móc pochwalić się nawet większą produktywnością. A po drugie - pisanie testów jest trendy i ciekawe - chwytliwe porównanie do seksu (jeśli pisanie testów jednostkowych nie sprawia ci przyjemności, robisz coś źle) niech podsumuje tą intrygującą prelekcję.

Bardzo czekałem na dwie prezentacje o dynamicznych i zwinnych kuzynach ociężałej i nudnej Javy: Groovim i Scali. Václav Pech opisał podstawowe cechy Grooviego analizując prostą klasę i jej test jednostkowy. Krok po kroku przepisywał tylko odrobinkę tendencyjnie napisany kod wzbudzając podziw audytorium, parokrotnie skracając klasę. Nauka nowego języka przebiegła bezboleśnie i z przyjemnością. Gdyby tylko ten Groovy był szybszy.

Potem przyszedł czas na Scalę w wykonaniu Luca Duponcheela. Niestety tajniki programowania funkcyjnego okazały się dla mnie (oraz sporej części multikinowej sali) nie do przełknięcia. Takiej ilości znaków specjalnych, ze szczególnym umiłowaniem wszelkiej maści nawiasów, dwukropków i strzałek, nie widziałem od czasu analizy matematycznej na studiach. Szkoda, może kiedyś jeszcze podejdę do tego języka.

Niezbyt elegancka ewakuacja na prelekcję Jacka Laskowskiego spod znaku EJB 3.1. Nie dość, że temat mi znany, to do tego dotarliśmy na ostatni slajd. Swoją drogą ciekawi mnie - czy kolejne wersje EJB będą standaryzowały pomysły Springa czy raczej Spring zacznie interpretować i emulować EJB (co już robi z niektórymi adnotacjami). Bo moim skromnym zdaniem EJB ustępuje obecnie możliwościami Springowi, nie dając wiele w zamian (chociaż od wersji 3.0 technologia to całkiem ciekawa).

Na koniec prezentacja Piotra Walczyszyna o inkorporacji Flexa, Google App Engine i Javy. Może troszkę ta Java naciągana (służyła ona do wystawienia serwletem usługi dla front-endu we Fleksie), ale gwoździem do trumny były fatalne problemy techniczne. Szkoda, bo sam temat mimo wszystko ciekawy. Zresztą w tej kwestii można by jeszcze wspomnieć o nietrafionym pomyśle wyświetlania obrazu z kamery skierowanej na prelegenta. Pół wielkiego ekranu było niezagospodarowane, prezentację było słabo widać a prelegent lepiej wyglądał na żywo. Jednak ten problem szybko rozwiązano. Więcej uwag nie mam.

Niestety obowiązki zmusiły mnie do powrotu już w czwartek, żałuję najbardziej Seama i JRuby - cóż, może za rok. Podsumowując - impreza jak najbardziej udana, wielki ekran i komfortowe siedzenia kinowej sali bardzo sprzyjały percepcji. Nie wiem jak przedstawiała się międzynarodowość uczestników, ale samą konferencję z pewnością można międzynarodową nazwać.

May 1, 2009

Logika biznesowa w Groovy ze Springiem i JPA część 2/2

Przypominam, że naszym celem jest pobieranie skryptów Groovy z bazy danych, a użyjemy do tego JPA. Oczywiście chcemy maksymalnie wykorzystać wsparcie springowe, jednak nie obejdzie się bez małego "tuningu". Krótka zabawa w detektywa i znajdujemy metodę convertToScriptSource() klasy org.springframework.scripting.support.ScriptFactoryPostProcessor:

protected ScriptSource convertToScriptSource(
String beanName, String scriptSourceLocator, ResourceLoader resourceLoader) {

if (scriptSourceLocator.startsWith(INLINE_SCRIPT_PREFIX)) {
return new StaticScriptSource(scriptSourceLocator.substring(INLINE_SCRIPT_PREFIX.length()), beanName);
}
else {
return new ResourceScriptSource(resourceLoader.getResource(scriptSourceLocator));
}
}
Metoda ta parsuje string przekazany jako argument atrybutu script-source i zwraca obiekt implementujący ScriptSource:
package org.springframework.scripting;

public interface ScriptSource {
String getScriptAsString() throws IOException;
boolean isModified();
String suggestedClassName();
}
Ale zacznijmy od poprawienia ScriptFactoryPostProcessor. Stworzyłem klasę dziedziczącą po niej i przesłoniłem metodę convertToScriptSource() tak, aby rozpoznawała również prefiks "jpa". Przykład na koniec a implementacja trywialna. Najważniejsze to jak zmusić Springa, żeby używał naszego post procesora, skoro nigdzie jawnie nie deklarujemy tego oryginalnego? Otóż wystarczy dodać bean:
<bean id="scriptFactoryPostProcessor" class="com.blogspot.nurkiewicz.PluggableScriptFactoryPostProcessor"/>
Kwestia konwencji nazewniczej: jeśli użytkownik zdefiniuje bean o id scriptFactoryPostProcessor, będzie on używany zamiast tego oryginalnego. Ot cała filozofia. A co zwraca nasz post procesor po napotkaniu prefiksu "jpa"?
public class JpaScriptSource implements ScriptSource {

private final String id;
private final ScriptDao scriptDao;

private Date lastModified;

public JpaScriptSource(ScriptDao scriptDao, String id) {
this.scriptDao = scriptDao;
this.id = id;
}

@Override
public synchronized String getScriptAsString() throws IOException {
Script script = scriptDao.restore(id);
lastModified = script.getLastModified();
return script.getSource();
}

@Override
public synchronized boolean isModified() {
return scriptDao.restore(id).getLastModified().after(lastModified);
}

@Override
public String suggestedClassName() {
return null;
}

}
I do kompletu obiekt reprezentujący nasz skrypt w bazie danych (szczegóły modelu relacyjnego trzymam w orm.xml, tak jest chyba czytelniej):
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Script {

@Id
private String id;
private String description;
private String source;
private Date lastModified = new Date();

//get/set
}
Po kolei. Metoda getScriptAsString() najpierw ładuje z bazy obiekt typu Script (u mnie z tabelki Scripts) o podanym kluczu głównym. W naszym wypadku id jest napisem będącym nazwą skryptu. To id przekażemy w atrybucie script-source beanu. Potem wystarczy zwrócić treść skryptu (właściwość source).

Atrybut lastModified jest wykorzystywany w metodzie isModified(). Jej działanie jest proste: Spring co zadany czas (atrybut refresh-check-delay tagu ) sprawdza, czy skrypt nie został zmodyfikowany i jeśli tak: ładuje go ponownie. Technicznie woła właśnie metodę isModified() i jeśli zwróci ona prawdę, zawoła metodę getScriptAsString(). Ta ostatnia pobierze z bazy danych uaktualniony kod i voila!

Ponieważ ilość wywołań ScriptDao.restore() jest znacząca, encja Script jest objęta cachem w Hibernate. Możliwe przypadki użycia są bardzo szerokie. Przykładowo administrator biznesowy modyfikuje skrypt w bazie za pomocą przyjaznego interfejsu webowego (jedyne odwołanie do bazy i aktualizacja cache) a w ciągu sekundy (ustawiono 1000 ms) Spring woła metodę isModified(). Ta wyciąga z cache nową wersję skryptu i zauważa, że data lastModified zmieniła się. Zwraca prawdę sugerując Springowi odświeżenie kodu. Metoda getScriptAsString() ponownie trafia w cache, znowu oszczędzając cenne zasoby bazy danych. A czemu nie zwracać w isModified() zawsze prawdy? Skoro i tak skrypt zawsze znajdziemy w cache, po co utrudniać sobie życie? Ano skrypt może i jest w cache, ale skoro się zmienił, to Spring musi go ponownie przetworzyć (zależy od języka: preprocesowanie, kompilacja, optymalizacja). Dlatego lepiej nakazywać odświeżanie tylko wtedy, gdy faktycznie skrypt został zmodyfikowany.

Jak to wszystko się spina razem? Nasza definicja w applicationContext.xml wygląda teraz następująco:
<lang:groovy id="customerVerifier" script-source="jpa:CustomerVerifier"
refresh-check-delay="1000" autowire="byType" />
Nasz "podrasowany" post procesor rozpoznaje prefiks "jpa" i przekazuje identyfikator skryptu "CustomerVerifier" (jeśli ktoś woli, może użyć numerycznego klucza) do nowoutworzonego obiektu JpaScriptSource. Obiekt ten jest następnie wykorzystywany przez wewnętrzną infrastrukturę Springa. Za jego pomocą framework załaduje skrypt z bazy (korzystając ze ScriptDao) - reszta jest poza naszą odpowiedzialnością.

Naturalnie nic nie stoi na przeszkodzie, żeby zadeklarować wiele beanów <lang:groovy>, przekazując po prefiksie "jpa" identyfikatory różnych skryptów z bazy. W ten sposób tworzymy całą aplikację opartą o trzon usług napisanych w Javie (beany springowe) oraz zbiór skryptów w Groovy (które można zmieniać w trakcie działania, np. poprzez interfejs webowy) realizujących szybkozmienną logikę biznesową. Taka architektura ma swoje wady i zalety, ktoś może też powiedzieć "OSGi dla ubogich". Ale na pewno warto ją rozważyć projektując duże systemy, gdzie elastyczność stoi na pierwszym miejscu.

Na koniec kilka uwag. Testowanie jednostkowe tego rozwiązania jest mocno utrudnione. Ponieważ nie da się ładować skryptów leniwie jak innych beanów, skrypty muszą być w bazie w chwili wstawania kontekstu. Niestety, baza (ja używałem H2) tworzy się w pamięci przy starcie kontekstu a metoda oznaczona jako @Before uruchamia się dopiero później. Czyli nie da się wstawić skryptów przed startem kontekstu - bo wtedy nie ma jeszcze bazy - a później jest już za późno, bo kontekst i tak nie może wstać bez wstawionych skryptów :-(.

Przy okazji chciałem użyć schematów (schema) w PostgresQL, na którym testowałem JPA. O ile w bazie działają znakomicie, o tyle Hibernate nie radzi sobie z generacją kodu DDL: tworzy tabelki bez uprzedniego stworzenia schematów. Uniemożliwiło to dalsze korzystanie z dobrodziejstw tego mechanizmu, chociaż JPA wspiera nazwy schematów i gdy są już gotowe, wszystko śmiga. Błąd jest już zgłoszony do JIRA Hibernate (HHH-1853) - czeka 3 lata, rok w tą czy w tamtą nie zrobi różnicy :-(

Logika biznesowa w Groovy ze Springiem i JPA część 1/2

Na początek scenka rodzajowa: w pewnym systemie istotną rolę odgrywała wstępna weryfikacja klienta opisanego następującym obiektem:
public class CustomerProfile {
private long pesel;
private int age;
private BigDecimal salary;
private Locale country;
//get/set
}
Komponent realizujący weryfikację ma intuicyjny interfejs:
public interface CustomerVerifier {
boolean verify(CustomerProfile profile);
}
W pierwszej wersji systemu wystarczyła zwykła implementacja i wpis w deskryptorze springowym:
<bean id="customerVerifier " class="com.blogspot.nurkiewicz.customer.CustomerVerifierImpl">
Życie jednak pokazało, że logika biznesowa dokonująca weryfikacji zmienia się jak w kalejdoskopie. Dochodzą nowe warunki, zmieniają się parametry, coraz nowe komponenty biorą udział w weryfikacji. Jasnym stało się, że zamiast sztywnego kodu w Javie potrzebny jest bardziej elastyczny kod skryptowy, niezależny od aplikacji i możliwy do modyfikacji bez ponownego wdrażania. Szczęściem w nieszczęściu wystarczy zmiana w XMLu:
<lang:groovy id="customerVerifier" script-source="/scripts/CustomerVerifier.groovy"
refresh-check-delay="1000" autowire="byType" />
Zamiast zwykłego beanu odwołujemy się do pliku z kodem źródłowym w Groovy gdzieś na dysku:
class GroovyCustomerVerifier implements CustomerVerifier {

boolean verify(CustomerProfile profile) {
return profile.age in (24..56)
}
}
Potęga Groovy w połączeniu ze Springiem jest widoczna od razu: po pierwsze klasa w Groovy implementuje interfejs biznesowy w Javie - z punktu widzenia użytkownika komponentu CustomerVerifier (a zatem i reszty istniejącego kodu) nie widać różnicy. Poza tym zwróćcie uwagę jak wygodną składnię daje Groovy w dostępie do CustomerProfile.

Dobrze, to było trywialne. Ale stary komponent CustomerVerifierImpl miał dodatkowo wstrzyknięty bean (już w Javie) implementujący interfejs CustomerBlackList (implementacja trywialna, semantyka oczywista :-)):
public interface CustomerBlackList {
boolean isBlackListed(long pesel);
}
Jak skorzystać z dobrodziejstw DI w Groovim? Tutaj Spring znowu pokazuje pazur:
class GroovyCustomerVerifier implements CustomerVerifier {
CustomerBlackList customerBlackList

boolean verify(CustomerProfile profile) {
return !customerBlackList.isBlackListed(profile.pesel) && profile.age in (24..56)
}

}
Wystarczyło dodać pole w klasie Groovy i już, można w skrypcie korzystać z beanu springowego! Mało tego, w ten sam sposób można wstrzyknąć do skryptu dowolny bean, a nawet EntityManager ze specyfikacji JPA. Mało tego, możemy użyc adnotacji @Transactional i objąć metody w Groovy transakcjami springowymi! Daje to ogromne możliwości twórcom skryptów zwłaszcza, że nie wymaga ponownego wdrażania aplikacji. A wszystko dzięki atrybutowi autowire="byType".

Jest już całkiem nieźle - logika została wyniesiona do pliku, można korzystać do woli z pozostałych komponentów w naszej aplikacji. Jest tylko mały problem - niemal niczego jeszcze nie osiągnęliśmy… Logika co prawda jest na zewnątrz, ale utrzymanie plików ze skryptem gdzieś na dysku i ich ręczna edycja to kiepski pomysł. Niewygodnie się to klastruje, nie pozwala na kontrolę dostępu, historię, transakcyjność… Słowem: potrzebujemy bazy danych do przechowywania naszych skryptów. I tu niemiły zgrzyt: Spring wspiera jedynie pobieranie skryptów z dysku lub wpisywanie ich bezpośrednio w deskryptorze applicationContext.xml - czyli jeszcze gorzej*. I tu zaczyna się cała zabawa! Jak zmusić Springa do ładowania (i odświeżania!) skryptów zapisanych w bazie danych - w następnym wpisie.

* Przynajmniej do czasu rozwiązania SPR-5106, czyli wersji 3.1 Springa...