24 stycznia 2006

Spring Framework - zastrzyk odrobiny świeżości

6 komentarzy
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.

21 stycznia 2006

Uruchamiamy pierwszą aplikację w technologii JavaServer Faces

6 komentarzy
JavaServer Faces (JSF) jest technologią komponentów graficznych (innymi słowy: szkieletem programistycznym) do budowania aplikacji uruchomionych po stronie serwera aplikacyjnego. Poszczególne wersje specyfikacji JSF rozwijane są w procesie Java Community Process (JCP). Ostatnia finalna wersja specyfikacji rozwijana była jako JSR-252 i nazwana jako JavaServer Faces 1.2.

Jak podkreślają autorzy specyfikacji JSF 1.2, technologia JavaServer Faces oparta jest o następujące specyfikacje:
  • JavaServer Pages (JSP) 2.1
  • Java Servlet 2.5
  • Java SE 5.0
  • Java EE 5.0
  • JavaBeans 1.0.1
  • JavaServer Pages Standard Tag Library (JSTL) 1.2
i była tworzona z myślą o wykorzystaniu z innymi szkieletami programistycznymi, m.in. portletami (JSR-168).

Ideą przewodnią JSF było uproszczenie procesu tworzenia skomplikowanych aplikacji graficznych, których stan komponentów wizualnych uaktualniany jest automatycznie pomiędzy poszczególnymi akcjami użytkownika. Zbiór dostępnych komponentów graficznych pozwala na tworzenie aplikacji reagujących na zdarzenia wyzwalane po stronie klienta i przesyłanie ich, bez ingerencji programisty, na stronę serwera. Oprócz oczywistych zalet stosowania ustandaryzowanego szkieletu programistycznego z gotowymi komponentami do budowania interfejsu użytkownika, istnieje możliwość stworzenia i dystrybucji własnych. Przede wszystkim pozwala to na stworzenie narzędzi programistycznych, które dostarczałyby standardowych komponentów i dodatkowych rozszeżeń. Dołączając do nich możliwość tworzenia interfejsu graficznego poprzez technikę "przeciągnij-i-upuść" dochodzimy do przyczyn leżących u podstaw stworzenia technologii JavaServer Faces.

Istnienie specyfikacji poparte jest możliwością jej wykorzystania w projektach. Uruchomienie aplikacji opartej o JavaServer Faces wymaga środowiska uruchomieniowego, który dostarcza implementacji pomysłów opisanych w specyfikacji. Specyfikacja jest jedynie zbiorem wytycznych, podczas gdy implementacja pozwala na ich zmaterializowanie. Każdej specyfikacji w procesie Java Community Process (JCP) towarzyszy implementacja referencyjna (ang. RI - reference implementation). Implementacja referencyjna gwarantuje środowisko do uruchomienia aplikacji zgodnych z wytycznymi specyfikacji. Nie gwarantuje jednak jej szybkości działania i często nie dostarcza wielu dodatkowych uproszczeń, które są wielce porządane, jednakże ciało ustawodawcze nie dopracowało się jednolitego stanowiska odnośnie ich realizacji. W wielu przypadkach, zatem, powstają alternatywne implementacje specyfikacji. Ich zgodność stwierdzana jest poprzez uruchomienie tzw. TCK - Test Compatibility Kit, czyli zbioru testów do badania zgodności ze specyfikacją. W przypadku specyfikacji JavaServer Faces, alternatywną implementacją jest projekt wolnodostępny - Apache MyFaces.

Apache MyFaces (w skrócie MyFaces) jest wolnym oprogramowaniem tworzonym pod auspicjami Apache Software Foundation - niekomercyjnej fundacji rozwoju oprogramowania. Ostatnia wersja Apache MyFaces to 1.1.1.

Projekt składa się z dwóch części - podprojektów: biblioteki komponentów JSF oraz biblioteki rozszerzającej - Tomahawk.

Uruchomienie przykładowych aplikacji dostarczanych z Apache MyFaces

Projekt MyFaces rozprowadzany jest z wieloma przykładowymi aplikacjami, które prezentują możliwości JavaServer Faces w aplikacjach internetowych. Przykłady są rozprowadzane oddzielnie jako myfaces-wersja-examples, które można pobrać pod adresem http://myfaces.apache.org/binary.cgi. Do ich uruchomienia potrzebny jest kontener servletów, np. Apache Tomcat.

