Spring Web-contexten

1. Inleiding

Wanneer u Spring in een webapplicatie gebruikt, hebben we verschillende opties om de applicatiecontexten te organiseren die het allemaal bedraden.

In dit artikel gaan we de meest voorkomende opties die Spring biedt analyseren en uitleggen.

2. De context van de rootwebtoepassing

Elke Spring webapp heeft een bijbehorende applicatiecontext die is gekoppeld aan de levenscyclus: de root webapplicatiecontext.

Dit is een oude functie die ouder is dan Spring Web MVC, dus het is niet specifiek gekoppeld aan een webframeworktechnologie.

De context wordt gestart wanneer de toepassing start en wordt vernietigd wanneer deze stopt, dankzij een servlet-contextlistener. De meest voorkomende typen contexten kunnen ook tijdens runtime worden vernieuwd, hoewel niet alle ApplicationContext implementaties hebben deze mogelijkheid.

De context in een webapplicatie is altijd een instantie van WebApplicationContext. Dat is een interface die zich uitbreidt ApplicationContext met een contract voor toegang tot het ServletContext.

Hoe dan ook, applicaties hoeven zich meestal geen zorgen te maken over die implementatiedetails: de context van de rootwebtoepassing is gewoon een gecentraliseerde plaats om gedeelde bonen te definiëren.

2.1. De ContextLoaderListener

De context van de rootwebtoepassing die in de vorige sectie is beschreven, wordt beheerd door een listener of class org.springframework.web.context.ContextLoaderListener, dat deel uitmaakt van de lente-web module.

Standaard laadt de luisteraar een XML-toepassingscontext van /WEB-INF/applicationContext.xml. Deze standaardinstellingen kunnen echter worden gewijzigd. We kunnen bijvoorbeeld Java-annotaties gebruiken in plaats van XML.

We kunnen deze luisteraar configureren in de webapp-descriptor (web.xml bestand) of programmatisch in Servlet 3.x-omgevingen.

In de volgende secties zullen we elk van deze opties in detail bekijken.

2.2. Gebruik makend van web.xml en een XML Application Context

Tijdens gebruik web.xml, we configureren de luisteraar zoals gewoonlijk:

  org.springframework.web.context.ContextLoaderListener 

We kunnen een alternatieve locatie van de XML-contextconfiguratie specificeren met de contextConfigLocation parameter:

 contextConfigLocation /WEB-INF/rootApplicationContext.xml 

Of meer dan één locatie, gescheiden door komma's:

 contextConfigLocation /WEB-INF/context1.xml, /WEB-INF/context2.xml 

