blog.smart-java.nl
Ordina J-Technologies – Java Blog



Manage je Digesters met Spring

By: Michel.Schudel, 21 February 2008

Iedereen heeft wel eens een Java bean die op startup time van je applicatie geconfigureerd dient te worden. Het Spring framework biedt je uiteraard die mogelijkheid in de vorm van de IoC container, waarmee je met behulp van <bean> declaraties beans eenvoudig kan optuigen. Zelfs als de beanconfiguratie iets complexer wordt, kan je met <list>, <map>, <set> en geneste <bean> tags een aardig “boompje” opbouwen.

Wat lastiger wordt het als je meer xml-to-object achtige configuraties wil gaan toepassen waarbij de mapping van xml naar object(en) niet altijd triviaal of 1-op-1 is. Nu biedt Apache daar een prima oplossing voor in de vorm van Commons Digester, een component die een xml configuratie, aan de hand van een set van rules, je java objecten gaat instantieren. Voor de precieze werking van Digester kan je terecht bij de betreffende Apache site.

De logica om de Digester te initializeren zie ik nog steeds vaak in de code zelf staan. In dit artikeltje gaan we de initialisatie van Digesters en het parsen van input xmls geheel aan Spring uitbesteden.

We pakken een voorbeeldje waarin vanuit een xml structuur met films een java bean genaamd FilmCollectie wordt gemaakt met daarin een lijst van Film beans. Uiteraard zou dit eenvoudige voorbeeld ook met normale Spring bean configuratie kunnen, maar gaat gaat even om het idee dat we een Digester willen gebruiken.

In code wordt vaak onderstaande structuur gebruikt om een Digester te initialiseren en xml te parsen:

InputStream is = getClass().getResourceAsStream("movies.xml");
Digester digester = new Digester();
digester.addObjectCreate("mijnFilms", FilmCollectie.class);
digester.addObjectCreate("mijnFilms/film", Film.class);
digester.addBeanPropertySetter("mijnFilms/film/titel", "titel");
digester.addSetNext("mijnFilms/film", "addFilm");
FilmCollectie mijnCollectie = (FilmCollectie) digester.parse(is.getInputStream());

Het enige wat ik als developer wil gebruiken is de uiteindelijke FilmCollectie (die op de laatste regel beschikbaar komt). We willen nu Spring verantwoordelijk maken voor het initialiseren van de Digester en het parsen van de input xml.

In het voorbeeld definieren we eerst, voor de volledigheid, een client die gebruik gaat maken van de objecttree met films:

<bean id="movieManager" class="MovieManager">
  <property name="movies" ref="movies"/>
</bean>


We willen graag zowel de Digester rules als de input van het file system trekken. De input data zou er als volgt uit kunnen zien:

<mijnFilms>
<film>
<titel>Persepolis</titel>
</film>
<film>
<titel>American Gangster</titel>
</film>
</mijnFilms>

En de digester rules als volgt:

<digester-rules>
  <object-create-rule pattern="mijnFilms" classname="FilmCollectien"/>
  <object-create-rule pattern="mijnFilms/film" classname="Film"/>
  <property-setter-rule pattern="mijnFilms/film/titel" name="titel"/>
  <add-set-next-rule pattern="mijnFilms/film method="addFilm"/>
</digester-rules>

Nu heeft de Digester component een DigesterLoader factory aan boord die je in staat stelt met een factory method je gewenste bean plus onderliggende beans op te leveren:static Object load(URL digesterRules, ClassLoader classLoader, InputStream input)
Deze factory willen we graag gebruiken in Spring op de objecttree op te leveren.
Nu dient de Digester opgetuigd te worden. Omdat de DigesterLoader een factory is, dienen we die ook zo in Spring te configureren. Voor het gemak vullen we even een null is voor het tweede argument (de classloader).

<bean id="movies" factory-method="load" class="org.apache.commons.digester.xmlrules.DigesterLoader">
  <constructor-arg ref="...een url..."/>
  <constructor-arg><null/></constructor-arg> 
  <constructor-arg ref="...een inputStream..."/>
</bean>


Het probleem is nu: de load method verwacht een URL en een InputStream als constructor arguments van de factory method. Spring geeft je wel de mogelijkheid om Resources te definieren, die een eenduidige manier van omgaan met low-level resources implementeren.

We definieren twee resources voor de digester-rules en de xml input. De xml bestanden worden in dit voorbeeld van het classpath geladen.

<bean id="digesterRulesResource" class="org.springframework.core.io.ClassPathResource">
  <constructor-arg value="digester-rules.xml"/>
</bean>
<bean id="xmlInputResource" class="org.springframework.core.io.ClassPathResource">
  <constructor-arg value="movies.xml"/>
</bean>


We hebben nu twee Resources die de digester rules en input xml bestanden representeren. Hoe komen we nu aan een InputStream en een URL die voor de DigesterLoader nodig is?

Spring heeft een bean genaamd PropertyPathFactoryBean waarmee je een bean kan optuigen die eigenlijk een property van een andere bean is. Omdat een Resource de methods getURL() en getInputStream() heeft, kunnen we met behulp van deze Spring bean de URL en InputStream uit de Resources halen.

<bean id="xmlInputStream" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
  <property name="targetBeanName" value="xmlInputResource"/>
<property name="propertyPath" value="inputStream"/>
</bean>
<bean id="digesterRulesURL" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
  <property name="targetBeanName" value="digesterResource"/>
<property name="propertyPath" value="URL"/>
</bean>   


Het resultaat van bovenstaande declaratie is een bean met naam xmlInputStream van type InputStream en een bean met naam digesterURL met type URL. Deze beans kunnen nu geinjecteerd worden in de movies bean.

<bean id="movies" factory-method="load" class="org.apache.commons.digester.xmlrules.DigesterLoader">
  <constructor-arg ref="digesterRulesURL"/>
  <constructor-arg><null/></constructor-arg> 
  <constructor-arg ref="xmlInputStream"/>
</bean>


Het resultaat hiervan is een MovieManager die een property movies heeft van het type FilmCollectie. Dit object is gemaakt met behulp van de Digester zonder een letter Java code te schrijven voor het optuigen van de digester en het parsen van een input bestand. Met behulp van de PropertyPathFactoryBean kunnen we de InputStream en URL van elke willekeurige resource halen. De digester rules en input xml kunnen bijvoorbeeld ook in de applicationContext zelf worden opgenomen.

Verder zijn er natuurlijk mogelijkheden tot verkorte schrijfwijze van de PropertyFactoryBean waarvoor ik verwijs naar de Spring Appendix.

Eén reactie op “Manage je Digesters met Spring”

  1. Ron Thijssen zegt:

    Ik heb weer wat geleerd! erg handig :)

Laat een reactie achter