Wykonaj poniższe kroki w celu uruchomienia przykładowych aplikacji JSF projektu MyFaces na kontenerze servletów - Apache Tomcat.

  • Instalacja Apache Tomcat

    Instalacja Tomcata sprowadza się do rozpakowania odpowiedniej paczki do dowolnie wybranego katalogu (do którego odwołujemy się później za pomocą zmiennej CATALINA_HOME).

  • Kopiowanie przykładowych aplikacji

    Przekopiuj wszystkie pliki *.war z myfaces-wersja-examples do katalogu CATALINA_HOME/webapps

  • Uruchomienie aplikacji MyFaces

    Po uruchomieniu Tomcata (CATALINA_HOME/bin/catalina.bat run) pod adresem http://localhost:8080/simple znajduje się jedna z 4 aplikacji - simple. Zastąpienie simple w adresie przez blank, sandbox, czy tiles uruchamia pozostałe przykłady.

Zaleca się przejrzenie wszystkich przykładów, aby zapoznać się z możliwościami JSF w wydaniu Apache MyFaces.

Własna aplikacja JSF

Po zapoznaniu się z przykładowymi aplikacjami JSF dostarczanymi z MyFaces czas na stworzenie własnej. Nic tak nie uczy jak praktyka, a napotkane utrudnienia poprawiają naszą zdolność zapamiętywania. Dodatkowo, pozwala to w pełni dostrzec korzyści z potencjalnego wykorzystania we własnych projektach.
  • Utworzenie projektu aplikacji internetowej

    Możesz to wykonać w wybranym przez siebie środowisku IDE, bądź ręcznie zakładając katalog o nazwie mojapp w dowolnym miejscu.

  • Umieszczenie bibliotek MyFaces w aplikacji

    Biblioteki MyFaces znajdują się w katalogu głównym projektu MyFaces w pliku myfaces-all.jar. Przekopiuj plik do katalogu WEB-INF/lib aplikacji. Dodatkowo potrzebne nam bedą następujące biblioteki:

    W sumie potrzeba 12 plików jar w katalogu WEB-INF/lib.
  • Stworzenie deskryptora aplikacji internetowej - WEB-INF/web.xml

    Każda aplikacja internetowa wymaga pliku konfiguracyjnego - WEB-INF/web.xml. Deskryptor instalacji (ang. deployment descriptor) instruuje kontener servletów o wymaganych usługach. Zawiera on deklarację zewnętrznych zależności z kontenerem servletów. Znajdziemy tam m.in. deklarację servletów i ich mapowania na adresy URL. W naszej przykładowej aplikacji informacja o servlecie - Faces Servlet - i jego mapowaniu będzie wystarczająca.

    <?xml version="1.0" encoding="ISO-8859-2"?>
    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
    <web-app>
    <description>Przykładowa aplikacja JSF</description>
    <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    </web-app>
    Powyższa konfiguracja zapewnia nam zainicjowanie kontekstu JSF poprzez wywołanie servletu javax.faces.webapp.FacesServlet.
    Mapowanie (poprzez servlet-mapping) zapewnia nam, że każde wywołanie typu *.jsf uruchomi FacesServlet, a ten zgodnie z wytycznymi w konfiguracji JSF - faces-config.xml - wykona odpowiednie czynności.

  • Stworzenie konfiguracji JSF - faces-config.xml

    Podobnie jak w przypadku aplikacji internetowych, które konfigurowane są poprzez swój własny deskryptor - web.xml - aplikacje oparte o JSF wymagają własnego pliku konfiguracyjnego. Standardowo jest to plik o nazwie faces-config.xml, jednakże nazwa podlega konfiguracji i można ją zmienić. Poniżej znajduje się plik konfiguracyjny dla naszej aplikacji.

    <?xml version="1.0" encoding="ISO-8859-2"?>
    <!DOCTYPE faces-config PUBLIC
    "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
    "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
    <faces-config>
    <application>
    <message-bundle>pl.org.laskowski.i18n.Komunikaty</message-bundle>
    <locale-config>
    <default-locale>pl</default-locale>
    <supported-locale>en</supported-locale>
    </locale-config>
    </application>
    <managed-bean>
    <managed-bean-name>formularz</managed-bean-name>
    <managed-bean-class>pl.org.laskowski.Formularz</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>
    <navigation-rule>
    <description>
    Kiedy dane formularza są poprawne wyświetl ekran powitalny,
    w przeciwnym przypadku wróć do formularza
    </description>
    <navigation-case>
    <from-outcome>poprawne_dane</from-outcome>
    <to-view-id>/witaj.jsp</to-view-id>
    </navigation-case>
    <navigation-case>
    <from-outcome>niepoprawne_dane</from-outcome>
    <to-view-id>/formularz.jsp</to-view-id>
    </navigation-case>
    </navigation-rule>
    </faces-config>
    Definiuje on plik zawierający komunikaty dopasowane do aktualnego języka, pojedyńczy komponent - formularz - odpowiedzialny za przechowywanie danych i dwie reguły nawigacyjne - poprawne_dane i niepoprawne_dane. Obie są jedynie nazwą dla docelowej strony jaka będzie wyświetlana, kiedy środowisko JSF otrzyma informację o przejściu z aktualnego widoku (strony) do odpowiadającemu nazwie widokowi.

  • Stworzenie pliku z komunikatami - Komunikaty_pl.properties

    Plik z komunikatami zawiera dane w postaci zgodnej z formatem wymaganym przez klasę java.util.Properties.

    wprowadz_imie = Wprowad\u017a swoje imi\u0119
    zatwierdz = Zatwierd\u017a
    witaj = Witaj
    ponownie = Jeszcze raz!
    tytul = Moja pierwsza aplikacja JSF

    javax.faces.component.UIInput.REQUIRED = Nie podano warto\u015bci
    Na uwagę zasługuje klucz javax.faces.component.UIInput.REQUIRED, który nadpisuje standardową wartość klucza w języku angielskim na polski odpowiednik. Pozostałe klucze są specyficzne dla aplikacji.

    Zapisz plik w katalogu WEB-INF/classes/pl/org/laskowski.

  • Utworzenie widoków (stron JSP) - formularz.jsp i witaj.jsp

    Strony (widoki) tworzące aplikację JSF są zwykłymi stronami JSP, które mogą zawierać specyficzne dla JSF znaczniki. W poniższych plikach wyróżnić można dwie główne biblioteki znaczników JSP - html i core. Obie dostarczają podstawowych komponentów wizualnych JSF.


    Pierwszy plik - formularz.jsp - prezentuje formularz, składający się z pojedyńczego pola tekstowego (h:inputText) i przycisku do zatwierdzenia formularza (h:commandButton). Można również zobaczyć wykorzystanie komunikatów z zewnętrznego pliku do ich lokalizacji (f:loadBundle).

    <%@ page contentType="text/html; charset=utf-8"%>

    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
    <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>

    <html>
    <f:view locale="pl">
    <f:loadBundle basename="pl.org.laskowski.i18n.Komunikaty" var="komunikaty"/>

    <head>
    <title><h:outputText value="#{komunikaty.tytul}"/></title>
    </head>
    <body>
    <h:form id="formularzForm1">
    <h:outputText value="#{komunikaty['wprowadz_imie']}: "/>
    <h:inputText id="imie" value="#{formularz.imie}" maxlength="10" size="25" required="true"/>
    <h:message for="formularzForm1:imie" styleClass="error" />
    <br/>
    <h:commandButton value="#{komunikaty.zatwierdz}" action="#{formularz.submit}"/>
    </h:form>
    </body>
    </f:view>
    </html>
    Druga strona - witaj.jsp - wyświetla powitanie z wykorzystaniem wartości parametru podanej w formularzu (h:outputText) oraz umożliwia powrót do strony początkowej (h:outputLink).
    <%@ page contentType="text/html; charset=utf-8"%>

    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
    <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%>

    <html>
    <body>
    <f:view>
    <f:loadBundle basename="pl.org.laskowski.i18n.Komunikaty" var="komunikaty"/>

    <h:outputText value="#{komunikaty['witaj']}"/>
    <h:outputText value="#{formularz.imie}"/>!
    <p/>
    <h:outputLink value="formularz.jsf" >
    <h:outputText value="#{komunikaty['ponownie']}"/>
    </h:outputLink>
    </f:view>
    </body>
    </html>

  • Utworzenie zarządzanego komponentu JSF - pl.org.laskowski.Formularz

    Stan pomiędzy widokami przechowywany jest w JSF przy pomocy komponentów zarządzanych (ang. managed beans). Nie jest to nic odkrywczego pamiętając, że podejście to jest wzorcem programistycznym - Model-View-Controller (MVC) - gdzie Modelem są właśnie klasy JavaBeans (nie mylić z Enterprise JavaBeans - EJB). Takie podejście znajdziemy w innych szkieletach programistycznych, które były prekursorem JavaServer Faces, np. Apache Struts.

    Komponenty zarządzane są klasami z metodami dostępowymi zgodnymi ze specyfikacją JavaBeans. Nie ma wymagania odnośnie implementacji klasy poza zgodnością ze specyfikacją JavaBeans, która, m.in. nakreśla sposób dostępu do danych za pomocą metod set (ang. setters) i get (ang. getters).

    package pl.org.laskowski;

    public class Formularz {

    private String imie;

    public String getImie() {
    return imie;
    }

    public void setImie(String email) {
    this.imie = email;
    }

    public String submit() {
    if ("Jacek".equals(getImie())) {
    return "poprawne_dane";
    }
    return "niepoprawne_dane";
    }
    }

    Skompilowana klasa pl.org.laskowski.Formularz powinna znajdować się w katalogu WEB-INF/classes/pl/org/laskowski.

  • Początkowy plik aplikacji - index.jsp

    Uruchomienie aplikacji internetowej w środowisku kontenera servletów przez użytkownika wiąże się z uruchomieniem pewnego servletu (JSP jest również servletem). Standardowo uruchamianą stroną w kontenerze servletów jest m.in. index.jsp. Ważnym krokiem zapewniającym uruchomienie strony startowej jest jej istnienie. Nie można zdefiniować strony startowej w deskryptorze aplikacji, jeśli plik nie istnieje fizycznie w aplikacji. Drugą ważną rzeczą jest, aby nasze strony, zawierające znaczniki JSF, były wywoływane przez FacesServlet, który zapewni poprawną konfigurację środowiska JSF. Z tych dwóch powodów konieczne jest posłużenie się pewnym rozwiązaniem - stroną JSP, która zostanie zadeklarowana w web.xml jako strona startowa aplikacji i która będzie przekierowywała użytkownika na właściwą stronę JSF - w naszym przypadku formularz.jsf.

    <%
    response.sendRedirect("formularz.jsf");
    %>
  • Uruchomienie aplikacji

    Po stworzeniu wszystkich w/w plików najwyższa pora zobaczyć naszą aplikację w akcji. Przekopiuj katalog mojapp do katalogu CATALINA_HOME/webapps i uruchom Tomcata. Pod adresem http://localhost:8080/mojapp znajduje się nasza aplikacja.

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

