24 stycznia 2006

Spring Framework - zastrzyk odrobiny świeżości

Spring Framework jest szkieletem programistycznym opartym na zasadzie przekazywania zależności komponentowi, który w ten sposób otrzymuje, a nie odszukuje i bierze. Dostarczanie zależności zamiast ich odszukiwanie jest wzorcem projektowym o nazwie Przekazywanie Zależności (badź jak tłumaczy się często bezpośrednio z nazwy angielskiej Dependency Injection - wstrzykiwanie/wstrzeliwanie zależości). Pierwszy, obszerny artykuł na ten temat znajdziemy w artykule Martina Fowlera Inversion of Control Containers and the Dependency Injection pattern.

Postanowiłem spróbować swoich sił i wdrożyć Spring Framework do projektu. Projekt jest już przy końcu cyklu wytwarzania i w zasadzie niedługo trafi do produkcji, a później utrzymania, jednakże sądzę, że zawsze jest czas na udoskonalenia (zapewne niejednego aż zmroziło przypominając sobie, jak ślęczał godzinami przed komputerem, w nocy, aby odszukać i poprawić błąd zgłoszony w środowisku produkcyjnym, właśnie po tym, jak chwilę przed oddaniem projektu i świętowaniem jego sukcesu, wdrożono jedno fascynujące rozwiązanie). Mimo takich doświadczeń i spędzenia niejednej nocy nad rozwiązywaniem problemów "na wczoraj", wierzę, że dobre rozwiązanie to takie, które da się przetestować bez zestawiania środowiska produkcyjnego, które po prostu da się przetestować. Wszystko zawsze i tak zależy w dużej mierze od doświadczenia zespołu, ale jego brak nie powinien powstrzymywać "wizjonerów" od działania. Nie potrafię jednoznacznie określić, czy zastosowanie Springa w danym kontekście będzie lepsze od innych kontenerów IoC (ang. Inversion of Control - inna nazwa wzorca Przekazywania Zależności), m.in. PicoContainer, ale jak na początek mojego zabiegu wstrzykiwania Springa (z nadzieją odrodzenia projektu z popiołów), brak jakiejkolwiek zależności od środowiska i brak koniecznych modyfikacji istniejącego kodu bardzo przypadł mi do gustu. Dodatkowo, zmiany, które będzie można łatwo wdrożyć w projekcie, podniosą jakość oprogramowania przez dokładniejsze jego testowanie. Jest to kolejna cecha kontenerów IoC (Spring Framework, PicoContainer), która upraszczaa testowanie aplikacji na nich opartych. Często spotykamy się z sytuacjami, w których środowisko produkcyjne będzie wymagało zaawansowanych konfiguracji, których próba uruchomienia na własnym komputerze byłaby (delikatnie mówiąc) nierozsądna. Dzięki Spring Framework, projekt zwiększy możliwości testowania przez dostarczenie zależności odpowiednich do środowiska. Zależności mogą być różnorakie: klasa obsługująca bazę danych, moduł bezpieczeństwa, moduł transakcyjny, czy moduł odpowiedzialny za wyrysowywanie grafiki. Zakładając, że owe zależności są już przetestowane - są oddzielnymi projektami żyjącymi własnym cyklem rozwojowym - nie ma potrzeby testować ich ponownie, a ich zastąpienie przez tzw. imitacje obiektów (ang. mock objects) znacząco przyspieszy i umożliwi testowanie w innych, skromniejszych, warunkach. Docelowo i tak czekają nas testy różnego rodzaju (wydajnościowe, funkcjonalne, regresyjne, etc.) w środowisku testowym, jednakże możliwość uruchomienia choćby ich wycinka pozwala na wprowadzanie modyfikacji w dowolnej chwili, nawet "za pięć dwunasta" z całą świadomością ich znaczenia i wpływu na cały projekt.

Pobieramy projekt Spring Framework ze strony domowej projektu - http://www.springframework.org/download. Instalacja sprowadza się do rozpakowania paczki do dowolnego katalogu.

