Proljetni web konteksti

1. Uvod

Kada koristimo Spring u web aplikaciji, imamo nekoliko mogućnosti za organizaciju konteksta aplikacije koji sve to povezuju.

U ovom ćemo članku analizirati i objasniti najčešće opcije koje nudi Proljeće.

2. Kontekst korijenske web aplikacije

Svako proljetno webapp ima pridruženi kontekst aplikacije koji je vezan uz njegov životni ciklus: korijenski kontekst web aplikacije.

Ovo je stara značajka koja prethodi Spring Web MVC, tako da nije posebno vezana uz bilo koju tehnologiju web okvira.

Kontekst se pokreće kad se aplikacija pokrene, a uništava se kad se zaustavi zahvaljujući preslušavaču konteksta servleta. Najčešće vrste konteksta također se mogu osvježiti tijekom izvođenja, iako ne sve ApplicationContext implementacije imaju ovu sposobnost.

Kontekst u web aplikaciji uvijek je instanca WebApplicationContext. To se sučelje proširuje ApplicationContext s ugovorom o pristupu ServletContext.

Svejedno, aplikacije se obično ne bi trebale baviti onim detaljima implementacije: kontekst korijenske web aplikacije jednostavno je centralizirano mjesto za definiranje zajedničkog graha.

2.1. The ContextLoaderListener

Kontekstom korijenske web aplikacije opisanom u prethodnom odjeljku upravlja slušatelj klase org.springframework.web.context.ContextLoaderListener, koji je dio proljeće-mreža modul.

Prema zadanim postavkama, slušatelj će učitati kontekst XML aplikacije iz /WEB-INF/applicationContext.xml. Međutim, te zadane vrijednosti mogu se promijeniti. Primjerice, možemo upotrijebiti Java bilješke umjesto XML-a.

Ovog slušatelja možemo konfigurirati bilo u webapp opisu (web.xml datoteka) ili programski u okruženjima Servlet 3.x.

U sljedećim odjeljcima detaljno ćemo razmotriti svaku od ovih opcija.

2.2. Koristeći web.xml i kontekst XML aplikacije

Prilikom korištenja web.xml, slušatelja konfiguriramo kao i obično:

  org.springframework.web.context.ContextLoaderListener 

Možemo odrediti zamjensko mjesto konfiguracije XML konteksta s contextConfigLocation parametar:

 contextConfigLocation /WEB-INF/rootApplicationContext.xml 

Ili više mjesta, odvojenih zarezima:

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

