Spring (framework)
Van Wikipedia
Portaal Javaplatform |
Het Spring Framework is een ondersteunend raamwerk gericht op ontwikkeling van software binnen het J2EE platform. Het raamwerk combineert een aantal API's en ideeën op een manier die een alternatief biedt voor de standaard manier van het ontwikkelen van software op het J2EE platform.
Inhoud |
[bewerk] De J2EE standaard
[bewerk] De normale manier van werken
Het J2EE-platform is gericht op het ontwikkelen van complexe applicaties, voornamelijk voor de zakelijke markt. Het voornaamste, architecturele idee achter dit platform is dat applicaties ontwikkeld worden volgens een functionele opdeling in een aantal lagen (tiers genaamd) en dat iedere laag geïmplementeerd wordt door een aantal componenten -- met per laag een eigen soort component.
De bovengenoemde componenten (servlets en EJBs) dienen om bepaalde, zakelijke functionaliteit aan te bieden passend bij hun eigen laag. Daarbij wordt deze functionaliteit omkleed met een technische infrastructuur om het mogelijk te maken voor andere applicaties en voor gebruikers om de zakelijke functionaliteit van ieder component aan te spreken.
De technische architectuur bestaat uit een verzameling technische "dienstverlening" (te denken valt aan toegang tot databases, communicatiemechanismen, transactiemechanismen en dergelijke) die aan het zakelijke gedeelte van een component aangeboden wordt via een standaard faciliteit -- een "container" geheten. Deze container accepteert als invoer een stukje software van een programmeur met daarin de implementatie van de zakelijke functionaliteit en omkleedt deze dan met toegang tot de technische infrastructuur.
Bij het J2EE-platform hoort, naast de API's en andere faciliteiten, een verzameling afspraken over hoe de technische infrastructuur aangesproken en bediend hoort te worden vanuit de zakelijke functionaliteit. Daarnaast zijn er afspraken over hoe de zakelijke functionaliteit aangesloten hoort te worden op de container. Met deze afspraken in de hand is het in principe mogelijk om de zakelijke functionaliteit zo te schrijven dat deze zonder verandering ingevoerd kan worden in iedere container-implementatie en transparant toegang kan krijgen tot de specifieke faciliteiten van die container (de zakelijke functionaliteit weet dat er een database is met een bepaalde verzameling tabellen -- of het nou een Oracle database is of MySQL, die details regelt de container). Daarnaast kan andere software transparant gebruikmaken van de zakelijke functionaliteit -- deze wordt op een standaard manier aangeboden ("gepubliceerd") binnen het systeem en kan dus op een standaard manier benaderd worden.
[bewerk] Kritieken op de standaard
Op het bovenstaande systeem is vrij veel kritiek te leveren. Met name op het gebied van complexiteit en limitaties van het systeem zijn hele boekwerken van kritiek verschenen.
De grootste kritiekpunten vallen in drie categorieën (die echter wel sterk samenhangen):
- Er is veel te veel werk voor nodig om de zakelijke functionaliteit goed in de container te krijgen en dit werk is ook nog eens te ingewikkeld
- Om transparant te kunnen ontwikkelen, leunt het J2EE-plaftorm heel sterk op configuratie -- instelling van allerlei details middels XML-bestanden. Deze bestanden ("deployment descriptors") zijn vaak erg ingewikkeld van aard, omdat er zeer veel in moet staan. Te ingewikkeld, volgens critici.
- EJBs zijn ingewikkeld om te ontwikkelen en te zwaar voor wat ze doen
- Dit is een uitloper van het vorige punt, toegespitst op EJBs. De ontwikkeling van EJBs gaat veel verder dan alleen het definiëren van de zakelijke functionaliteit; om een EJB goed aan te laten sluiten op de container, moet een EJB uitgerust worden met verschillende extra zaken -- niet alleen een deployment descriptor, maar ook minstens twee interfaces. Die synchroon gehouden moeten worden met de EJB als deze verandert. En voor bepaalde soorten EJBs komen daar nog extra interfaces en hulpklassen bij. Een EJB kan een heel kunststuk worden om te ontwikkelen. Veel ingewikkelder dan het ontwikkelen van functionaliteit in "standaard" Java.
Daar komt dan nog bij dat EJBs in het gebruik zwaar zijn, door de manier waarop de container ermee omgaat. De container voegt allerlei nuttige functionaliteit toe, maar deze functionaliteit neemt wel geheugen en processortijd in beslag. En voor dienstverlening naar buiten toe ook bandbreedte op het netwerk en databasetijd. Dit laatste kan dusdanig extreme vormen aannemen dat betwijfeld kan worden of EJBs (destijds ontwikkeld als manier om makkelijk veilige databasetoegang en dienstverlening op te zetten) wel geschikt zijn als middel om toepassingen te ontwikkelen die zeer databasegericht zijn.
- De standaard manier om dienstverlening te publiceren is aardig -- maar heeft wel als gevolg dat het gebruiken van diensten de code vastpint op een enkele installatie en ook dat het opvragen van diensten door de hele code heen verspreid wordt
- Om diensten te kunnen gebruiken binnen J2EE moet de toegang tot die diensten altijd met naam opgevraagd worden. Dit is een repetitieve aangelegenheid die het ook nog eens nodig kan maken om de software die diensten gebruikt hard vast te pinnen aan een bepaalde container-implementatie en -installatie. Transparantie is leuk en aardig, maar houdt op buiten de container; een stuk software van buiten de container dat de diensten van binnen de container wil gebruiken, moet alles hard bij naam en specifiek adres opvragen.
[bewerk] Spring: alles precies andersom
[bewerk] Inversion of Control (of Dependency Injection)
De hoofdgedachte achter Spring is dat een container (een ding dat een technische infrastructuur biedt aan een bundel zakelijke functionaliteit) een goed idee is, maar dat deze container op geen enkele wijze eisen mag stellen aan de implementatie van de zakelijke functionaliteit. Dat wil zeggen dat de zakelijke functionaliteit geïmplementeerd dient de worden door normale, klassieke Java objecten (in het Engels Plain Old Java Object of POJO) en dat deze objecten ook niets meer doen dan het implementeren van die functionaliteit. Een POJO mag niet gedwongen worden om een bepaalde vorm aan te nemen om met de container samen te kunnen werken, mag geen extra interfaces of hulpklassen nodig hebben ten behoeve van de container en ook geen kilometers lang configuratiebestand met allerlei rarigheden die alleen maar van toepassing zijn op een specifiek soort component. En ook mag het niet nodig zijn om allerlei toeren uit te halen om diensten op te vragen, want dat drukt weer een specifieke vorm op de implementatie van de zakelijke functionaliteit.
De manier waarop Spring (voornamelijk) aan al deze eisen voldoet, is een mechanisme dat Inversion of Control of Dependency Injection genoemd wordt. Het Spring raamwerk beschouwt iedere component, iedere implementatie van zakelijke functionaliteit, als een simpele JavaBean. En een simpele JavaBean weet in principe niets over de rest van het systeem -- het is een simpele klasse in Java, met een hoop normale methoden en wat methoden om van buitenaf informatie op te kunnen vragen ("getters", methoden wiens naam met "get" begint) en van buitenaf informatie in te kunnen voeren ("setters", methoden wiens naam met "set" begint). Uiteraard hangt de werking van deze beans (in de context van Spring ook wel Spring beans genoemd) af van andere beans -- maar een bean weet niet hoe hij aan een instantie van een andere bean komt. Het enige mechanisme dat een bean daarvoor heeft is een setter om een andere bean ingevoerd te kunnen krijgen, of een constructor om een andere bean ingevoerd te kunnen krijgen.
Merk op dat in de vorige paragraaf gesproken wordt over beans die andere beans ingevoerd kunnen krijgen. Ze voeren die beans niet zelf in, ze instantiëren die beans niet zelf, ze doen zelf helemaal niets. Alle beans die ergens nodig zijn, moeten door een externe entiteit aangeleverd worden. Die entiteit is de Spring container.
De Spring container is het systeem dat ervoor zorgt dat een bean voorzien wordt van alle faciliteiten die nodig zijn voor die bean om te kunnen functioneren. Heeft een bean een andere bean nodig, dan weet de Spring container
- welke bean nodig is
- waar hij nodig is
- welke setter gebruikt moet worden om die bean in te voeren
De Spring container instantieert alle beans die nodig zijn op het moment dat ze nodig zijn en voorziet ze op hun beurt weer van alle beans die zij nodig hebben. Dit is dependency injection: wat een bean nodig heeft, wordt door de Spring container in die bean geïnjecteerd, wordt door de Spring container ingebracht. Dit is een wezenlijk verschil met de EJB-manier waarin ieder component zelf op zoek moet naar alles wat hij nodig heeft. Dit principe maakt het ook mogelijk om componenten te ontwerpen en te bouwen die werkelijk vrijwel alleen maar zakelijke functionaliteit in zich hebben -- een stuk infrastructuur of een andere dienst om te gebruiken is voor een dergelijke component alleen maar het invoeren van een variabele om een object in vast te houden en een setter om het object door ingevoerd te krijgen. Het eigenlijke opzoeken, aanmaken en andere moeilijke zaken worden door de Spring container gedaan en het component (de POJO) hoeft verder van niets te weten (zelfs niet dat er een Spring container is).
De Spring container op zijn beurt wordt gevoed door een configuratiebestand dat een lijst bevat van alle beans in het systeem en waar per bean in staat welke andere beans nodig zijn. Daarnaast zijn er extra configuratie-opties per bean, die voornamelijk neerkomen op het instellen van initiële waarden voor de bean bij aanmaken.
Merk op dat dit betekent dat Spring moet zondigen tegen zijn eigen principes: een bean moet wel degelijk een kleine vormaanpassing ondergaan voor dependency injection (een extra variabele en een setter of een parameter in de constructor) en een configuratiebestand blijft nodig. Van dit configuratiebestand gebiedt de eerlijkheid wel op te merken dat het veel simpeler is dat de J2EE dependency descriptors; de vorm is veel simpeler en eenduidiger en er zijn veel minder soorten van configuratie die gedaan kunnen worden. Dit volgt overigens ook direct uit het feit dat voor de Spring container alles een Spring bean en dus feitelijk een POJO is: als je als container niets weet dan dat iets een bean is en dat je wat setters kunt verwachten, kun je ook geen configuratie invoeren voor iets anders dan setters on een POJO.
[bewerk] Aspect-georiënteerd programmeren
Het is vrijwel onmogelijk om een zakelijke applicatie te schrijven die alleen maar uit zakelijke functionaliteit bestaat. Een moderne applicatie voor het bedrijfsleven biedt ook andere functionaliteiten -- beveiliging bijvoorbeeld, of andere regels voor het toepassen van de zakelijke functionaliteit. Allerhande zaken die om de zakelijke functionaliteit heen gebeuren, vaak terugkomen, applicatie-specifiek zijn (en dus niet in de container verwerkt kunnen worden) en die in de applicatie tussen de container en de beans in moet zitten.
Een dergelijke functionaliteit vraagt eigenlijk om een manier om code te kunnen schrijven die aangeroepen kan worden op verschillende, specifieke punten in het programma -- rondom of middenin de zakelijke functionaliteit. Maar natuurlijk zonder dit in de implementatie van de zakelijke functionaliteit op te nemen, want anders wordt die weer vervuild met technische details.
Het Spring framework voorziet in een (rudimentaire) voorziening voor aspect-georiënteerd programmeren. Deze techniek van programmeren bestaat eruit dat programma's op bepaalde punten "onderbroken" kunnen worden door code van buitenaf. Deze code doet iets, verandert mogelijk de toestand van het programma of het verloop ervan, en laat het programma daarna verder lopen alsof er niets gebeurd was. Binnen Spring is het mogelijk om een programma in de Spring container voor en na iedere methode-aanroep te laten onderbreken door een apart stuk code. Hierdoor wordt het mogelijk om zaken als beveiliging te regelen op ieder punt, zonder een stuk code meerdere keren in het programma op te hoeven nemen en zelfs zonder dat het hoofdprogramma "weet wat er aan de hand is". Het is mogelijk om in onderbrekende code de loop van het programma te wijzigen, argumenten aan onderbroken methodes aan te passen of zelfs de methode volledig af te vangen en het programma met een foutmelding te onderbreken.
[bewerk] Model-View-Controller
Een andere functionaliteit die Spring biedt, speciaal aan de web-kant van webapplicaties, is een model-view-controller structuur. Deze faciliteit (een alternatief voor zaken als Jakarta Struts) maakt het mogelijk een nette scheiding aan te brengen in de presentatie van informatie via een webpagina en het gebruik van zakelijke functionaliteit achter die webpagina.
[bewerk] Verdere functionaliteit
Wat verdere functionaliteit betreft, is Spring heel pragmatisch. Voor zaken als databasetoegang (persistentie), transactionaliteit en dergelijke hebben de makers van Spring gezegd "er zijn genoeg goede implementaties van deze zaken, dat hoeven we niet over te doen". Spring ondersteunt de transparante toegang en het gebruik van allerlei andere raamwerken en APIs, waaronder Hibernate, iBATIS, JDO, JTA en JMS. Deze ondersteuning bestaat over het algemeen uit een gesimuleerde bean over het betreffende framework of API heen -- een bean die opgenomen kan worden in de Spring configuratie en zo dus naadloos geïntegreerd kan worden in andere beans in het programma, middels dependency injection.
Naast de ondersteuning voor aparte persistentie-frameworks biedt Spring ook een interface naar directe databasecommunicatie via de JDBC API; dit voor mensen die geen ander framework kunnen of willen gebruiken maar wel een aantal klassieke nadelen van JDBC willen vermijden (waaronder de grote hoeveelheden foutafhandelingscode die normaal voor JDBC-communicatie geschreven moet worden). Deze ondersteuning verdient meestal echter niet de voorkeur boven het gebruik van aparte persistentie-frameworks en levert ook meestal meer codeerwerk op dan bij externe frameworks noodzakelijk is.
Bronnen en referenties: |
|