20 stycznia 2006

Wprowadzenie do Commons Configuration

4 komentarzy
Pod adresem http://jakarta.apache.org/commons/configuration znajduje się ciekawy projekt Commons Configuration do zarządzania konfiguracją aplikacji.

Istnieje wiele technik obsługi konfiguracji, ale to, co przykuło moją uwagę w Commons Configuration, to wyjątkowa prostota użycia. Żadnych wstępnych wymagań, ograniczeń i dodatkowo różnorodność źródeł skąd pobierane są dane konfiguracyjne - wszystko to sprawia, że korzystanie z projektu sprowadza się do następujących kroków:
  • Umieszczenia biblioteki Commons Configuration - commons-configuration-1.2.jar - do CLASSPATH. Oczywiście jak z każdym projektem z Jakarta Commons należy dodać jeszcze inne biblioteki zależne - Commons BeanUtils, Commons Collections, Commons Digester, Commons Lang i w końcu Commons Logging.
  • Stworzeniu pliku konfiguracyjnego - w XML, jako plik w formacie zgodnym z java.lang.Properties - bądź konfiguracja drzewa JNDI czy bazy danych. W rachubę wchodzi również nie definowanie niczego. Skorzystamy wtedy z konfiguracji podawanej z linii poleceń. Wybierzmy najpierw plik XML.
    <nieistotnyznacznik>
    <bazadanych typ="MySQL">
    <alias>test</alias>
    <uzytkownik>jlaskowski</uzytkownik>
    <haslo>********</haslo>
    </bazadanych>
    <bazadanych typ="Oracle">
    <alias>prod</alias>
    <uzytkownik>jacek.laskowski</uzytkownik>
    <haslo>inne</haslo>
    </bazadanych>
    </nieistotnyznacznik>
  • Następny krok to napisanie programu do wczytywania danych konfiguracyjnych - pl.org.laskowski.MyConfigurationService.
    package pl.org.laskowski;

    import org.apache.commons.configuration.Configuration;
    import org.apache.commons.configuration.ConfigurationException;
    import org.apache.commons.configuration.XMLConfiguration;

    public class MyConfigurationService {

    public static void main(String[] args) {
    try {
    Configuration cfg = new XMLConfiguration("config-service.xml");

    String dbTyp = cfg.getString("bazadanych[@typ]");
    String dbAlias = cfg.getString("bazadanych.alias");
    String dbUser = cfg.getString("bazadanych.uzytkownik");
    String dbPassword = cfg.getString("bazadanych.haslo");

    String dbUrl = dbUser + ":" + dbPassword + "@" + dbAlias;
    System.out.println("Bazy danych " + dbTyp + " o adresie " + dbUrl);
    } catch (ConfigurationException ce) {
    ce.printStackTrace();
    }
    }
    }
  • Uruchomienie programu.
    $ java pl.org.laskowski.MyConfigurationService
    Bazy danych MySQL o adresie jlaskowski:********@test
