Michał Mech – W stronę Java

Zapiski programisty

Spring, JAXB oraz Maven – czyli łatwe i przyjemne budowanie oraz konfigurowanie aplikacji

brak komentarzy

Jakiś czas temu stanąłem przed koniecznością napisania aplikacji, która miała mieć możliwość konfigurowania z zewnętrznego pliku XML. Wybór formatu XML był podyktowany złożoną strukturą informacji, które należało wczytać. Aplikacja była oparta o szkielet Spring a zależnościami i budowaniem zajmował się Maven. Pozostał wybór narzędzia, które przeniesie mi w wygodny sposób dane z pliku XML do aplikacji. Wybór padł na JAXB – Java Architecture for XML Binding. JAXB to technologia pozwalająca wygenerować klasy języka Java, które pomogą nam odwzorować plik XML na obiekty.

Nie będę się skupiał nadto na opisie każdego z wymienionych narzędzi lecz skupię się raczej na pokazaniu jak zaprząc je razem do działania.

Przykładowa aplikacja

Zacznę od przygotowania niewielkiej aplikacji testowej. W tym celu w konsoli wpisujemy:

mvn archetype:generate

Powyższe polecenie zainicjuje kreator, w którym wybieramy 15 (maven-archetype-quickstart) a później podajemy szczegóły. Ja podałem:

Define value for groupId: : pl.michalmech
Define value for artifactId: : jaxb-example
Define value for version:  1.0-SNAPSHOT: :
Define value for package:  pl.michalmech: :

Aplikacja testowa jest gotowa a po wykonaniu polecenia:

mvn eclipse:eclipse

można wygodnie zaimportować ją do Eclipse‘a.

Kolejnym krokiem jest konfiguracja zależności aplikacji w pliku POM. Jedyne czego potrzebujemy to Spring dlatego wyrzuciłem zależność od JUnit‘a oraz folder src/test.

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>pl.michalmech</groupId>
    <artifactId>jaxb-example</artifactId>
    <packaging>jar</packaging>
    <name>jaxb-example</name>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
            <version>2.5.6.SEC01</version>
        </dependency>
    </dependencies>
</project>

Teraz czas na skonfigurowanie i wczytanie kontekstu Springa. Na razie pustego ale niebawem wypełnimy go ziarnami. Najpierw przygotowujemy plik src/main/resources/context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

</beans>

Następnie zmieniamy klasę App:

public class App {
    public static void main(String[] args) {
        @SuppressWarnings("unused")
        ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
    }
}

Należy pamiętać o

mvn eclipse:eclipse

żeby Eclipse odświeżył biblioteki po zmianie pliku pom.xml. Próba uruchomiania (Run as Java Application) aplikacji nie powinna sprawić problemu.

Schemat XML

Założyłem, że aplikacja ma korzystać z klas wygenerowanych za pomocą JAXB‘a. Potrzebujemy więc schematu XML, na podstawie którego klasy zostaną wygenerowane. Dla przykładu skorzystamy z takiego schematu:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://michalmech.pl/jaxb-example"
    xmlns:tns="http://michalmech.pl/jaxb-example"
    elementFormDefault="qualified">

    <element name="config">
        <complexType>
            <sequence>
                <element name="parameters">
                    <complexType>
                        <sequence minOccurs="0" maxOccurs="unbounded">
                            <element name="parameter" type="tns:parameter" />
                        </sequence>
                    </complexType>
                </element>
                <element name="cities">
                    <complexType>
                        <sequence minOccurs="0" maxOccurs="unbounded">
                            <element name="city" type="tns:city" />
                        </sequence>
                    </complexType>
                </element>
            </sequence>
        </complexType>
    </element>

    <complexType name="parameter">
        <attribute name="name" use="required" type="string" />
        <attribute name="value" use="required" type="string" />
    </complexType>

    <complexType name="city">
        <attribute name="name" use="required" type="string" />
        <attribute name="postCode" use="required" type="tns:postCode" />
    </complexType>

    <simpleType name="postCode">
        <restriction base="string">
            <pattern value="[0-9]{2}-[0-9]{3}" />
        </restriction>
    </simpleType>
</schema>

Zapisujemy plik pod nazwą config.xsd w src/main/resources. W dużym skrócie powyższy schemat opisuje plik XML, w którym spodziewamy się węzła zawierającego parametry oraz węzła zawierającego miasta. Parametr to element posiadający atrybut name oraz value natomiast miasto to element posiadający atrybut name oraz postCode. Atrybut postCode jest dodatkowo ograniczony wzorcem wyrażenia regularnego.

Chcielibyśmy aby oczekiwane klasy, opisujące schemat config.xsd zostały wygenerowane w locie podczas budowania aplikacji. Skorzystamy w tym celu z pluginu do Mavena. Zmieniamy plik pom.xml dodając do niego poniższą konfigurację budowania projektu:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.5</source>
                <target>1.5</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxb2-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>xjc</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <packageName>${project.groupId}.config</packageName>
                <schemaDirectory>${basedir}/src/main/resources</schemaDirectory>
                <outputDirectory>${basedir}/src/main/generated-resources</outputDirectory>
            </configuration>
        </plugin>
    </plugins>
</build>

jaxb2-maven-plugin wyszuka wszystkie schematy plików XML znajdujące się w lokalizacji wskazanej przez schemaDirectory a następnie wygeneruje klasy umieszczając je w paczce pl.michalmech.config oraz zapisze w lokalizacji wskazanej przez outputDirectory.
Użycie pluginu maven-compiler-plugin jest wymagane ponieważ JAXB korzysta z adnotacji więc kompilacja musi odbyć się w Javie, w wersji nie niższej niż 1.5.

