Forårets webkontekster

1. Introduktion

Når vi bruger Spring i en webapplikation, har vi flere muligheder for at organisere applikationskonteksterne, der kabler det hele sammen.

I denne artikel vil vi analysere og forklare de mest almindelige muligheder, som Spring tilbyder.

2. Konteksten til rodwebapplikationen

Hver Spring-webapp har en tilknyttet applikationskontekst, der er bundet til dens livscyklus: rod-webapplikationskonteksten.

Dette er en gammel funktion, der går forud for Spring Web MVC, så den er ikke specifikt bundet til nogen web-framework-teknologi.

Konteksten startes, når applikationen starter, og den ødelægges, når den stopper takket være en servlet-kontekstlytter. De mest almindelige typer af sammenhænge kan også opdateres ved kørsel, selvom ikke alle ApplicationContext implementeringer har denne mulighed.

Konteksten i en webapplikation er altid en forekomst af WebApplicationContext. Det er en grænseflade, der udvides ApplicationContext med en kontrakt om adgang til ServletContext.

Under alle omstændigheder bør applikationer normalt ikke være bekymrede for disse implementeringsoplysninger: rod-webapplikationskonteksten er simpelthen et centraliseret sted at definere delte bønner.

2.1. Det ContextLoaderListener

Rødwebapplikationskonteksten beskrevet i det foregående afsnit administreres af en lytter i klassen org.springframework.web.context.ContextLoaderListener, som er en del af fjeder-web modul.

Som standard indlæser lytteren en XML-applikationskontekst fra /WEB-INF/applicationContext.xml. Disse standarder kan dog ændres. Vi kan f.eks. Bruge Java-kommentarer i stedet for XML.

Vi kan konfigurere denne lytter enten i webapp-deskriptoren (web.xml fil) eller programmatisk i Servlet 3.x-miljøer.

I de følgende afsnit vil vi se nærmere på hver af disse muligheder.

2.2. Ved brug af web.xml og en XML-applikationskontekst

Ved brug web.xml, konfigurerer vi lytteren som normalt:

  org.springframework.web.context.ContextLoaderListener 

Vi kan angive en alternativ placering af XML-kontekstkonfigurationen med contextConfigLocation parameter:

 contextConfigLocation /WEB-INF/rootApplicationContext.xml 

Eller mere end en placering, adskilt af kommaer:

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