Pamiętajmy, że plik konfiguracyjny config-service.xml musi znajdować się w katalogu bieżącym, w którym uruchamiamy aplikację, bądź w katalogu, czy pliku zawartym w CLASSPATH.

Powyższe kroki można zastosować do dowolnej konfiguracji, jednakże przy zmianie z pliku XML na inne źródło należy pamiętać o zmianie klasy obsługującej źródło. Źródła danych dziedziczą z abstrakcyjnej klasy org.apache.commons.configuration.AbstractConfiguration i odnalezienie wszystkich standardowo dostępnych klas i ich miejsce składowania danych znajdziemy w sekcji Direct Known Subclasses.

Często stosowanym rozwiązaniem przy obsłudze plików konfiguracyjnych jest przesłanianie konfiguracji i łączenie wielu źródeł. Projekt Commons Configuration dostarcza klasę org.apache.commons.configuration.CompositeConfiguration, która została stworzona właśnie w tym celu - do łączenia źródeł danych i ustawiania ich znaczenia (ważności). Pierwsze wystąpienie poszukiwanej zmiennej ma najwyższy priorytet i dalsze źródła nie są odpytywane.

Zmodyfikujmy nasz przykład i stwórzmy nową klasę pl.org.laskowski.MyCompositeConfigurationService - tak, aby skorzystać z możliwości dostarczanych przez o.a.c.c.CompositeConfiguration, a właściwie o.a.c.c.ConfigurationFactory
package pl.org.laskowski;