No to do dzieła! Wykonujemy polecenie:

mvn clean compile eclipse:clean eclipse:eclipse

oraz odświeżamy projekt w IDE. Zawartość projektu powinna prezentować się teraz jak na załączonym poniżej obrazku.

projekt jaxb-example

Konfiguracja kontekstu

Nasza aplikacja kompiluje się i uruchamia, nadal jednak żadnej konfiguracji nie wczytujemy. Do tego przede wszystkim potrzebujemy pliku XML zgodnego z utworzonym wcześniej schematem. Takiego jak ten:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://michalmech.pl/jaxb-example"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://michalmech.pl/jaxb-example config.xsd">

    <parameters>
        <parameter name="jakiś" value="parametr" />
        <parameter name="inna" value="opcja" />
    </parameters>

    <cities>
        <city name="Warszawa" postCode="03-481" />
        <city name="Łęczna" postCode="21-010" />
    </cities>
</config>

Umieszczamy go, tak jak pozostałe zasoby, w src/main/resources i nadajemy nazwę config.xml. Aby mieć pewność, że podczas budowania aplikacji dostarczany plik konfiguracyjny jest poprawny dołożymy jeszcze jeden plugin do POM‘a:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>xml-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>validate</goal>
            </goals>
            <phase>validate</phase>
        </execution>
    </executions>
    <configuration>
        <validationSets>
            <validationSet>
                <dir>${basedir}/src/main/resources</dir>
                <includes>
                    <include>config.xml</include>
                </includes>
                <systemId>${basedir}/src/main/resources/config.xsd</systemId>
                <schemaLanguage>http://www.w3.org/2001/XMLSchema</schemaLanguage>
                <validating>true</validating>
            </validationSet>
        </validationSets>
    </configuration>
</plugin>

Plugin ten zweryfikuje czy dostarczany plik XML jest zgodny ze schematem.

Do wczytania konfiguracji pozostało już bardzo niewiele. Posłużymy się narzędziami znajdującymi się w pakiecie org.springframework.oxm. W tym celu poprosimy Mavena o dostarczenie nam wymaganych źródeł dopisując odpowiednie zależności w pliku pom.xml:

<dependency>
    <groupId>org.springframework.ws</groupId>
    <artifactId>spring-ws</artifactId>
    <version>1.5.2</version>
</dependency>

Teraz zajmiemy się kontekstem aplikacji. Do tej pory wszystko za mnie robiło jakieś narzędzie czy biblioteka. Nie będę burzył tego porządku i inicjacją odpowiednich obiektów oraz wczytaniem zawartości pliku config.xml do aplikacji zajmie się Spring. Aby to zrobił, konieczna jest ingerencja w deskryptor kontekstu context.xml. Zmieniamy jego zawartość na:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="schema" value="classpath:config.xsd" />
        <property name="classesToBeBound">
            <list>
                <value>pl.michalmech.config.Config</value>
            </list>
        </property>
    </bean>

    <bean id="config" class="pl.michalmech.config.Config" factory-bean="jaxb2Marshaller" factory-method="unmarshal">
        <constructor-arg>
            <bean class="javax.xml.transform.stream.StreamSource">
                <constructor-arg>
                    <bean class="java.io.FileInputStream" factory-bean="configClasspathResource" factory-method="getInputStream" />
                </constructor-arg>
            </bean>
        </constructor-arg>
    </bean>

    <bean id="configClasspathResource" class="org.springframework.core.io.ClassPathResource">
        <constructor-arg value="config.xml" />
    </bean>
</beans>

Obiektem klasy ClassPathResource posłużymy się jak fabryką po to by wydobyć strumień zawierający zawartość pliku config.xml. Strumień tenże zostanie użyty w obiekcie fabrycznym Jaxb2Marshaller do naniesienia zawartości pliku config.xml na obiekt klasy Config.

Ostatnią czynnością jest pobranie obiektu Config z kontekstu i wyświetlenie przykładowych danych dostarczonych za pomocą pliku XML. Zmodyfikujmy więc aplikację do postaci:

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");

        Config config = (Config) context.getBean("config");

        for (Parameter parameter : config.getParameters().getParameter()) {
            System.out.println(parameter.getName() + ":" + parameter.getValue());
        }

        for (City city : config.getCities().getCity()) {
            System.out.println(city.getName() + " - " + city.getPostCode());
        }
    }
}

Uruchomienie aplikacji (Run as Java Application) da następujący wynik w konsoli:

jakiś:parametr
inna:opcja
Warszawa - 03-481
Łęczna - 21-010

Podsumowanie

To wszystko co chciałem przekazać. Nie zagłębiałem się w szczegóły każdego z użytych narzędzi bo wpis musiałby być dużo dłuższy. Zależało mi na tym by podać gotowy przepis na użycie JAXB‘a w projekcie opartym na szkielecie Spring. Poniżej zamieszczam źródła projektu, które przydadzą się do samodzielnego uruchomienia aplikacji.

Do pobrania: jaxb-example.zip

Po pobraniu najwygodniej jest wykonać:

mvn compile eclipse:eclipse

oraz zaimportować projekt do Eclipse‘a.

Opublikowany przez Michał Mech

2009-09-30 o 00:19:00

Kategorie Java, Programowanie

Tagi , , , ,