2. Artikel 1 - Spring IoC
Doelstellingen van dit document:
- de configuratie- en integratiemogelijkheden van het Spring-framework (http://www.springframework.org) verkennen
- het begrip IoC (Inversion of Control), ook wel afhankelijkheidsinjectie (Dependency Injection) genoemd, definiëren en gebruiken
2.1. Een 3-tier-applicatie configureren met Spring
Laten we eens kijken naar een klassieke 3-tier-applicatie:
![]() |
We gaan ervan uit dat de toegang tot de bedrijfslaag en DAO wordt geregeld door Java-interfaces:
- de interface [IArticlesDao] voor de gegevenslaag
- de interface [IArticlesManager] voor de bedrijfslaag
In de gegevenslaag of DAO-laag (Data Access Object) wordt vaak gewerkt met een SGBD en dus met een driver JDBC. Laten we eens kijken naar het raamwerk van een klasse die toegang biedt tot een artikeltabel in een SGBD:
Om een bewerking uit te voeren op de SGBD, heeft elke methode een [Connection]-object nodig dat de verbinding met de database vertegenwoordigt, waarlangs de uitwisselingen tussen de database en de Java-code zullen verlopen. Om dit object aan te maken, zijn vier gegevens nodig:
de naam van de klasse van de driver JDBC van de SGBD | |
de URL JDBC van de te gebruiken database | |
de identiteit waarmee de verbinding wordt aangemaakt | |
het wachtwoord van deze identiteit |
Hoe kan onze eerdere klasse [ArticlesDaoPlainJdbc] deze gegevens verkrijgen? Er zijn verschillende mogelijkheden:
oplossing 1 – de gegevens zijn hard gecodeerd in de klasse:
Het nadeel van deze oplossing is dat de Java-code moet worden aangepast zodra deze gegevens worden gewijzigd, bijvoorbeeld bij het wijzigen van het wachtwoord.
Oplossing 2 – de gegevens worden bij het aanmaken van het object doorgegeven:
Hier ontvangt het object bij het aanmaken de gegevens die het nodig heeft om te functioneren. Het probleem verschuift dan naar de code die de vier gegevens heeft doorgegeven. Hoe heeft deze de gegevens verkregen? De volgende klasse [ArticlesManagerWithDataBase] uit de bedrijfslaag zou een object [ArticlesDaoPlainJdbc] uit de gegevenslaag kunnen aanmaken:
![]() |
We zien dat ook hier de informatie die nodig is voor het aanmaken van het object [ArticlesDaoPlainJdbc] wordt verstrekt aan de constructor van het object [ArticlesManagerWithDataBase]. We kunnen ons voorstellen dat deze informatie wordt doorgegeven door een hogere laag, zoals de gebruikersinterface-laag. Zo komen we stap voor stap bij de bovenste laag van de applicatie. Vanwege haar positie wordt deze laag niet aangeroepen door een laag die haar de configuratie-informatie zou kunnen doorgeven die ze nodig heeft. Er moet dus een andere oplossing worden gevonden dan configuratie via de constructor. De gebruikelijke oplossing om een applicatie op het niveau van de bovenste laag te configureren, is het gebruik van een bestand waarin alle informatie staat die in de loop van de tijd kan veranderen. Er kunnen meerdere van dergelijke bestanden zijn. Bij het opstarten van de applicatie zal een initialisatielaag dan alle of een deel van de objecten aanmaken die nodig zijn voor de verschillende lagen van de applicatie.
Er bestaat een grote verscheidenheid aan configuratiebestanden. De huidige trend is het gebruik van XML-bestanden. Dit is de keuze die Spring heeft gemaakt. Het configuratiebestand voor een [ArticlesDaoPlainJdbc]-object zou er als volgt uit kunnen zien:
Een applicatie is een verzameling objecten die Spring ‘beans’ noemt, omdat ze voldoen aan de JavaBean-standaard voor het benoemen van accessors en initializers (getters/setters) van de privévelden van een object. Objecten die in een applicatie tot taak hebben een dienst te verlenen, worden vaak in één enkel exemplaar aangemaakt. Deze worden singletons genoemd. Zo zal in ons hier besproken voorbeeld van een meerlaagse applicatie de toegang tot de artikeldatabase worden verzorgd door één enkel exemplaar van de klasse [ArticlesDaoPlainJdbc]. Bij een webapplicatie bedienen deze serviceobjecten meerdere clients tegelijk. Er wordt niet per client een serviceobject aangemaakt.
Met het bovenstaande Spring-configuratiebestand kan één enkel serviceobject van het type [ArticlesDaoPlainJdbc] worden aangemaakt in een pakket met de naam [istia.st.articles.dao]. De vier gegevens die nodig zijn voor de constructor van dit object worden gedefinieerd binnen een <bean>...</bean>-tag. Er zijn evenveel van dergelijke <bean>-tags als er singletons moeten worden aangemaakt.
Op welk moment vindt de aanmaak van de in het Spring-bestand gedefinieerde objecten plaats? Het initialiseren van een applicatie kan worden toevertrouwd aan de methode main van diezelfde applicatie, indien deze er een heeft. Voor een webapplicatie kan dit de methode [init] van de hoofd-servlet zijn. In elke applicatie is er een methode die gegarandeerd als eerste wordt uitgevoerd. Meestal vindt het aanmaken van de singletons in deze methode plaats.
Laten we een voorbeeld nemen. Stel dat we de eerder genoemde klasse [ArticlesDaoPlainJdbc] willen testen met behulp van een test JUnit. Een testklasse JUnit heeft een methode [setUp] die vóór alle andere methoden wordt uitgevoerd. Daar wordt het singleton [ArticlesDaoPlainJdbc] aangemaakt.
Als we de oplossing volgen waarbij de configuratiegegevens via de constructor worden doorgegeven, krijgen we de volgende testklasse:
De aanroepende klasse [TestArticlesPlainJdbc] moet de vier gegevens kennen die nodig zijn voor het initialiseren van het te construeren singleton [ArticlesDaoPlainJdbc].
Als we de oplossing volgen waarbij configuratie-informatie via een configuratiebestand wordt doorgegeven, zouden we de volgende testklasse kunnen hebben door gebruik te maken van het hierboven beschreven Spring-bestand.
Hier hoeft de aanroepende klasse [TestSpringArticlesPlainJdbc] de gegevens die nodig zijn voor het initialiseren van het te bouwen singleton niet te kennen. Ze hoeft alleen het volgende te weten:
- [springArticlesPlainJdbc.xml]: de naam van het hierboven beschreven Spring-configuratiebestand
- [articlesDao]: de naam van het te maken singleton
Een wijziging in het configuratiebestand, buiten deze twee entiteiten om, heeft geen invloed op de Java-code. Deze methode voor het configureren van objecten in een applicatie is zeer flexibel. Om zichzelf te configureren, hoeft de applicatie slechts twee dingen te weten:
- de naam van het Spring-bestand dat de definitie bevat van de te bouwen singletons
- de namen van deze singletons, die door de Java-code worden gebruikt om via het configuratiebestand een verwijzing te verkrijgen naar de objecten waaraan ze zijn gekoppeld
2.2. Afhankelijkheidsinjectie en omkering van controle
Laten we nu het begrip afhankelijkheidsinjectie (Dependency Injection) introduceren dat door Spring wordt gebruikt om applicaties te configureren. Men gebruikt ook de term omkering van controle (IoC, Inversion of Control). Laten we eens kijken naar het aanmaken van het singleton [ArticlesManagerWithDataBase] uit de businesslaag van onze applicatie:
![]() |
Om toegang te krijgen tot de gegevens van SGBD, moet de businesslaag gebruikmaken van de diensten van een object dat de interface [IArticlesDao] implementeert, bijvoorbeeld een object van het type [ArticlesDaoPlainJdbc]. De code van de klasse [ArticlesManagerWithDataBase] zou er als volgt uit kunnen zien:
public class ArticlesManagerWithDataBase implements IArticlesManager {
// een instantie van de gegevenstoegang
private IArticlesDao articlesDao;
....
public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
...
// aanmaken van de gegevenstoegangsdienst
articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
...
}
public ... doSomething(...){
...
}
}
De klasse [ArticlesDaoPlainJdbc] wordt hier verondersteld een interface [IArticlesDao] te implementeren:
Om het singleton van het type [IArticlesDao] te maken dat nodig is voor de werking van de klasse, gebruikt de constructor ervan expliciet de naam van de implementatieklasse van de interface [IArticlesDao]:
Er is dus een harde afhankelijkheid in de code van de klassenaam. Als de implementatieklasse van de interface [IArticlesDao] zou veranderen, zou de code van de bovenstaande constructor moeten worden aangepast. Er zijn de volgende relaties tussen de objecten:
![]() |
De klasse [ArticlesManagerWithDataBase] neemt zelf het initiatief om het object [ArticlesDaoPlainJdbc] aan te maken dat zij nodig heeft. Om terug te komen op de term „omkering van controle”: we zouden kunnen zeggen dat zij de „controle” heeft om het object aan te maken dat zij nodig heeft.
Als we een testklasse JUnit voor de klasse [ArticlesManagerWithDataBase] zouden moeten schrijven, zou dat er ongeveer als volgt uitzien:
De testklasse maakt een instantie aan van de businessklasse [ArticlesManagerWithDataBase], die op haar beurt in haar constructor een instantie aanmaakt van de data-accessklasse [ArticlesDaoPlainJdbc].
De oplossing met Spring maakt het overbodig dat de businessklasse [ArticlesManagerWithDataBase] de naam [ArticlesDaoPlainJdbc] van de benodigde data-accessklasse kent. Hierdoor kan deze worden gewijzigd zonder de Java-code van de businessklasse aan te raken. Met Spring kunnen beide singletons tegelijk worden aangemaakt: die van de gegevenslaag en die van de businesslaag. In het configuratiebestand van Spring wordt een nieuwe bean gedefinieerd:
Het nieuwe element is de bean die het te creëren singleton van de businessklasse definieert:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
- de klasse die de bean [articlesManager] implementeert, is gedefinieerd: [ArticlesManagerWithDataBase]
- het veld [articlesDao] van de bean krijgt een waarde via de tag <property name="articlesDao">. Dit is het veld dat is gedefinieerd in de klasse [ArticlesManagerWithDataBase]:
Om ervoor te zorgen dat het veld [articlesDao] door Spring en de bijbehorende tag <property> kan worden geïnitialiseerd, moet het veld voldoen aan de norm JavaBean en moet er een methode [setArticlesDao] bestaan om het veld [articlesDao] te initialiseren. Let op de naam van de methode, die op een zeer precieze manier is afgeleid van de naam van het veld. Tegelijkertijd bestaat er vaak een methode [get...] om de waarde van het veld op te halen. Hier is dat de methode [getArticlesDao]. In deze nieuwe versie heeft de klasse [ArticlesManagerWithDataBase] geen constructor meer. Die is niet langer nodig.
- De waarde die door Spring aan het veld [articlesDao] wordt toegewezen, is die van de bean [articlesDao] die in het configuratiebestand is gedefinieerd:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
<bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
<constructor-arg index="0">
.............
</bean>
- wanneer Spring het singleton [ArticlesManagerWithDataBase] aanmaakt, zal het ook het singleton [ArticlesDaoPlainJdbc] moeten aanmaken:
- Spring stelt een afhankelijkheidsgrafiek van de beans op en ziet dat de bean [articlesManager] afhankelijk is van de bean [articlesDao]
- het zal de bean [articlesDao] aanmaken, dus een object van het type [ArticlesDaoPlainJdbc]
- vervolgens zal het de bean [articlesManager] van het type [ArticlesManagerWithDataBase] aanmaken
Laten we nu eens een test JUnit voor de klasse [ArticlesManagerWithDataBase] voorstellen. Deze zou er als volgt uit kunnen zien:
Laten we eens kijken hoe de twee singletons worden aangemaakt die zijn gedefinieerd in het Spring-bestand met de naam [springArticlesManagerWithDataBase.xml].
- De bovenstaande methode [setUp] vraagt om een verwijzing naar de bean met de naam [articlesManager]
- Spring raadpleegt zijn configuratiebestand en vindt de bean [articlesManager]. Als deze al is aangemaakt, geeft Spring gewoon een verwijzing naar het object (singleton) terug; anders maakt Spring de bean aan.
- Spring ziet de afhankelijkheid van de bean [articlesManager] ten opzichte van de bean [articlesDao]. Het maakt daarom de singleton [articlesDao] van het type [ArticlesDaoPlainJdbc] aan, als deze nog niet bestaat (singleton).
- Het maakt de singleton [articlesManager] van het type [ArticlesManagerWithDataBase] aan
Dit mechanisme kan als volgt worden weergegeven:
![]() |
Laten we nog eens kijken naar de structuur van de klasse [ArticlesManagerWithDataBase]:
Aan het einde van de aanmaak van de singletons door Spring hebben we een object van het type [ArticlesManagerWithDataBase] waarvan het veld [articlesDao] is geïnitialiseerd zonder dat het weet hoe. We zeggen dat er een afhankelijkheid is geïnjecteerd in het object [ArticlesManagerWithDataBase]. Men zegt ook dat de controle is omgekeerd: het is niet langer het object [ArticlesManagerWithDataBase] dat het initiatief neemt om zelf het object aan te maken dat de interface [IArticlesDao] implementeert en dat het nodig heeft, maar de applicatie op het hoogste niveau (bij het opstarten) zorgt ervoor dat alle objecten worden aangemaakt die zij nodig heeft, waarbij de onderlinge afhankelijkheden tussen deze objecten worden beheerd.
Het belangrijkste voordeel van het configureren van de singleton [ArticlesManagerWithDataBase] via een Spring-bestand is dat we nu de implementatieklasse die overeenkomt met het veld [articlesDao] van de klasse [ArticlesManagerWithDataBase] kunnen wijzigen zonder dat de code van deze klasse hoeft te worden aangepast. Het volstaat om de naam van de klasse in de definitie van de bean [articlesDao] in het Spring-bestand te wijzigen:
wordt dan bijvoorbeeld:
De bean [ArticlesManagerWithDataBase] zal met deze nieuwe klasse voor gegevenstoegang werken, zonder dat hij het zelf doorheeft.
2.3. Spring IoC in de praktijk
2.3.1. Voorbeeld 1
Laten we eens kijken naar de volgende klasse:
De klasse bevat:
- twee privévelden: naam en leeftijd
- de lees- (get) en schrijfmethoden (set) voor deze twee velden
- een methode toString om de waarde van het object [Personne] op te halen in de vorm van een tekenreeks
- een methode `init` die door Spring wordt aangeroepen bij het aanmaken van het object, en een methode `close` die wordt aangeroepen bij het vernietigen van het object
Om objecten van het type [Personne] aan te maken, gebruiken we het volgende Spring-bestand:
Dit bestand krijgt de naam config.xml.
- Het definieert twee beans met respectievelijk de sleutels "persoon1" en "persoon2" van het type [Personne]
- Het initialiseert de velden [nom, age] van elke persoon
- het definieert de methoden die moeten worden aangeroepen bij de initiële constructie van het object [init-method] en bij de vernietiging van het object [destroy-method]
Voor onze tests gebruiken we één enkele testklasse JUnit waaraan we achtereenvolgens methoden zullen toevoegen. De eerste versie van deze klasse ziet er als volgt uit:
Opmerkingen:
- om de beans op te halen die zijn gedefinieerd in het bestand [config.xml], gebruiken we een object van het type [ListableBeanFactory]. Er bestaan andere objecttypen waarmee toegang tot de beans kan worden verkregen. Het object [ListableBeanFactory] wordt verkregen in de methode [setUp] van de testklasse en opgeslagen in een privévariabele. Het zal zo beschikbaar zijn voor alle testmethoden.
- Het bestand [config.xml] wordt in de [ClassPath] van de applicatie, c.a.d, geplaatst. Dit gebeurt in een van de mappen die door de Java-virtuele machine worden doorzocht wanneer deze op zoek is naar een klasse waarnaar door de applicatie wordt verwezen. Het object [ClassPathResource] wordt gebruikt om een bron te zoeken in het [ClassPath] van een applicatie, in dit geval het bestand [config.xml].
- Spring kan configuratiebestanden in verschillende formaten gebruiken. Met het object [XmlBeanFactory] kan een configuratiebestand in het formaat XML worden geparseerd.
- Het verwerken van een Spring-bestand levert een object van het type [ListableBeanFactory] op, in dit geval het object bf. Met dit object kan een bean, geïdentificeerd door de sleutel C, worden opgehaald via bf.getBean(C).
- De methode [test1] vraagt de waarde op van de beans met de sleutels "personne1" en "personne2" en geeft deze weer.
De structuur van het Eclipse-project van onze applicatie is als volgt:

Opmerkingen:
- de map [src] bevat de broncodes. De gecompileerde codes komen in een map [bin] die hier niet wordt weergegeven.
- Het bestand [config.xml] bevindt zich in de hoofdmap van de map [src]. Tijdens het bouwen van het project wordt het automatisch gekopieerd naar de map [bin], die deel uitmaakt van de map [ClassPath] van de applicatie. Daar wordt het gezocht door het object [ClassPathResource].
- De map [lib] bevat drie Java-bibliotheken die nodig zijn voor de applicatie:
- commons-logging.jar en spring-core.jar voor de Spring-klassen
- junit.jar voor de klassen JUnit
- de map [lib] maakt eveneens deel uit van de map [ClassPath] van de applicatie
Het uitvoeren van de methode [test1] van de test JUnit levert de volgende resultaten op:
Opmerkingen:
- Spring registreert een aantal gebeurtenissen via de bibliotheek [commons-logging.jar]. Deze logbestanden geven ons meer inzicht in de werking van Spring.
- Het bestand [config.xml] is geladen en vervolgens verwerkt
- de bewerking*
heeft de aanmaak van de bean [personne1] geforceerd. We zien hierover het logboek van Spring. Omdat in de definitie van de bean [personne1] per ongeluk [init-method="init"] was geschreven, werd de methode [init] van het aangemaakte object [Personne] uitgevoerd. Het bijbehorende bericht wordt weergegeven.
- De bewerking
heeft de waarde van het aangemaakte object [Personne] weergegeven.
- Hetzelfde gebeurt opnieuw voor de sleutel-bean [personne2].
- De laatste bewerking
personne2 = (Personne) bf.getBean("personne2");
System.out.println("personne2=" + personne2.toString());
heeft niet geleid tot het aanmaken van een nieuw object van het type [Personne]. Als dat wel het geval was geweest, zou de methode [init] zijn weergegeven, wat hier niet het geval is. Dit is het principe van de singleton. Spring maakt standaard slechts één exemplaar aan van de beans uit zijn configuratiebestand. Het is een objectreferentieservice. Als er om de referentie van een nog niet aangemaakt object wordt gevraagd, maakt Spring het aan en geeft het een referentie terug. Als het object al is aangemaakt, geeft Spring alleen een referentie terug.
- We kunnen opmerken dat er geen spoor te bekennen is van de methode [close] van het object [Personne], terwijl we deze wel hadden opgenomen in de definitie van de bean [destroy-method=close]. Het is mogelijk dat deze methode pas wordt uitgevoerd wanneer het geheugen dat door het object wordt ingenomen, wordt vrijgemaakt door de garbage collector. Op het moment dat dit gebeurt, is de applicatie al beëindigd en heeft het weergeven op het scherm geen effect. Dit moet nog worden gecontroleerd.
Nu we de basis van een Spring-configuratie onder de knie hebben, zullen we voortaan wat sneller door de uitleg heen gaan.
2.3.2. Voorbeeld 2
Laten we eens kijken naar de volgende nieuwe klasse [Voiture]:
De klasse bevat:
- drie privévelden: type, merk en eigenaar. Deze velden kunnen worden geïnitialiseerd en uitgelezen via de openbare get- en set-methoden van de bean. Ze kunnen ook worden geïnitialiseerd met behulp van de constructor Auto(String, String, Persoon). De klasse beschikt tevens over een constructor zonder argumenten om te voldoen aan de norm JavaBean.
- een methode toString om de waarde van het object [Voiture] op te halen in de vorm van een tekenreeks
- een methode `init` die door Spring wordt aangeroepen direct na het aanmaken van het object, en een methode `close` die wordt aangeroepen bij het vernietigen van het object
Om objecten van het type [Voiture] aan te maken, gebruiken we het volgende Spring-bestand [config.xml]:
Dit bestand voegt aan de eerdere definities een bean toe met de sleutel "auto1" van het type [Voiture]. Om deze bean te initialiseren, had men het volgende kunnen schrijven:
In plaats van deze reeds besproken methode te kiezen, hebben we hier gekozen voor de constructor Voiture(String, String, Personne) van de klasse. Bovendien definieert de bean [voiture1] de methode die moet worden aangeroepen bij de initiële constructie van het object [init-method] en de methode die moet worden aangeroepen bij de vernietiging van het object [destroy-method].
Voor onze tests gebruiken we de reeds geïntroduceerde testklasse JUnit, waaraan we de volgende methode [test2] toevoegen:
De methode [test2] haalt de bean [voiture1] op en geeft deze weer.
De structuur van het Eclipse-project blijft hetzelfde als in de vorige test. Het uitvoeren van de methode [test2] uit de test JUnit levert de volgende resultaten op:
Opmerkingen:
- de methode [test2] vraagt een verwijzing naar de bean [voiture1]
- regel 4: Spring begint met het aanmaken van de bean [voiture1] omdat deze bean nog niet is aangemaakt (singleton)
- regel 6: omdat de bean [voiture1] verwijst naar de bean [personne2], wordt deze laatste bean op zijn beurt aangemaakt
- regel 7: de bean [personne2] is aangemaakt. De bijbehorende methode [init] wordt vervolgens uitgevoerd.
- regel 9: Spring geeft aan dat het een constructor gaat gebruiken om de bean [voiture1] aan te maken
- regel 10: de bean [voiture1] is aangemaakt. De bijbehorende methode [init] wordt vervolgens uitgevoerd.
- regel 11: de methode [test2] geeft de waarde van de bean [voiture1] weer
2.3.3. Voorbeeld 3
We introduceren de volgende nieuwe klasse [GroupePersonnes]:
De twee privé-leden ervan zijn:
leden: een array met personen die lid zijn van de groep
groupesDeTravail: een woordenboek dat een persoon aan een werkgroep toewijst
Hier valt op dat de klasse [GroupePersonnes] geen constructor zonder argumenten definieert, in overeenstemming met de norm JavaBean. Ter herinnering: bij het ontbreken van een constructor bestaat er een "standaard" constructor, namelijk de constructor zonder argumenten, die niets doet.
We willen hier laten zien hoe Spring het mogelijk maakt om complexe objecten te initialiseren, zoals objecten met velden van het type array of woordenboek. We voegen een nieuwe bean toe aan het vorige Spring-bestand [config.xml]:
- Met de tag <list> kun je een veld van het type array of een veld dat de interface List implementeert, initialiseren met verschillende waarden.
- Met de tag <map> kun je hetzelfde doen met een veld dat de interface Map implementeert
Voor onze tests gebruiken we de reeds gepresenteerde testklasse JUnit, waaraan we de volgende methode [test3] toevoegen:
De methode [test3] haalt de bean [groupe1] op en geeft deze weer.
De structuur van het Eclipse-project blijft hetzelfde als in de vorige test. Het uitvoeren van de methode [test3] uit de test JUnit levert de volgende resultaten op:
Opmerkingen:
- de methode [test3] vraagt om een verwijzing naar de bean [groupe1]
- regel 4: Spring begint met het aanmaken van deze bean
- omdat de bean [groupe1] verwijst naar de beans [personne1] en [personne2], worden deze twee beans aangemaakt (regels 6 en 9) en wordt hun init-methode uitgevoerd (regels 7 en 10)
- regel 11: de bean [groupe1] is aangemaakt. De methode [init] ervan wordt nu uitgevoerd.
- regel 12: weergave aangevraagd door de methode [test3].
2.4. Spring voor het configureren van drielaagse webapplicaties
2.4.1. Algemene architectuur van de applicatie
We willen een drielaagse applicatie bouwen met de volgende structuur:
![]() |
- de drie lagen worden onafhankelijk gemaakt door het gebruik van Java-interfaces
- De integratie van de drie lagen wordt gerealiseerd door Spring
- Er worden afzonderlijke pakketten aangemaakt voor elk van de drie lagen, die we Control, Domain en Dao zullen noemen. Een extra pakket zal de testapplicaties bevatten.
De structuur van de applicatie in Eclipse zou er als volgt uit kunnen zien:

2.4.2. De gegevenslaag DAO
De laag DAO zal de volgende interface implementeren:
package istia.st.demo.dao;
public interface IDao1 {
public int doSometingInDaoLayer(int a, int b);
}
- Schrijf twee klassen, Dao1Impl1 en Dao1Impl2, die de interface IDao1 implementeren. De methode Dao1Impl1. doSomethingInDaoLayer levert a+b op en de methode Dao1Impl2. doSomethingInDaoLayer levert a-b op.
- een testklasse JUnit schrijven die de twee voorgaande klassen test
2.4.3. De bedrijfslaag
De bedrijfslaag implementeert de volgende interface:
package istia.st.demo.domain;
public interface IDomain1 {
public int doSomethingInDomainLayer(int a, int b);
}
- Schrijf twee klassen, Domain1Impl1 en Domain1Impl2, die de interface IDomain1 implementeren. Deze klassen moeten een constructor hebben die een parameter van het type IDao1 accepteert. De methode Domain1Impl1.doSomethingInDomainLayer verhoogt a en b met één en geeft deze twee parameters vervolgens door aan de methode doSomethingInDaoLayer van het ontvangen object van het type IDao1. De methode Domain1Impl2.doSomethingInDomainLayer daarentegen zal a en b met één eenheid verlagen alvorens hetzelfde te doen.
- Schrijf een testklasse JUnit om de twee voorgaande klassen te testen
2.4.4. De gebruikersinterface-laag
De gebruikersinterface-laag implementeert de volgende interface:
package istia.st.demo.control;
public interface IControl1 {
public int doSometingInControlLayer(int a, int b);
}
- Schrijf twee klassen, Control1Impl1 en Control1Impl2, die de interface IControl1 implementeren. Deze klassen moeten een constructor hebben die een parameter van het type IDomain1 ontvangt. De methode Control1Impl1.doSomethingInControlLayer verhoogt a en b met één en geeft deze twee parameters vervolgens door aan de methode doSomethingInDomainLayer van het ontvangen object van het type IDomain1. De methode Control11Impl2.doSomethingInControlLayer daarentegen zal a en b met één eenheid verlagen alvorens hetzelfde te doen.
- Schrijf een testklasse JUnit om de twee voorgaande klassen te testen
2.4.5. Integratie met Spring
- Schrijf een Spring-configuratiebestand dat bepaalt welke klassen elk van de drie voorgaande lagen moet gebruiken
- Schrijf een testklasse JUnit die verschillende Spring-configuraties gebruikt, om de flexibiliteit van de geschreven applicatie te benadrukken
- Schrijf een zelfstandige applicatie (main-methode) die twee parameters doorgeeft aan de interface IControl1 en het door de interface weergegeven resultaat toont.
2.4.6. Een oplossing
2.4.6.1. Het Eclipse-project