Na początek naszej przygody ze Spring Framework, rozważmy bardzo uproszczoną klasę - pl.org.laskowski.WelcomeBean.

package pl.org.laskowski;

public class WelcomeBean {
private String configFilename;

public String getConfigFilename() {
return configFilename;
}

public void setConfigFilename(String configFilename) {
this.configFilename = configFilename;
}

public void execute() {
System.out.println("Witaj!");
System.out.println("Korzystam z pliku konfiguracyjnego " + getConfigFilename());
}
}
Klasa nie wyróżnia się niczym specjalnym - jest po prostu klasą udostępniającą właściwość configFilename oraz bezparametrową metodę execute. Mimo swej prostoty jej działanie jest uzależnione od świata zewnętrznego - pliku konfiguracyjnego. Bez niej klasa nie będzie poprawnie funkcjonować (zakładamy, że wyświetlenie null nie należy do kategorii "aplikacje poprawnie działające").

Teraz przyjrzyjmy się plikowi XML - beans.xml.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="welcomeBean" class="pl.org.laskowski.WelcomeBean">
<property name="configFilename" value="config.xml"/>
</bean>
</beans>
Zrozumienie zawartości pliku jest trywialne - plik sam uzasadnia swoje istnienie. Poprzez znacznik property ustawiana jest zmienna instancji wskazana przez atrybut name na wartość wskazaną przez value. Skoro znacznik property jest zagnieżdżony w bean wskazuje to na jego przynależność - klasę wskazywana przez atrybut class znacznika bean.

Jak można odczytać z (opcjonalnego) DOCTYPE jest to plik konfiguracyjny dla klasy org.springframework.beans.factory.xml.XmlBeanFactory należącej do Spring Framework. W naszym rozwiązaniu będzie to element kluczowy w naszym wdrażaniu Springa. Przyjrzyjmy się zatem jak uruchomić Spring Framework tak, aby on z kolei uruchomił naszą klasę - pl.org.laskowski.WelcomeBean. Stworzymy do tego celu klasę - pl.org.laskowski.Main.

package pl.org.laskowski;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class Main {

public static void main(String[] args) {
XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));
WelcomeBean tb = (WelcomeBean) bf.getBean("welcomeBean");
tb.execute();
}
}
Do uruchomienia aplikacji będą nam potrzebne następujące biblioteki:
  • spring-framework.jar
  • commons-logging.jar
Po ustawieniu środowiska tak, aby wspomniane biblioteki były dostępne, uruchomienie aplikacji sprowadza się do wywołania klasy pl.org.laskowski.Main.
java pl.org.laskowski.Main
Bardzo istotnym elementem wdrażania Spring Framework jest fakt braku koniecznych modyfikacji. Jak można zauważyć w przykładowej klasie pl.org.laskowski.WelcomeBean, klasa nie posiada żadnych dodatkowych obciążeń, czego wynikiem mogłoby być wykorzystanie Spring Framework.
Daje to możliwość rozważnego wdrażania do projektu, bez niepotrzebnej rewolucji w sposobie wytwarzania aplikacji, szczególnie w ostatnich dniach projektu (które zwykle i tak nie są ostatnimi). Dalszy rozwój aplikacji odbywa się bez jakichkolwiek zmian, a jedynie finalna klasa uruchamiająca projekt ulegnie zmianie.
Oczywiście wykorzystanie Springa z jego dostarczaniem zależności zdejmuje konieczność inicjowania i konfiguracji usług zewnętrznych, co często obciąża niepotrzebnie wielu członków zespołu. W sytuacji, kiedy zależność jest dostarczana, korzystamy z założenia, że została ona zainicjowana i przekazana zanim klasa została uruchomiona. Znacząco wpływa to na zmniejszenie ilości kodu do utrzymania przez zespół programistów.

Komentarze, spostrzeżenia i uwagi (szczególnie krytyczne) mile widziane. Pomysły na następne artykuły również. Skorzystaj z możliwości komentowania artykułu, poniżej.