Spring Framework

Spring Framework, meestal afgekort tot Spring, is een vrij framework gericht op ontwikkeling van software in de programmeertaal Java. Het framework combineert API's en ideeën waardoor het een alternatief biedt voor de standaard manier van ontwikkelen. Dankzij diverse uitbreidingen wordt het vooral gebruikt als alternatief voor of uitbreiding op technologieën uit J2EE-platform. Versie 3.1, die uitgegeven werd op 13 december 2011, bracht ondersteuning voor Java 7. Spring Framework versie 4 ondersteunt Java 6, 7 en 8. Versie 5 ondersteunt Java 8, 9 en 11.[2]

Spring Framework
Logo
Ontwikkelaar(s)VMware
Uitgebracht1 oktober 2002
Recentste versie5.3.23 [1] 
(15 september 2022)
Recentste bètaversie6.0.0-M6 [1] 
(15 september 2022)
StatusActief
BesturingssysteemMultiplatform
Geschreven inJava
CategorieFramework
Licentie(s)Apache-licentie 2.0
Versiebeheer
Website(en) Projectpagina
Portaal  Portaalicoon  Informatica
Javaplatform

Geschiedenis

De eerste versie werd ontwikkeld door Rod Johnson en verscheen bij de publicatie van zijn boek "Expert One-on-One J2EE Design and Development"[3] in 2002. Het framework werd voor het eerst uitgebracht in 2003, onder de Apache License.

J2EE

De normale manier van werken

Het J2EE-platform is gericht op het ontwikkelen van complexe applicaties, voornamelijk voor de zakelijke markt. Het 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. Elke laag heeft een eigen soort component: een servlet of EJB. Deze dienen om zakelijke functionaliteit aan te bieden omkleed met een technische infrastructuur die ervoor zorgt dat andere applicaties en gebruikers de functionaliteit van iedere component kunnen aanspreken.

De technische architectuur bestaat uit een verzameling van technische "dienstverlening": toegang tot databases, communicatiemechanismen en transactiemechanismen. De dienstverlening wordt aangeboden aan het zakelijke gedeelte van een component 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 containerimplementatie 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, maar de details (zoals het type database) zijn onbekend - die 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.

Kritiek

Op het bovenstaand 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.

  • Zakelijke functionaliteit in een container krijgen is te ingewikkeld: om transparant te kunnen ontwikkelen, leunt J2EE sterk op configuratie door middel van XML-bestanden. Deze bestanden, ook wel "deployment descriptors", zijn vaak complex omdat ze zeer veel informatie bevatten.
  • EJB's zijn ingewikkeld en zwaar: de ontwikkeling van EJB's 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. Voor bepaalde soorten EJB's komen daar nog extra interfaces en hulpklassen bij.
  • EJB's zijn zwaar in gebruik door de manier waarop de container ermee omgaat: de container voegt allerlei nuttige functionaliteit toe, maar deze functionaliteit neemt geheugen en processortijd in beslag. Voor dienstverlening neemt het ook bandbreedte van het netwerk en databasetijd in beslag. Dit laatste kan dusdanig extreme vormen aannemen dat betwijfeld kan worden of EJB's, 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 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 containerimplementatie en -installatie. Transparantie houdt op buiten de container; een stuk software van buiten de container dat de diensten van binnen de container wil gebruiken, moet alles bij naam en specifiek adres opvragen.

Spring: alles andersom

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. De zakelijke functionaliteit moet dus geïmplementeerd worden met klassieke Java-objecten (Engels: Plain Old Java Object, afgekort 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 lang configuratiebestand met allerlei rarigheden die alleen maar van toepassing zijn op een specifiek soort component.

Door inversion of control of dependency injection wordt iedere component en iedere implementatie van zakelijke functionaliteit als een simpele JavaBean geschouwd. Een eenvoudige JavaBean weet (in principe) niets over de rest van het systeem: het is een eenvoudige klasse in Java, gecombineerd met methoden om informatie op te vragen ("getters") en informatie in te voeren ("setters"). De werking van deze beans hangt af van andere beans, maar heeft een setter of constructor nodig om een andere bean ingevoerd te kunnen krijgen (beans voeren andere beans niet zelf in en instantiëren de beans ook niet zelf). Beans die nodig zijn worden aangeleverd door een externe entiteit (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 Springcontainer

  • welke bean nodig is
  • waar hij nodig is
  • welke setter gebruikt moet worden om die bean in te voeren.

De Springcontainer 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 Springcontainer in die bean geïnjecteerd. Dit is een wezenlijk verschil met de EJB-manier waarin iedere 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 de component (de POJO) hoeft verder van niets te weten (zelfs niet dat er een Springcontainer is).

De Springcontainer 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.

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. Dit configuratiebestand is veel simpeler dan 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 Springcontainer alles een Spring-bean en dus feitelijk een POJO is: als je als container niets meer 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 op een POJO.

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 van code schrijven die aangeroepen kan worden op verschillende, specifieke punten in het programma -- rondom of midden in 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.

Spring voorziet in een (basale) voorziening voor aspectgeoriënteerd programmeren zodat 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 is. Binnen Spring is het mogelijk om een programma in de Spring-container voor en na iedere methode-aanroep te laten onderbreken door een apart stukje 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 de 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.

Model-View-Controller

Een andere functionaliteit die Spring biedt, speciaal aan de webkant van webapplicaties, is een model-view-controller-structuur. Deze faciliteit (een alternatief voor zaken als Apache 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.

Verdere functionaliteit

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 frameworks en API's, 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 gesimuleerde bean is een bean die opgenomen kan worden in de Spring-configuratie en zo 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.