De archieven van de map [lib] zijn toegevoegd aan de map [ClassPath] van het project.
2.4.6.2. Het pakket [istia.st.demo.dao]
De interface:
Een eerste implementatieklasse:
Een tweede implementatieklasse:
2.4.6.3. Het pakket [istia.st.demo.domain]
De interface:
Een eerste implementatieklasse:
Een tweede implementatieklasse:
2.4.6.4. Het pakket [istia.st.demo.control]
De interface
Een eerste implementatieklasse:
Een tweede implementatieklasse:
2.4.6.5. De configuratiebestanden [Spring]
Een eerste [springMainTest1.xml]:
Een tweede [springMainTest2.xml]:
2.4.6.6. Het testpakket [istia.st.demo.tests]
Een test van het type [main]:
De resultaten op de Eclipse-console:
Een andere test met het tweede configuratiebestand [Spring]:
De resultaten op de Eclipse-console:
Tot slot een Junit-test:
2.5. Conclusion
Het Spring-framework biedt echte flexibiliteit, zowel in de architectuur van applicaties als in hun configuratie. We hebben gebruikgemaakt van het concept IoC, een van de twee pijlers van Spring. De andere pijler is AOP (Aspect Oriented Programming), die we niet hebben behandeld. Hiermee kan via configuratie ‘gedrag’ aan een klassemethode worden toegevoegd zonder de code ervan te wijzigen. Schematisch gezien maakt AOP het mogelijk om aanroepen naar bepaalde methoden te filteren:
![]() |
- het filter kan vóór of na de doelmethode M worden uitgevoerd, of beide.
- De methode M is zich niet bewust van het bestaan van deze filters. Deze worden gedefinieerd in het configuratiebestand van Spring.
- De code van de M-methode wordt niet gewijzigd. Filters zijn Java-klassen die moeten worden geïmplementeerd. Spring biedt vooraf gedefinieerde filters, met name voor het beheer van transacties van SGBD.
- Filters zijn beans en worden als zodanig in het Spring-configuratiebestand gedefinieerd als beans.
Een veelgebruikte filter is de transactiefilter. Laten we een methode M uit de bedrijfslaag nemen die twee onlosmakelijk met elkaar verbonden bewerkingen op gegevens uitvoert (werkeenheid). Deze methode roept twee methoden M1 en M2 uit de laag DAO aan om deze twee bewerkingen uit te voeren.
![]() |
Omdat methode M zich in de bedrijfslaag bevindt, houdt deze geen rekening met het medium waarop deze gegevens zijn opgeslagen. De methode hoeft bijvoorbeeld niet ervan uitgaan dat de gegevens zich in een SGBD bevinden en dat de twee aanroepen van de methoden M1 en M2 binnen een transactie van SGBD moeten worden geplaatst. Het is aan de laag DAO om deze details te regelen. Een oplossing voor het voorgaande probleem is dan om in de laag DAO een methode aan te maken die zelf de methoden M1 en M2 aanroept, waarbij deze aanroepen worden opgenomen in een transactie van SGBD.
![]() |
De filteroplossing AOP is flexibeler. Hiermee kan een filter worden gedefinieerd dat, vóór de aanroep van M, een transactie start en na de aanroep, afhankelijk van de situatie, een commit of rollback uitvoert.
![]() |
Deze aanpak biedt verschillende voordelen:
- zodra het filter is gedefinieerd, kan het op meerdere methoden worden toegepast, bijvoorbeeld op alle methoden die een transactie vereisen
- de op deze manier gefilterde methoden hoeven niet te worden herschreven
- aangezien de te gebruiken filters via de configuratie worden gedefinieerd, kunnen ze worden gewijzigd
Naast de concepten IoC en AOP biedt Spring talrijke ondersteunende klassen voor drielaagse applicaties:
- voor JDBC, SqlMap (iBatis), en voor Hibernate, JDO (Java Data Object) in de DAO-laag
- voor het model MVC in de gebruikersinterface-laag
Voor meer informatie: http://www.springframework.org.