Vi kan endda bruge mønstre:

 contextConfigLocation /WEB-INF/*-context.xml 

Under alle omstændigheder kun en kontekst er defineret, ved at kombinere alle bønnedefinitioner indlæst fra de angivne placeringer.

2.3. Ved brug af web.xml og en Java-applikationskontekst

Vi kan også specificere andre typer sammenhænge udover den standard XML-baserede. Lad os for eksempel se, hvordan man bruger Java-kommentar konfiguration i stedet.

Vi bruger contextClass parameter for at fortælle lytteren, hvilken type kontekst der skal instantieres:

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

Hver type kontekst kan have en standardkonfigurationsplacering. I vores tilfælde er AnnotationConfigWebApplicationContext har ikke en, så vi er nødt til at give den.

Vi kan således liste en eller flere kommenterede klasser:

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

Eller vi kan fortælle konteksten at scanne en eller flere pakker:

 contextConfigLocation com.baeldung.bean.config 

Og selvfølgelig kan vi blande og matche de to muligheder.

2.4. Programmatisk konfiguration med Servlet 3.x

Version 3 af Servlet API har konfigureret gennem web.xml fil helt valgfri. Biblioteker kan levere deres webfragmenter, som er stykker af XML-konfiguration, der kan registrere lyttere, filtre, servlets og så videre.

Brugere har også adgang til en API, der gør det muligt programmatisk at definere hvert element i en servletbaseret applikation.

Det fjeder-web modul bruger disse funktioner og tilbyder sin API til at registrere komponenter i applikationen, når den starter.

Spring scanner applikationens klassesti for forekomster af org.springframework.web.WebApplicationInitializer klasse. Dette er en grænseflade med en enkelt metode, ugyldigt onStartup (ServletContext servletContext) kaster ServletException, der påberåbes ved opstart af applikationen.

Lad os nu se på, hvordan vi kan bruge denne facilitet til at skabe de samme typer rod-webapplikationskontekster, som vi har set tidligere.

2.5. Brug af Servlet 3.x og en XML-applikationskontekst

Lad os starte med en XML-kontekst, ligesom i afsnit 2.2.

Vi implementerer ovennævnte onStartup metode:

offentlig klasse ApplicationInitializer implementerer WebApplicationInitializer {@ Override public void onStartup (ServletContext servletContext) kaster ServletException {// ...}}

Lad os nedbryde implementeringen linje for linje.

Vi opretter først en rodkontekst. Da vi ønsker at bruge XML, skal det være en XML-baseret applikationskontekst, og da vi er i et webmiljø, skal det implementeres WebApplicationContext såvel.

Den første linje er således den eksplicitte version af contextClass parameter, som vi har stødt på tidligere, som vi beslutter, hvilken specifik kontekstimplementering vi skal bruge:

XmlWebApplicationContext rootContext = ny XmlWebApplicationContext ();

Derefter fortæller vi i anden linje sammenhængen, hvor dens bønnedefinitioner skal indlæses. Igen, setConfigLocations er den programmatiske analog af contextConfigLocation parameter i web.xml:

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

Endelig opretter vi en ContextLoaderListener med rodkonteksten og registrer den med servletbeholderen. Som vi kan se, ContextLoaderListener har en passende konstruktør, der tager en WebApplicationContext og gør det tilgængeligt for applikationen:

servletContext.addListener (ny ContextLoaderListener (rootContext));

2.6. Brug af Servlet 3.x og en Java-applikationskontekst

Hvis vi ønsker at bruge en annoteringsbaseret kontekst, kan vi ændre kodestykket i det forrige afsnit for at gøre det øjeblikkeligt AnnotationConfigWebApplicationContext i stedet.

Lad os dog se en mere specialiseret tilgang for at opnå det samme resultat.

Det WebApplicationInitializer klasse, som vi har set tidligere, er en generel grænseflade. Det viser sig, at Spring giver et par mere specifikke implementeringer, inklusive en abstrakt klasse kaldet AbstractContextLoaderInitializer.

Dets opgave, som navnet antyder, er at oprette en ContextLoaderListener og registrer det med servletbeholderen.

Vi skal kun fortælle det, hvordan man bygger rodkonteksten:

offentlig klasse AnnotationsBasedApplicationInitializer udvider AbstractContextLoaderInitializer {@Override-beskyttet WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = ny AnnotationConfigWebApplicationContext (); rootContext.register (RootApplicationConfig.class); returner rootContext; }}

Her kan vi se, at vi ikke længere behøver at registrere ContextLoaderListener, hvilket sparer os for en lille smule kedelpladekode.

Bemærk også brugen af Tilmeld metode, der er specifik for AnnotationConfigWebApplicationContext i stedet for det mere generiske setConfigLocations: ved at påberåbe det, kan vi registrere individuelle @Konfiguration kommenterede klasser med konteksten og undgår således pakkescanning.

3. Dispatcher Servlet-sammenhænge

Lad os nu fokusere på en anden type applikationskontekst. Denne gang vil vi henvise til en funktion, der er specifik for Spring MVC, snarere end en del af Spring's generiske support til webapplikationer.

Spring MVC-applikationer har mindst en Dispatcher Servlet konfigureret (men muligvis mere end en, vi taler om den sag senere). Dette er servlet, der modtager indgående anmodninger, sender dem til den relevante controller-metode og returnerer visningen.

Hver DispatcherServlet har en tilknyttet applikationskontekst. Bønner defineret i sådanne sammenhænge konfigurerer servlet og definerer MVC-objekter som controllere og visningsopløsere.

Lad os se, hvordan du konfigurerer servlets kontekst først. Vi vil se på nogle detaljerede detaljer senere.

3.1. Ved brug af web.xml og en XML-applikationskontekst

DispatcherServlet er typisk erklæret i web.xml med et navn og en kortlægning:

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

Hvis ikke andet er angivet, bruges navnet på servlet til at bestemme den XML-fil, der skal indlæses. I vores eksempel bruger vi filen WEB-INF / normal-webapp-servlet.xml.

Vi kan også angive en eller flere stier til XML-filer på samme måde som ContextLoaderListener:

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

3.2. Ved brug af web.xml og en Java-applikationskontekst

Når vi ønsker at bruge en anden type kontekst, fortsætter vi som med ContextLoaderListener, igen. Det vil sige, vi specificerer en contextClass parameter sammen med en passende contextConfigLocation:

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

3.3. Brug af Servlet 3.x og en XML-applikationskontekst

Igen vil vi se på to forskellige metoder til programmatisk at erklære en DispatcherServlet, og vi anvender den ene til en XML-kontekst og den anden for en Java-kontekst.

Så lad os starte med en generisk WebApplicationInitializer og en XML-applikationskontekst.

Som vi tidligere har set, er vi nødt til at implementere onStartup metode. Denne gang opretter og registrerer vi dog også en afsenderservlet:

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

Vi kan let tegne en parallel mellem ovenstående kode og det tilsvarende web.xml konfigurationselementer.

3.4. Brug af Servlet 3.x og en Java-applikationskontekst

Denne gang konfigurerer vi en annoteringsbaseret kontekst ved hjælp af en specialiseret implementering af WebApplicationInitializer: AbstractDispatcherServletInitializer.

Det er en abstrakt klasse, der udover at skabe en rod-webapplikationskontekst som tidligere set giver os mulighed for at registrere en dispatcherservlet med mindst kedelplade:

@ Override beskyttet WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = ny AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); returner secureWebAppContext; } @ Override-beskyttet streng [] getServletMappings () {returner ny streng [] {"/ s / api / *"}; }

Her kan vi se en metode til at skabe den sammenhæng, der er knyttet til servleten, nøjagtigt som vi tidligere har set for rodkonteksten. Vi har også en metode til at specificere servlets tilknytninger, som i web.xml.

4. Forældre- og barnekontekster

Indtil videre har vi set to hovedtyper af sammenhænge: rod-webapplikationskonteksten og dispatcherservlet-sammenhænge. Så har vi måske et spørgsmål: er disse sammenhænge relaterede?

Det viser sig, at ja, det er de. Faktisk, rodkonteksten er overordnet til hver dispatcherservlet-kontekst. Således er bønner defineret i rodwebapplikationskonteksten synlige for hver dispatcherservlet-kontekst, men ikke omvendt.

Så typisk bruges rodkonteksten til at definere servicebønner, mens afsenderkonteksten indeholder de bønner, der specifikt er relateret til MVC.

Bemærk, at vi også har set måder at oprette dispatcherservlet-konteksten programmatisk på. Hvis vi manuelt indstiller dets overordnede, tilsidesætter Spring ikke vores beslutning, og dette afsnit gælder ikke længere.

I enklere MVC-applikationer er det tilstrækkeligt at have en enkelt kontekst tilknyttet den eneste dispatcherservlet. Der er ikke behov for alt for komplekse løsninger!

Forholdet mellem forældre og barn bliver stadig nyttigt, når vi har konfigureret flere afsenderservlets. Men hvornår skal vi gider at have mere end en?

Generelt, vi erklærer flere afsenderservlets når vi har brug for flere sæt MVC-konfiguration. For eksempel kan vi have en REST API sammen med en traditionel MVC-applikation eller et usikret og sikkert afsnit på et websted:

Bemærk: når vi forlænger AbstractDispatcherServletInitializer (se afsnit 3.4) registrerer vi både en rod-webapplikationskontekst og en enkelt dispatcherservlet.

Så hvis vi vil have mere end en servlet, har vi brug for flere AbstractDispatcherServletInitializer implementeringer. Vi kan dog kun definere en rodkontekst, ellers starter applikationen ikke.

Heldigvis er createRootApplicationContext metode kan vende tilbage nul. Således kan vi have en AbstractContextLoaderInitializer og mange AbstractDispatcherServletInitializer implementeringer, der ikke skaber en rodkontekst. I et sådant scenario tilrådes det at bestille initialiseringerne med @Bestille eksplicit.

Bemærk også, at AbstractDispatcherServletInitializer registrerer servlet under et givet navn (afsender) og selvfølgelig kan vi ikke have flere servlets med samme navn. Så vi er nødt til at tilsidesætte getServletName:

@ Override-beskyttet streng getServletName () {returner "en anden afsender"; }

5. A Forældre og barns kontekst Eksempel

Antag, at vi har to områder af vores applikation, for eksempel en offentlig, som er tilgængelig for hele verden og en sikret, med forskellige MVC-konfigurationer. Her definerer vi bare to controllere, der udsender en anden besked.

Antag også, at nogle af controllerne har brug for en tjeneste, der har betydelige ressourcer; en allestedsnærværende sag er vedholdenhed. Derefter vil vi kun starte denne tjeneste én gang for at undgå at fordoble dens ressourceforbrug, og fordi vi tror på ikke gentag dig selv-princippet!

Lad os nu gå videre med eksemplet.

5.1. Den delte tjeneste

I vores hej verdenseksempel slog vi os til en enklere hilsen-tjeneste i stedet for vedholdenhed:

pakke com.baeldung.contexts.services; @Service offentlig klasse GreeterService {@Resource privat hilsenhilsen; public String greet () {return greeting.getMessage (); }}

Vi erklærer tjenesten i rod-webapplikationskonteksten ved hjælp af komponentscanning:

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

Vi foretrækker måske XML i stedet:

5.2. Controllerne

Lad os definere to enkle controllere, der bruger tjenesten og sender en hilsen:

pakke com.baeldung.contexts.normal; @Controller offentlig klasse HelloWorldController {@Autowired private GreeterService greeterService; @RequestMapping (sti = "/ velkommen") offentlig ModelAndView helloWorld () {String besked = "

Normal "+ greeterService.greet () +"

"; returner ny ModelAndView (" velkommen "," besked ", besked);}} //" Sikker "Controller-pakke com.baeldung.contexts.secure; Strengmeddelelse ="

Sikker "+ greeterService.greet () +"

";

Som vi kan se, ligger controllerne i to forskellige pakker og udskriver forskellige meddelelser: den ene siger "normal", den anden "sikker".

5.3. Dispatcher Servlet-sammenhænge

Som vi sagde tidligere, vil vi have to forskellige dispatcherservlet-sammenhænge, ​​en til hver controller. Så lad os definere dem i Java:

// Normal kontekst @Configuration @EnableWebMvc @ComponentScan (basePackages = {"com.baeldung.contexts.normal"}) offentlig klasse NormalWebAppConfig implementerer WebMvcConfigurer {// ...} // "Sikker" kontekst @Configuration @EnableWebMvc @ComponentScan basePackages = {"com.baeldung.contexts.secure"}) offentlig klasse SecureWebAppConfig implementerer WebMvcConfigurer {// ...}

Eller hvis vi foretrækker det i XML:

5.4. Samler det hele

Nu hvor vi har alle brikkerne, er vi bare nødt til at bede Spring om at binde dem op. Husk, at vi har brug for at indlæse rodkonteksten og definere de to dispatcherservlets. Selvom vi har set flere måder at gøre det på, fokuserer vi nu på to scenarier, en Java og en XML. Lad os starte med Java.

Vi definerer en AbstractContextLoaderInitializer for at indlæse rodkonteksten:

@ Override beskyttet WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = ny AnnotationConfigWebApplicationContext (); rootContext.register (RootApplicationConfig.class); returnere rootContext; } 

Derefter skal vi oprette de to servlets, så vi definerer to underklasser af AbstractDispatcherServletInitializer. For det første den “normale”:

@ Override beskyttet WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext normalWebAppContext = ny AnnotationConfigWebApplicationContext (); normalWebAppContext.register (NormalWebAppConfig.class); returner normalWebAppContext; } @ Override beskyttet streng [] getServletMappings () {returner ny streng [] {"/ api / *"}; } @ Override-beskyttet streng getServletName () {returner "normal-afsender"; } 

Derefter den "sikre", der indlæser en anden kontekst og kortlægges til en anden sti:

@ Override beskyttet WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = ny AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); returner secureWebAppContext; } @ Override-beskyttet streng [] getServletMappings () {returner ny streng [] {"/ s / api / *"}; } @ Override-beskyttet streng getServletName () {returner "sikker-afsender"; }

Og vi er færdige! Vi har lige anvendt det, vi rørte ved i de foregående sektioner.

Vi kan gøre det samme med web.xmligen bare ved at kombinere de stykker, vi hidtil har diskuteret.

Definer en rodapplikationskontekst:

  org.springframework.web.context.ContextLoaderListener 

En "normal" afsenderkontekst:

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

Og endelig en "sikker" kontekst:

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

6. Kombination af flere sammenhænge

Der er andre måder end forældre-barn til at kombinere flere konfigurationsplaceringer, at opdele store sammenhænge og bedre adskille forskellige bekymringer. Vi har allerede set et eksempel: når vi specificerer contextConfigLocation med flere stier eller pakker, bygger Spring en enkelt kontekst ved at kombinere alle definitioner af bønner, som om de var skrevet i en enkelt XML-fil eller Java-klasse i rækkefølge.

Vi kan dog opnå en lignende effekt med andre midler og endda bruge forskellige tilgange sammen. Lad os undersøge vores muligheder.

En mulighed er komponentscanning, som vi forklarer i en anden artikel.

6.1. Import af en sammenhæng til en anden

Alternativt kan vi få en kontekstdefinition til at importere en anden. Afhængigt af scenariet har vi forskellige typer import.

Import af en @Konfiguration klasse i Java:

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

Indlæser en anden type ressource, for eksempel en XML-kontekstdefinition, i Java:

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

Endelig med en XML-fil i en anden:

Således har vi mange måder at organisere de tjenester, komponenter, controllere osv., Der samarbejder om at skabe vores fantastiske applikation. Og det pæne er, at IDE'er forstår dem alle!

7. Spring Boot Web-applikationer

Spring Boot konfigurerer automatisk komponenterne i applikationen, så generelt er der mindre behov for at tænke på, hvordan man organiserer dem.

Under emhætten bruger Boot stadig forårsfunktioner, inklusive dem, vi har set indtil videre. Lad os se et par bemærkelsesværdige forskelle.

Spring Boot-webapplikationer, der kører i en integreret container, kører ikke nogen WebApplicationInitializer af design.

Hvis det er nødvendigt, kan vi skrive den samme logik i a SpringBootServletInitializer eller a ServletContextInitializer i stedet afhængigt af den valgte implementeringsstrategi.

For at tilføje servlets, filtre og lyttere som vist i denne artikel er det imidlertid ikke nødvendigt at gøre det. Faktisk registrerer Spring Boot automatisk alle servlet-relaterede bønner til containeren:

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

De således definerede objekter kortlægges i henhold til konventioner: filtre kortlægges automatisk til / *, det vil sige til enhver anmodning. Hvis vi registrerer en enkelt servlet, kortlægges den til /, ellers kortlægges hver servlet til sit bønnenavn.

Hvis ovenstående konventioner ikke fungerer for os, kan vi definere en FilterRegistrationBean, ServletRegistreringBønne, eller ServletListenerRegistrationBean i stedet. Disse klasser giver os mulighed for at kontrollere de fine aspekter ved registreringen.

8. Konklusioner

I denne artikel har vi givet et dybtgående overblik over de forskellige muligheder, der er tilgængelige for at strukturere og organisere en Spring-webapplikation.

Vi har udeladt nogle funktioner, især understøttelsen af ​​en delt kontekst i virksomhedsapplikationer, som i skrivende stund stadig mangler fra foråret 5.

Implementeringen af ​​alle disse eksempler og kodestykker findes i GitHub-projektet.