We kunnen zelfs patronen gebruiken:

 contextConfigLocation /WEB-INF/*-context.xml 

In elk geval, er is slechts één context gedefinieerd, door alle bean-definities te combineren die vanaf de opgegeven locaties zijn geladen.

2.3. Gebruik makend van web.xml en een Java Application Context

We kunnen ook andere soorten contexten specificeren naast de standaard op XML gebaseerde. Laten we bijvoorbeeld eens kijken hoe we in plaats daarvan de Java-annotatieconfiguratie kunnen gebruiken.

Wij gebruiken de contextClass parameter om de luisteraar te vertellen welk type context moet worden geïnstantieerd:

 contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext 

Elk type context kan een standaardconfiguratielocatie hebben. In ons geval is de AnnotationConfigWebApplicationContext heeft er geen, dus we moeten ervoor zorgen.

We kunnen dus een of meer geannoteerde klassen opsommen:

 contextConfigLocation com.baeldung.contexts.config.RootApplicationConfig, com.baeldung.contexts.config.NormalWebAppConfig 

Of we kunnen de context vertellen om een ​​of meer pakketten te scannen:

 contextConfigLocation com.baeldung.bean.config 

En natuurlijk kunnen we de twee opties mixen en matchen.

2.4. Programmatische configuratie met Servlet 3.x

Versie 3 van de Servlet API heeft configuratie gemaakt via de web.xml bestand volledig optioneel. Bibliotheken kunnen hun webfragmenten leveren, dit zijn stukjes XML-configuratie die luisteraars, filters, servlets enzovoort kunnen registreren.

Gebruikers hebben ook toegang tot een API waarmee elk element van een servlet-gebaseerde applicatie programmatisch kan worden gedefinieerd.

De lente-web module maakt gebruik van deze features en biedt zijn API aan om componenten van de applicatie te registreren bij het opstarten.

Spring scant het klassenpad van de toepassing op exemplaren van het org.springframework.web.WebApplicationInitializer klasse. Dit is een interface met een enkele methode, void onStartup (ServletContext servletContext) gooit ServletException, dat wordt aangeroepen bij het opstarten van de applicatie.

Laten we nu eens kijken hoe we deze faciliteit kunnen gebruiken om dezelfde typen rootwebtoepassingscontexten te creëren die we eerder hebben gezien.

2.5. Servlet 3.x en een XML-toepassingscontext gebruiken

Laten we beginnen met een XML-context, net als in paragraaf 2.2.

We zullen het bovenstaande implementeren onStartup methode:

openbare klasse ApplicationInitializer implementeert WebApplicationInitializer {@Override public void onStartup (ServletContext servletContext) gooit ServletException {// ...}}

Laten we de implementatie regel voor regel opsplitsen.

We maken eerst een rootcontext. Omdat we XML willen gebruiken, moet het een XML-gebaseerde toepassingscontext zijn, en aangezien we ons in een webomgeving bevinden, moet het implementeren WebApplicationContext ook.

De eerste regel is dus de expliciete versie van het contextClass parameter die we eerder zijn tegengekomen, waarmee we beslissen welke specifieke contextimplementatie we gebruiken:

XmlWebApplicationContext rootContext = nieuwe XmlWebApplicationContext ();

Vervolgens vertellen we in de tweede regel de context waar de bean-definities vandaan moeten worden geladen. Opnieuw, setConfigLocations is de programmatische analoog van de contextConfigLocation parameter in web.xml:

rootContext.setConfigLocations ("/ WEB-INF / rootApplicationContext.xml");

Ten slotte maken we een ContextLoaderListener met de root-context en registreer deze met de servlet-container. Zoals we kunnen zien, ContextLoaderListener heeft een geschikte constructor die een WebApplicationContext en stelt het ter beschikking van de applicatie:

servletContext.addListener (nieuwe ContextLoaderListener (rootContext));

2.6. Servlet 3.x en een Java-toepassingscontext gebruiken

Als we een op annotaties gebaseerde context willen gebruiken, kunnen we het codefragment in de vorige sectie wijzigen om het een instantie te maken van een AnnotationConfigWebApplicationContext in plaats daarvan.

Laten we echter eens kijken naar een meer gespecialiseerde aanpak om hetzelfde resultaat te verkrijgen.

De WebApplicationInitializer class die we eerder hebben gezien, is een interface voor algemene doeleinden. Het blijkt dat Spring een paar meer specifieke implementaties biedt, waaronder een abstracte klasse met de naam AbstractContextLoaderInitializer.

Het is zijn taak, zoals de naam al aangeeft, om een ContextLoaderListener en registreer het bij de servlet-container.

We hoeven het alleen maar te vertellen hoe de root-context moet worden gebouwd:

openbare klasse AnnotationsBasedApplicationInitializer breidt AbstractContextLoaderInitializer uit {@Override beveiligde WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = nieuwe AnnotationConfigWebApplicationContext (); rootContext.register (RootApplicationConfig.class); retourneer rootContext; }}

Hier kunnen we zien dat we het ContextLoaderListener, wat ons een klein beetje standaardcode bespaart.

Let ook op het gebruik van de registreren methode die specifiek is voor AnnotationConfigWebApplicationContext in plaats van de meer algemene setConfigLocations: door het aan te roepen, kunnen we een individu registreren @Configuratie geannoteerde klassen met de context, waardoor het scannen van pakketten wordt vermeden.

3. Dispatcher Servlet-contexten

Laten we ons nu concentreren op een ander type toepassingscontext. Deze keer verwijzen we naar een functie die specifiek is voor Spring MVC, in plaats van een onderdeel van de algemene ondersteuning voor webtoepassingen van Spring.

Voor Spring MVC-applicaties is ten minste één Dispatcher Servlet geconfigureerd (maar mogelijk meer dan één, we zullen het later over die zaak hebben). Dit is de servlet die inkomende verzoeken ontvangt, deze naar de juiste controllermethode verzendt en de weergave retourneert.

Elk DispatcherServlet heeft een bijbehorende toepassingscontext. Bonen die in dergelijke contexten zijn gedefinieerd, configureren de servlet en definiëren MVC-objecten zoals controllers en view resolvers.

Laten we eens kijken hoe we eerst de context van de servlet kunnen configureren. We zullen later enkele diepgaande details bekijken.

3.1. Gebruik makend van web.xml en een XML Application Context

DispatcherServlet wordt doorgaans gedeclareerd in web.xml met een naam en een afbeelding:

 normal-webapp org.springframework.web.servlet.DispatcherServlet 1 normal-webapp / api / * 

Indien niet anders gespecificeerd, wordt de naam van de servlet gebruikt om te bepalen welk XML-bestand moet worden geladen. In ons voorbeeld gebruiken we het bestand WEB-INF / normaal-webapp-servlet.xml.

We kunnen ook een of meer paden naar XML-bestanden specificeren, op dezelfde manier als ContextLoaderListener:

 ... contextConfigLocation /WEB-INF/normal/*.xml 

3.2. Gebruik makend van web.xml en een Java Application Context

Als we een ander type context willen gebruiken, gaan we verder zoals bij ContextLoaderListener, opnieuw. Dat wil zeggen, we specificeren een contextClass parameter samen met een geschikt contextConfigLocation:

 normal-webapp-annotaties org.springframework.web.servlet.DispatcherServlet contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext contextConfigLocation com.baeldung.contexts.config.NormalWebAppConfig 1 

3.3. Servlet 3.x en een XML-toepassingscontext gebruiken

Nogmaals, we zullen twee verschillende methoden bekijken voor het programmatisch declareren van een DispatcherServlet, en we passen de ene toe op een XML-context en de andere op een Java-context.

Dus laten we beginnen met een generiek WebApplicationInitializer en een XML-toepassingscontext.

Zoals we eerder hebben gezien, moeten we het onStartup methode. Deze keer maken en registreren we echter ook een dispatcher-servlet:

XmlWebApplicationContext normalWebAppContext = nieuwe XmlWebApplicationContext (); normalWebAppContext.setConfigLocation ("/ WEB-INF / normal-webapp-servlet.xml"); ServletRegistration.Dynamic normal = servletContext.addServlet ("normal-webapp", nieuwe DispatcherServlet (normalWebAppContext)); normal.setLoadOnStartup (1); normal.addMapping ("/ api / *");

We kunnen gemakkelijk een parallel trekken tussen de bovenstaande code en het equivalent web.xml configuratie-elementen.

3.4. Servlet 3.x en een Java-toepassingscontext gebruiken

Deze keer configureren we een op annotaties gebaseerde context met behulp van een gespecialiseerde implementatie van WebApplicationInitializer: AbstractDispatcherServletInitializer.

Dat is een abstracte klasse die, naast het creëren van een rootwebtoepassingscontext zoals eerder gezien, ons in staat stelt om één dispatcher-servlet te registreren met een minimum standaard:

@Override beveiligde WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = nieuwe AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); retourneer secureWebAppContext; } @Override beschermde String [] getServletMappings () {retourneer nieuwe String [] {"/ s / api / *"}; }

Hier kunnen we een methode zien voor het creëren van de context die aan de servlet is gekoppeld, precies zoals we eerder hebben gezien voor de rootcontext. We hebben ook een methode om de toewijzingen van de servlet te specificeren, zoals in web.xml.

4. Ouder- en kindcontexten

Tot nu toe hebben we twee belangrijke soorten contexten gezien: de context van de rootwebtoepassing en de contexten van de dispatcher-servlet. Dan hebben we misschien een vraag: zijn die contexten gerelateerd?

Het blijkt dat ze dat inderdaad zijn. In feite, de basiscontext is de bovenliggende context van elke dispatcher-servletcontext. Beans die in de context van de rootwebtoepassing zijn gedefinieerd, zijn dus zichtbaar voor elke dispatcher-servletcontext, maar niet omgekeerd.

De rootcontext wordt dus meestal gebruikt om servicebonen te definiëren, terwijl de dispatchercontext die bonen bevat die specifiek gerelateerd zijn aan MVC.

Merk op dat we ook manieren hebben gezien om de dispatcher-servlet-context programmatisch te maken. Als we de ouder handmatig instellen, overschrijft Spring onze beslissing niet en is deze sectie niet langer van toepassing.

In eenvoudigere MVC-toepassingen is het voldoende om een ​​enkele context te hebben die is gekoppeld aan de enige dispatcher-servlet. Er zijn geen al te complexe oplossingen nodig!

Toch wordt de ouder-kindrelatie nuttig wanneer we meerdere dispatcher-servlets hebben geconfigureerd. Maar wanneer moeten we de moeite nemen om er meer dan één te hebben?

Over het algemeen, we declareren meerdere dispatcher-servlets wanneer we meerdere sets MVC-configuratie nodig hebben. We hebben bijvoorbeeld een REST API naast een traditionele MVC-applicatie of een onbeveiligd en beveiligd gedeelte van een website:

Let op: wanneer we verlengen AbstractDispatcherServletInitializer (zie paragraaf 3.4), registreren we zowel een rootwebapplicatiecontext als een enkele dispatcher-servlet.

Dus als we meer dan één servlet willen, hebben we er meerdere nodig AbstractDispatcherServletInitializer implementaties. We kunnen echter maar één rootcontext definiëren, anders start de applicatie niet.

Gelukkig is de createRootApplicationContext methode kan terugkeren nul. We kunnen er dus een hebben AbstractContextLoaderInitializer en veel AbstractDispatcherServletInitializer implementaties die geen rootcontext creëren. In een dergelijk scenario is het raadzaam om de initializers mee te bestellen @Bestellen uitdrukkelijk.

Merk ook op dat AbstractDispatcherServletInitializer registreert de servlet onder een bepaalde naam (coördinator) en natuurlijk kunnen we niet meerdere servlets met dezelfde naam hebben. Dus we moeten overschrijven getServletName:

@Override beschermde String getServletName () {retourneer "another-dispatcher"; }

5. A Ouder- en kindcontext Voorbeeld

Stel dat we twee delen van onze applicatie hebben, bijvoorbeeld een openbare die wereldwijd toegankelijk is en een beveiligde, met verschillende MVC-configuraties. Hier definiëren we twee controllers die een ander bericht uitzenden.

Stel ook dat sommige controllers een service nodig hebben die over aanzienlijke bronnen beschikt; een alomtegenwoordig geval is persistentie. Vervolgens willen we die service maar één keer starten om te voorkomen dat het gebruik van bronnen wordt verdubbeld, en omdat we geloven in het Don't Repeat Yourself-principe!

Laten we nu verder gaan met het voorbeeld.

5.1. De Shared Service

In ons hello world-voorbeeld hebben we genoegen genomen met een eenvoudigere begroetingsservice in plaats van volharding:

pakket com.baeldung.contexts.services; @Service openbare klasse GreeterService {@Resource persoonlijke begroeting; openbare String greet () {retourgroet.getMessage (); }}

We declareren de service in de context van de rootwebtoepassing, met behulp van componentscanning:

@Configuration @ComponentScan (basePackages = {"com.baeldung.contexts.services"}) openbare klasse RootApplicationConfig {// ...}

In plaats daarvan geven we misschien de voorkeur aan XML:

5.2. De controllers

Laten we twee eenvoudige controllers definiëren die de service gebruiken en een begroeting uitvoeren:

pakket com.baeldung.contexts.normal; @Controller openbare klasse HelloWorldController {@Autowired privé GreeterService greeterService; @RequestMapping (path = "/ welcome") openbaar ModelAndView helloWorld () {String message = "

Normaal "+ greeterService.greet () +"

"; return new ModelAndView (" welcome "," message ", message);}} //" Beveiligd "controllerpakket com.baeldung.contexts.secure; String message ="

Beveilig "+ greeterService.greet () +"

";

Zoals we kunnen zien, liggen de controllers in twee verschillende pakketten en drukken ze verschillende berichten af: de ene zegt "normaal", de andere "veilig".

5.3. De Dispatcher Servlet-contexten

Zoals we eerder zeiden, zullen we twee verschillende dispatcher-servlet-contexten hebben, één voor elke controller. Laten we ze dus in Java definiëren:

// Normale context @Configuration @EnableWebMvc @ComponentScan (basePackages = {"com.baeldung.contexts.normal"}) openbare klasse NormalWebAppConfig implementeert WebMvcConfig {// ...} // "Veilige" context @Configuration @EnableWebM @ComponentSvc ( basePackages = {"com.baeldung.contexts.secure"}) openbare klasse SecureWebAppConfig implementeert WebMvcConfigurer {// ...}

Of, als we dat willen, in XML:

5.4. Alles samenvoegen

Nu we alle stukjes hebben, hoeven we Spring alleen maar te vertellen dat hij ze moet bedraden. Bedenk dat we de rootcontext moeten laden en de twee dispatcher-servlets moeten definiëren. Hoewel we meerdere manieren hebben gezien om dat te doen, zullen we ons nu concentreren op twee scenario's, een Java-scenario en een XML-scenario. Laten we beginnen met Java.

We definiëren een AbstractContextLoaderInitializer om de root-context te laden:

@Override beveiligde WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = nieuwe AnnotationConfigWebApplicationContext (); rootContext.register (RootApplicationConfig.class); retourneer rootContext; } 

Vervolgens moeten we de twee servlets maken, dus we definiëren twee subklassen van AbstractDispatcherServletInitializer. Ten eerste de "normale":

@Override beveiligde WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext normalWebAppContext = nieuwe AnnotationConfigWebApplicationContext (); normalWebAppContext.register (NormalWebAppConfig.class); retourneer normalWebAppContext; } @Override beschermde String [] getServletMappings () {retourneer nieuwe String [] {"/ api / *"}; } @Override beschermde String getServletName () {retourneer "normal-dispatcher"; } 

Vervolgens de 'beveiligde', die een andere context laadt en wordt toegewezen aan een ander pad:

@Override beveiligde WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = nieuwe AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); retourneer secureWebAppContext; } @Override beschermde String [] getServletMappings () {retourneer nieuwe String [] {"/ s / api / *"}; } @Override beschermde String getServletName () {retourneer "secure-dispatcher"; }

En we zijn klaar! We hebben zojuist toegepast wat we in vorige secties hebben aangeraakt.

We kunnen hetzelfde doen met web.xml, opnieuw alleen door de stukken die we tot nu toe hebben besproken te combineren.

Definieer een roottoepassingscontext:

  org.springframework.web.context.ContextLoaderListener 

Een "normale" dispatcher-context:

 normal-webapp org.springframework.web.servlet.DispatcherServlet 1 normal-webapp / api / * 

En tot slot een ‘veilige’ context:

 secure-webapp org.springframework.web.servlet.DispatcherServlet 1 secure-webapp / s / api / * 

6. Meerdere contexten combineren

Er zijn andere manieren dan ouder-kind om meerdere configuratielocaties te combineren, om grote contexten te splitsen en verschillende zorgen beter te scheiden. We hebben al een voorbeeld gezien: wanneer we specificeren contextConfigLocation met meerdere paden of pakketten bouwt Spring een enkele context door alle bean-definities te combineren, alsof ze in een enkel XML-bestand of Java-klasse zijn geschreven.

We kunnen echter een soortgelijk effect bereiken met andere middelen en zelfs verschillende benaderingen samen gebruiken. Laten we onze opties eens bekijken.

Een mogelijkheid is het scannen van componenten, wat we in een ander artikel toelichten.

6.1. Een context in een andere importeren

Als alternatief kunnen we een contextdefinitie een andere laten importeren. Afhankelijk van het scenario hebben we verschillende soorten import.

Een @Configuratie klasse in Java:

@Configuration @Import (SomeOtherConfiguration.class) openbare klasse Config {...}

Een ander type bron laden, bijvoorbeeld een XML-contextdefinitie, in Java:

@Configuration @ImportResource ("classpath: basicConfigForPropertiesTwo.xml") openbare klasse Config {...}

Ten slotte, inclusief een XML-bestand in een ander:

We hebben dus veel manieren om de services, componenten, controllers, etc. te organiseren die samenwerken om onze geweldige applicatie te maken. En het leuke is dat IDE's ze allemaal begrijpen!

7. Spring Boot-webapplicaties

Spring Boot configureert automatisch de componenten van de applicatie, dus over het algemeen is er minder behoefte om na te denken over hoe ze te organiseren.

Toch gebruikt Boot onder de motorkap Spring-functies, inclusief degene die we tot nu toe hebben gezien. Laten we een paar opmerkelijke verschillen bekijken.

Spring Boot-webtoepassingen die in een ingesloten container worden uitgevoerd, draaien er geen WebApplicationInitializer met opzet.

Mocht het nodig zijn, dan kunnen we dezelfde logica in een SpringBootServletInitializer of een ServletContextInitializer in plaats daarvan, afhankelijk van de gekozen implementatiestrategie.

Voor het toevoegen van servlets, filters en luisteraars zoals in dit artikel wordt beschreven, is het echter niet nodig om dit te doen. In feite registreert Spring Boot automatisch elke servlet-gerelateerde boon in de container:

@Bean openbare Servlet myServlet () {...}

De aldus gedefinieerde objecten worden toegewezen volgens conventies: filters worden automatisch toegewezen aan / *, dat wil zeggen aan elk verzoek. Als we een enkele servlet registreren, wordt deze toegewezen aan /, anders wordt elke servlet toegewezen aan zijn bean-naam.

Als de bovenstaande conventies niet voor ons werken, kunnen we een FilterRegistrationBean, ServletRegistrationBean, of ServletListenerRegistrationBean in plaats daarvan. Die lessen stellen ons in staat om de fijne aspecten van de registratie te controleren.

8. Conclusies

In dit artikel hebben we een diepgaand overzicht gegeven van de verschillende opties die beschikbaar zijn om een ​​Spring-webtoepassing te structureren en te organiseren.

We hebben enkele functies weggelaten, met name de ondersteuning voor een gedeelde context in bedrijfstoepassingen, die op het moment van schrijven nog steeds ontbreekt in Spring 5.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in het GitHub-project.