import java.util.Iterator;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.ConfigurationFactory;

public class MyCompositeConfigurationService {

public static void main(String[] args) {
try {
ConfigurationFactory factory = new ConfigurationFactory("composite-config.xml");
Configuration cfg = factory.getConfiguration();

// Uruchom przykład z parametrem -Denv=prod
boolean prodEnv = "prod".equals(cfg.getString("env"));

String dbAlias;
String dbUser;
String dbPassword;
if (!prodEnv) {
// Środowisko testowe
dbAlias = cfg.getString("bazadanych(0).alias");
dbUser = cfg.getString("bazadanych(0).uzytkownik");
dbPassword = cfg.getString("bazadanych(0).haslo");
} else {
// Środowisko produkcyjne
dbAlias = cfg.getString("bazadanych(1).alias");
dbUser = cfg.getString("bazadanych(1).uzytkownik");
dbPassword = cfg.getString("bazadanych(1).haslo");
}

String dbUrl = dbUser + ":" + dbPassword + "@" + dbAlias;
System.out.println("Podłączam się do bazy danych " + dbUrl);

System.out.println("Wyświetlam dostępne klucze konfiguracyjne:");
Iterator it = cfg.getKeys();
while (it.hasNext()) {
System.out.println("\t" + it.next());
}
} catch (ConfigurationException ce) {
ce.printStackTrace();
}
}
}
W celu uruchomienia naszego przykładu konieczne jest zbudowanie nowego pliku konfiguracji wymaganego przez ConfigurationFactory - composite-config.xml - który bedzie instruował z jakich źródeł danych będzie korzystała nasza aplikacja.
<configuration>
<system/>
<xml fileName="config-service.xml"/>
</configuration>
Konfiguracja fabryki wymaga pliku o ustalonej strukturze (która nawiasem mówiąc jest obsługiwana przez inny projekt Commons Digester). Więcej informacji o strukturze znajdziesz na stronie projektu. Na początek znaczniki system i xml są wystarczające.

UWAGA: plik composite-config.xml musi znajdować się w katalogu bieżącym bądź w katalogu, czy pliku zawartym w CLASSPATH. Powyższy przykład łączy dwa źródła danych konfigurację podawaną na linii poleceń i z pliku XML.