Možemo čak koristiti i uzorke:

 contextConfigLocation /WEB-INF/*-context.xml 

U svakom slučaju, definiran je samo jedan kontekst, kombiniranjem svih definicija graha učitanih s određenih mjesta.

2.3. Koristeći web.xml i kontekst Java aplikacije

Možemo odrediti i druge vrste konteksta, osim zadanog na temelju XML-a. Pogledajmo, na primjer, kako umjesto toga koristiti konfiguraciju Java napomena.

Koristimo contextClass parametar da slušatelju kaže koju vrstu konteksta treba izvesti:

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

Svaka vrsta konteksta može imati zadano mjesto za konfiguraciju. U našem slučaju, AnnotationConfigWebApplicationContext nema, pa ga moramo pružiti.

Tako možemo navesti jednu ili više označenih klasa:

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

Ili možemo reći kontekstu da skenira jedan ili više paketa:

 contextConfigLocation com.baeldung.bean.config 

I, naravno, možemo kombinirati te dvije mogućnosti.

2.4. Programska konfiguracija sa Servlet 3.x

Verzija 3 Servlet API-ja izvršila je konfiguraciju putem web.xml datoteka potpuno neobavezna. Knjižnice mogu pružiti svoje web fragmente, koji su dijelovi XML konfiguracije koji mogu registrirati slušatelje, filtre, servlete i tako dalje.

Također, korisnici imaju pristup API-ju koji omogućuje programsko definiranje svakog elementa aplikacije zasnovane na servletima.

The proljeće-mreža modul koristi ove značajke i nudi svoj API za registraciju komponenata aplikacije kad se pokrene.

Proljeće skenira stazu aplikacije za primjerke datoteke org.springframework.web.WebApplicationInitializer razred. Ovo je sučelje s jednom metodom, void onStartup (ServletContext servletContext) baca ServletException, koji se poziva prilikom pokretanja aplikacije.

Pogledajmo sada kako možemo koristiti ovu mogućnost za stvaranje istih tipova konteksta korijenskih web aplikacija koje smo vidjeli ranije.

2.5. Korištenje Servleta 3.x i konteksta XML aplikacije

Počnimo s XML kontekstom, baš kao u odjeljku 2.2.

Provest ćemo gore spomenuto Na početku metoda:

javna klasa ApplicationInitializer implementira WebApplicationInitializer {@Override public void onStartup (ServletContext servletContext) baca ServletException {// ...}}

Razdvojimo implementaciju red po redak.

Prvo stvaramo korijenski kontekst. Budući da želimo koristiti XML, to mora biti kontekst aplikacije zasnovan na XML-u, a budući da smo u web okruženju, on mora implementirati WebApplicationContext također.

Stoga je prvi redak eksplicitna verzija datoteke contextClass parametar s kojim smo se ranije susreli, a kojim odlučujemo koju ćemo konkretnu implementaciju konteksta koristiti:

XmlWebApplicationContext rootContext = novi XmlWebApplicationContext ();

Zatim, u drugom retku, kažemo kontekstu odakle učitati njegove definicije graha. Opet, setConfigLocations je programski analogan programu contextConfigLocation parametar u web.xml:

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

Konačno, kreiramo ContextLoaderListener s korijenskim kontekstom i registrirajte ga u spremniku servleta. Kao što vidimo, ContextLoaderListener ima odgovarajući konstruktor koji uzima a WebApplicationContext i čini ga dostupnim aplikaciji:

servletContext.addListener (novi ContextLoaderListener (rootContext));

2.6. Korištenje Servleta 3.x i konteksta Java aplikacije

Ako želimo koristiti kontekst zasnovan na napomenama, mogli bismo izmijeniti isječak koda u prethodnom odjeljku kako bismo ga učinili instanciranim AnnotationConfigWebApplicationContext umjesto toga.

Međutim, pogledajmo specijaliziraniji pristup za postizanje istog rezultata.

The WebApplicationInitializer Klasa koju smo ranije vidjeli je sučelje opće namjene. Ispada da Spring pruža nekoliko specifičnijih implementacija, uključujući apstraktnu klasu tzv AbstractContextLoaderInitializer.

Njegov je posao, kao što i samo ime govori, stvaranje a ContextLoaderListener i registrirajte ga u spremniku za servlet.

Moramo mu samo reći kako izgraditi korijenski kontekst:

javna klasa AnnotationsBasedApplicationInitializer proširuje AbstractContextLoaderInitializer {@Override protected WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplication; rootContext.register (RootApplicationConfig.class); vrati rootContext; }}

Ovdje možemo vidjeti da više ne trebamo registrirati ContextLoaderListener, što nas spašava od malo šifre.

Imajte na umu i upotrebu Registar metoda koja je specifična za AnnotationConfigWebApplicationContext umjesto općenitijeg setConfigLocations: pozivanjem na njega možemo registrirati pojedinca @Konfiguracija komentirao klase kontekstom, izbjegavajući tako skeniranje paketa.

3. Konteksti servleta dispečera

Usredotočimo se sada na drugu vrstu konteksta aplikacije. Ovaj put, referirat ćemo se na značajku koja je specifična za Spring MVC, a ne na dio opće podrške Spring web aplikacija.

Proljetne MVC aplikacije imaju konfiguriran najmanje jedan dispečer servlet (ali možda više njih, o tom ćemo slučaju razgovarati kasnije). Ovo je servlet koji prima dolazne zahtjeve, upućuje ih na odgovarajuću metodu kontrolera i vraća prikaz.

Svaki DispatcherServlet ima pridruženi kontekst aplikacije. Grahovi definirani u takvim kontekstima konfiguriraju servlet i definiraju MVC objekte poput kontrolera i razrješivača prikaza.

Pogledajmo kako prvo konfigurirati kontekst servleta. Neke dublje detalje razmotrit ćemo kasnije.

3.1. Koristeći web.xml i kontekst XML aplikacije

DispatcherServlet obično se deklarira u web.xml s imenom i preslikavanjem:

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

Ako nije drugačije naznačeno, ime servleta koristi se za određivanje XML datoteke za učitavanje. U našem ćemo primjeru koristiti datoteku WEB-INF / normal-webapp-servlet.xml.

Također možemo odrediti jedan ili više putova do XML datoteka, na sličan način ContextLoaderListener:

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

3.2. Koristeći web.xml i kontekst Java aplikacije

Kada želimo koristiti drugu vrstu konteksta, nastavljamo slično ContextLoaderListener, opet. Odnosno, mi preciziramo a contextClass parametar zajedno s prikladnim contextConfigLocation:

 normal-webapp-annotations org.springframework.web.servlet.DispatcherServlet contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext contextConfigLocation com.baeldung.contexts.confbANonfig.Normalg. 

3.3. Korištenje Servleta 3.x i konteksta XML aplikacije

Ponovno ćemo se osvrnuti na dvije različite metode za programsko proglašavanje a DispatcherServlet, a jedan ćemo primijeniti na XML kontekst, a drugi na Java kontekst.

Dakle, krenimo s generičkim WebApplicationInitializer i kontekst XML aplikacije.

Kao što smo ranije vidjeli, moramo implementirati Na početku metoda. Međutim, ovaj put ćemo stvoriti i registrirati i servlet dispečera:

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

Lako možemo povući paralelu između gornjeg koda i ekvivalenta web.xml elementi konfiguracije.

3.4. Korištenje Servleta 3.x i konteksta Java aplikacije

Ovaj ćemo put konfigurirati kontekst zasnovan na napomenama pomoću specijalizirane implementacije WebApplicationInitializer: AbstractDispatcherServletInitializer.

To je apstraktna klasa koja, osim stvaranja konteksta korijenske web aplikacije, kao što je prethodno viđeno, omogućuje nam registraciju jednog servleta dispečera s minimalnim uzorkom:

@Override zaštićen WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = new AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); vratiti secureWebAppContext; } @Override protected String [] getServletMappings () {return new String [] {"/ s / api / *"}; }

Ovdje možemo vidjeti metodu za stvaranje konteksta povezanog sa servletom, točno onakvu kakvu smo već vidjeli za korijenski kontekst. Također, imamo metodu za specificiranje preslikavanja servleta, kao u web.xml.

4. Konteksti roditelja i djeteta

Do sada smo vidjeli dvije glavne vrste konteksta: kontekst korijenske web aplikacije i kontekst servleta dispečera. Tada bismo mogli imati pitanje: jesu li ti konteksti povezani?

Ispada da jesu. Zapravo, korijenski kontekst je roditelj svakog konteksta servleta dispečera. Dakle, grah definiran u kontekstu korijenske web aplikacije vidljiv je svakom kontekstu servleta dispečera, ali ne i obrnuto.

Dakle, obično se korijenski kontekst koristi za definiranje graha usluge, dok dispečerov kontekst sadrži one grahove koji su posebno povezani s MVC.

Imajte na umu da smo vidjeli i načine kako programski stvoriti kontekst servleta dispečera. Ako ručno postavimo njegovog roditelja, tada Spring neće nadjačati našu odluku i ovaj se odjeljak više neće primjenjivati.

U jednostavnijim MVC aplikacijama dovoljno je imati jedan kontekst povezan s jedinim dispečerskim servletom. Nema potrebe za previše složenim rješenjima!

Ipak, odnos roditelja i djeteta postaje koristan kada imamo konfigurirano više servleta dispečera. Ali kada bismo se trebali truditi da ih imamo više?

Općenito, deklariramo više dispečerskih servleta kada trebamo više skupova MVC konfiguracije. Na primjer, možda imamo REST API uz tradicionalnu MVC aplikaciju ili nesigurni i sigurni odjeljak web stranice:

Napomena: kada produžimo AbstractDispatcherServletInitializer (pogledajte odjeljak 3.4), registriramo i kontekst korijenske web aplikacije i jedan servlet dispečera.

Dakle, ako želimo više servleta, trebamo više njih AbstractDispatcherServletInitializer implementacije. Međutim, možemo definirati samo jedan korijenski kontekst, inače se aplikacija neće pokrenuti.

Srećom, createRootApplicationContext metoda može vratiti null. Dakle, možemo ga imati AbstractContextLoaderInitializer i mnogi AbstractDispatcherServletInitializer implementacije koje ne stvaraju korijenski kontekst. U takvom je scenariju uputno inicijalizatore naručiti s @Narudžba eksplicitno.

Također, imajte na umu da AbstractDispatcherServletInitializer registrira servlet pod određenim imenom (dispečer) i, naravno, ne možemo imati više servleta s istim imenom. Dakle, moramo nadjačati getServletName:

@Override zaštićeni niz getServletName () {return "drugi-dispečer"; }

5. A Kontekst roditelja i djeteta Primjer

Pretpostavimo da imamo dva područja naše aplikacije, na primjer javno, svjetski dostupno i osigurano, s različitim MVC konfiguracijama. Ovdje ćemo samo definirati dva kontrolora koja izlažu drugačiju poruku.

Također, pretpostavimo da neki od kontrolora trebaju uslugu koja sadrži značajne resurse; sveprisutan slučaj je ustrajnost. Zatim ćemo tu uslugu htjeti pokrenuti samo jednom, kako bismo izbjegli udvostručavanje upotrebe resursa i zato što vjerujemo u princip Ne ponavljaj se!

Nastavimo sada s primjerom.

5.1. Zajednička usluga

U našem primjeru iz zdravog svijeta odlučili smo se za jednostavniju uslugu dobrodošlice umjesto ustrajnosti:

paket com.baeldung.contexts.services; @Service javna klasa GreeterService {@ Resurs izvorni pozdravni pozdrav; javni String greet () {return pozdrav.getMessage (); }}

Uslugu ćemo deklarirati u kontekstu korijenske web aplikacije, koristeći skeniranje komponenata:

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

Umjesto toga možda bismo više voljeli XML:

5.2. Kontroleri

Definirajmo dva jednostavna kontrolera koji koriste uslugu i ispisuju pozdrav:

paket com.baeldung.contexts.normal; @Controller javna klasa HelloWorldController {@Autowired private GreeterService greeterService; @RequestMapping (path = "/ welcome") public ModelAndView helloWorld () {String message = "

Uobičajeno "+ greeterService.greet () +"

"; return new ModelAndView (" welcome "," message ", message);}} //" Sigurni "paket kontrolera com.baeldung.contexts.secure; String message ="

Sigurno "+ greeterService.greet () +"

";

Kao što vidimo, kontroleri se nalaze u dva različita paketa i ispisuju različite poruke: jedna kaže "normalno", a druga "sigurno".

5.3. Konteksti poslužitelja dispečera

Kao što smo ranije rekli, imat ćemo dva različita konteksta servleta dispečera, po jedan za svaki kontroler. Dakle, definirajmo ih na Javi:

// Normalni kontekst @Configuration @EnableWebMvc @ComponentScan (basePackages = {"com.baeldung.contexts.normal"}) javna klasa NormalWebAppConfig implementira WebMvcConfigurer {// ...} // "Sigurni" kontekst @Configuration @EnableWebMvcc (@ basePackages = {"com.baeldung.contexts.secure"}) javna klasa SecureWebAppConfig implementira WebMvcConfigurer {// ...}

Ili, ako nam je draže, u XML-u:

5.4. Sve to zajedno

Sad kad imamo sve dijelove, trebamo samo reći Springu da ih ožiči. Prisjetimo se da moramo učitati korijenski kontekst i definirati dva servleta dispečera. Iako smo vidjeli više načina za to, sada ćemo se usredotočiti na dva scenarija, jedan Java i XML. Počnimo s Javom.

Mi ćemo definirati AbstractContextLoaderInitializer za učitavanje korijenskog konteksta:

@Override zaštićen WebApplicationContext createRootApplicationContext () {AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext (); rootContext.register (RootApplicationConfig.class); vrati rootContext; } 

Zatim, moramo stvoriti dva servleta, pa ćemo definirati dvije podrazrede AbstractDispatcherServletInitializer. Prvo, onaj "normalni":

@Override zaštićen WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext normalWebAppContext = new AnnotationConfigWebApplicationContext (); normalWebAppContext.register (NormalWebAppConfig.class); vratiti normalWebAppContext; } @Override protected String [] getServletMappings () {return new String [] {"/ api / *"}; } @Override zaštićeni niz getServletName () {return "normalni-dispečer"; } 

Zatim, onaj "sigurni", koji učitava drugačiji kontekst i preslikava se na drugi put:

@Override zaštićen WebApplicationContext createServletApplicationContext () {AnnotationConfigWebApplicationContext secureWebAppContext = new AnnotationConfigWebApplicationContext (); secureWebAppContext.register (SecureWebAppConfig.class); vratiti secureWebAppContext; } @Override protected String [] getServletMappings () {return new String [] {"/ s / api / *"}; } @Override zaštićeni niz getServletName () {return "secure-depecher"; }

I gotovi smo! Upravo smo primijenili ono što smo se dotakli u prethodnim odjeljcima.

To možemo učiniti i s web.xml, opet samo kombiniranjem dijelova o kojima smo do sada razgovarali.

Definirajte kontekst korijenske aplikacije:

  org.springframework.web.context.ContextLoaderListener 

"Normalni" dispečerski kontekst:

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

I, na kraju, "siguran" kontekst:

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

6. Kombiniranje više konteksta

Postoje i drugi načini osim roditelja-djeteta da kombiniraju više lokacija za konfiguraciju, podijeliti veliki kontekst i bolje razdvojiti različite probleme. Već smo vidjeli jedan primjer: kada odredimo contextConfigLocation s više staza ili paketa, Spring gradi jedan kontekst kombinirajući sve definicije graha, kao da su napisane u jednoj XML datoteci ili Java klasi, redom.

Međutim, sličan učinak možemo postići i drugim sredstvima, pa čak i zajedno koristiti različite pristupe. Ispitajmo naše mogućnosti.

Jedna je mogućnost skeniranje komponenata, što objašnjavamo u drugom članku.

6.1. Uvoz konteksta u drugi

Alternativno, možemo imati definiciju konteksta koja uvozi drugu. Ovisno o scenariju, imamo različite vrste uvoza.

Uvoz a @Konfiguracija tečaj na Javi:

Konfiguracija javne klase @Configuration @Import (SomeOtherConfiguration.class) {...}

Učitavanje neke druge vrste resursa, na primjer, definicije XML konteksta, u Javi:

@Configuration @ImportResource ("classpath: basicConfigForPropertiesTwo.xml") javna klasa Config {...}

Konačno, uključujući XML datoteku u drugu:

Dakle, imamo mnogo načina za organizaciju usluga, komponenata, kontrolera itd., Koji surađuju kako bi stvorili našu sjajnu aplikaciju. I lijepa stvar je što ih IDE-i sve razumiju!

7. Proljetne pokretačke web aplikacije

Spring Boot automatski konfigurira komponente aplikacije, pa je, općenito, manje potrebno razmišljati o tome kako ih organizirati.

Ipak, ispod haube Boot koristi značajke Spring, uključujući one koje smo do sada vidjeli. Pogledajmo nekoliko značajnih razlika.

Web aplikacije Spring Boot koje se izvode u ugrađenom spremniku ne pokreću se WebApplicationInitializer po dizajnu.

Ako je potrebno, možemo zapisati istu logiku u a SpringBootServletInitializer ili a ServletContextInitializer umjesto toga, ovisno o odabranoj strategiji implementacije.

Međutim, za dodavanje servleta, filtara i slušatelja, kao što se vidi u ovom članku, nije potrebno to učiniti. Zapravo, Spring Boot automatski registrira svaki grah povezan s servletom u spremnik:

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

Tako definirani objekti mapiraju se prema konvencijama: filtri se automatski mapiraju u / *, odnosno u svaki zahtjev. Ako registriramo jedan servlet, on se preslikava u /, u suprotnom, svaki se servlet preslikava u svoje ime graha.

Ako nam gore navedene konvencije ne uspiju, možemo definirati a FilterRegistrationBean, ServletRegistrationBean, ili ServletListenerRegistrationBean umjesto toga. Te klase omogućuju nam kontrolu finih aspekata registracije.

8. Zaključci

U ovom smo članku detaljno prikazali razne opcije dostupne za strukturiranje i organizaciju web aplikacije Spring.

Izostavili smo neke značajke, posebno podršku za zajednički kontekst u poslovnim aplikacijama, koja u vrijeme pisanja ovog članka još uvijek nedostaje u proljeću 5.

Provedbu svih ovih primjera i isječaka koda možete pronaći u projektu GitHub.