Sercem działania złożonej konfiguracji Commons Configuration jest klasa-fabryka - org.apache.commons.configuration.ConfigurationFactory. Służy ona do tworzenia konfiguracji, która będzie odpytywała wszystkie zdefiniowane konfiguracje o parametr i wygrywa pierwsze wystapienie. Musimy zatem pamiętać o kolejności wpisów poszczególnych konfiguracji w pliku konfiguracyjnym dla ConfigurationFactory.

Uruchomienie przykładu demonstruje kolejne możliwości Commons Configuration:
$ java -Denv=prod pl.org.laskowski.MyCompositeConfigurationService

Podłączam się do bazy danych jacek.laskowski:inne@prod
Wyświetlam dostępne klucze konfiguracyjne:
java.runtime.name
sun.boot.library.path
java.vm.version
java.vm.vendor
java.vendor.url
path.separator
java.vm.name
file.encoding.pkg
env
user.country
sun.os.patch.level
java.vm.specification.name
user.dir
java.runtime.version
java.awt.graphicsenv
java.endorsed.dirs
os.arch
java.io.tmpdir
line.separator
java.vm.specification.vendor
user.variant
os.name
sun.jnu.encoding
java.library.path
java.specification.name
java.class.version
sun.management.compiler
os.version
user.home
user.timezone
java.awt.printerjob
file.encoding
java.specification.version
java.class.path
user.name
java.vm.specification.version
java.home
sun.arch.data.model
user.language
java.specification.vendor
awt.toolkit
java.vm.info
java.version
java.ext.dirs
sun.boot.class.path
java.vendor
file.separator
java.vendor.url.bug
sun.io.unicode.encoding
sun.cpu.endian
sun.desktop
sun.cpu.isalist
bazadanych[@typ]
bazadanych.alias
bazadanych.uzytkownik
bazadanych.haslo
Parametr env jest opcjonalny w naszej aplikacji. Jak widać wszystkie parametry systemowe JVM są również zawarte w konfiguracji poprzez wyspecyfikowanie <system/> w pliku konfiguracyjnym dla CompositeConfiguration.

Podczas uruchomienia klasy p.o.l.MyCompositeConfigurationService wyświetlona została pewna wartość bazadanych[@typ]. Jest to właśnie sposób na odwołanie się do atrybutów (w naszym przypadku typ) znacznika (u nas bazadanych).

Inną ciekawostką, której wykorzystanie można zobaczyć w przykładzie p.o.l.MyCompositeConfigurationService jest odwoływanie się do zmiennych występujących wielokrotnie w pliku konfiguracyjnym. W przykładzie sprawdza się wartość zmiennej env i w zależności od niej odwołujemy się do pierwszego wystąpienia sekcji bazadanych (bazadanych(0)) bądź kolejnego (bazadanych(1)). Numeracja zaczyna się od 0.

Komentarze, spostrzeżenia mile widziane. Skorzystaj z możliwości komentowania artykułu, poniżej.

17 stycznia 2006

"Portlets and Apache Portals" za darmo, ale po angielsku

0 komentarzy
Właśnie natrafiłem na ciekawą pozycję o portletach i Jetspeed2. Postaram się przybliżyć temat w kolejnych odsłonach, jednakże teraz ograniczę się jedynie do podania odnośnika dla niecierpliwych. Zajrzyj na http://www.manning.com/books/hepper. Na razie książka pójdzie na półkę...

13 stycznia 2006

W końcu się zdecydowałem!

5 komentarzy
Upubliczniam pomysł, który narodził się wiele miesięcy temu. Trudno znaleźć ciekawe i wysokiej klasy artykuły o projektach komercyjnych i darmowych w Javie i J2EE w rodzimym języku. Nie jest to dla wielu żadnym problemem, jednakże stanowi nie lada wyzwanie dla znaczącej grupy osób nie posługujących się językiem angielskim, bądź też dla tych, których pisanie w języku angielskim niepotrzebnie spowalnia. Zastanawia mnie niepokojąco niska aktywność polskich architektów i programistów Java/J2EE, a co za tym idzie niewielka liczba publikacji po polsku. Gazet o programowaniu w Javie jest niewiele, a ich zawartość nieciekawa, raczej monotonna. Postanowiłem to odrobinę zmienić! Zamierzam opisywać moje doświadczenia ze świata Javy i udoskonalać mój warsztat projektowy poprzez publiczną recenzję moich dokonań. Może pomysł spodoba się innym i zachęci ich do kolejnych publikacji.