Atributi sesije u proljetnom MVC-u

1. Pregled

Pri razvoju web aplikacija često se moramo pozivati ​​na iste atribute u nekoliko prikaza. Na primjer, možemo imati sadržaj košarice koji treba biti prikazan na više stranica.

Dobro mjesto za pohranu tih atributa je u korisničkoj sesiji.

U ovom uputstvu usredotočit ćemo se na jednostavan primjer i ispitati dvije različite strategije za rad s atributom sesije:

  • Upotreba opsega proxyja
  • Koristiti @Atributi sesije bilješka

2. Postavljanje Mavena

Koristit ćemo pokretače Spring Boot za pokretanje našeg projekta i unošenje svih potrebnih ovisnosti.

Za naše postavljanje potrebna je roditeljska deklaracija, web starter i timileaf starter.

Uključit ćemo i proljetni ispitni pokretač kako bismo pružili neke dodatne korisnosti u našim jediničnim testovima:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot- test ispitivanja startera 

Najnovije verzije ovih ovisnosti mogu se naći na Maven Central.

3. Primjer slučaja upotrebe

Naš primjer će implementirati jednostavnu "TODO" aplikaciju. Imat ćemo obrazac za stvaranje primjeraka TodoItem i prikaz popisa koji prikazuje sve TodoItems.

Ako stvorimo a TodoItem pomoću obrasca, naknadni pristupi obrascu unaprijed će se popuniti vrijednostima nedavno dodanih TodoItem. Koristit ćemo tnjegova značajka da pokaže kako se "sjećati" oblika vrijednosti koji su pohranjeni u opsegu sesije.

Naše 2 klase modela implementirane su kao jednostavni POJO:

javna klasa TodoItem {opis privatnog niza; private LocalDateTime createDate; // geteri i postavljači}
javna klasa TodoList proširuje ArrayDeque {}

Naše TodoList razred se proteže ArrayDeque kako bi nam omogućio prikladan pristup nedavno dodanoj stavci putem zaviritiLast metoda.

Trebat će nam 2 klase kontrolera: 1 za svaku strategiju koju ćemo pogledati. Imat će suptilne razlike, ali jezgra funkcionalnosti bit će zastupljena u obje. Svaka će imati 3 @RequestMappings:

  • @GetMapping (“/ obrazac”) - Ova će metoda biti odgovorna za inicijalizaciju obrasca i prikazivanje prikaza obrasca. Metoda će unaprijed popuniti obrazac s najnovijim dodanim TodoItem ako je TodoList nije prazna.
  • @PostMapping (“/ obrazac”) - Ova će metoda biti odgovorna za dodavanje poslanih TodoItem prema TodoList i preusmjeravanje na URL popisa.
  • @GetMapping (“/ todos.html”) - Ova metoda jednostavno će dodati TodoList prema Model za prikaz i prikaz prikaza popisa.

4. Korištenje opsega proxyja

4.1. Postaviti

U ovom postavljanju naš TodoList je konfiguriran kao opseg sesije @Grah koji je potpomognut opunomoćenikom. Činjenica da je @Grah "Proxy" znači da ga možemo ubrizgati u svoj singleton-opseg @Controller.

Budući da ne postoji sesija kada se kontekst inicijalizira, Spring će stvoriti proxy za TodoList ubrizgati kao ovisnost. Ciljana instanca TodoList instancirat će se prema potrebi kada se to zahtijeva zahtjevima.

Za detaljniju raspravu o opsegu graha u proljeće, pogledajte naš članak na tu temu.

Prvo definiramo svoj grah unutar a @Konfiguracija razred:

@Bean @Scope (value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) javni TodoList todos () {return new TodoList (); }

Dalje, grah proglašavamo ovisnošću za @Controller i ubrizgajte ga baš kao i bilo koju drugu ovisnost:

@Controller @RequestMapping ("/ scopedproxy") javna klasa TodoControllerWithScopedProxy {private TodoList todos; // mapiranje konstruktora i zahtjeva} 

Konačno, upotreba graha u zahtjevu jednostavno uključuje pozivanje njegovih metoda:

@GetMapping ("/ form") javni String showForm (model modela) {if (! Todos.isEmpty ()) {model.addAttribute ("todo", todos.peekLast ()); } else {model.addAttribute ("todo", novi TodoItem ()); } return "scopedproxyform"; }

4.2. Jedinstveno ispitivanje

Da bismo testirali našu implementaciju pomoću opsega proxyja, prvo konfiguriramo a SimpleThreadScope. To će osigurati da naši jedinični testovi točno simuliraju uvjete izvođenja koda koji testiramo.

Prvo definiramo a TestConfig i a CustomScopeConfigurer:

@Configuration javna klasa TestConfig {@Bean public CustomScopeConfigurer customScopeConfigurer () {CustomScopeConfigurer configurer = new CustomScopeConfigurer (); configurer.addScope ("sesija", novi SimpleThreadScope ()); return configurer; }}

Sada možemo započeti testiranjem da početni zahtjev obrasca sadrži neinicijaliziranu TodoItem:

@RunWith (SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc @import (TestConfig.class) public class TodoControllerWithScopedProxyIntegrationTest {// ... @Test public void whenFirstRequest_thenContainsUnintializedTodo () baca Izuzetak {MvcResult rezultat = mockMvc.perform (get ( "/ scopedproxy / obrazac ")) .andExpect (status (). isOk ()) .andExpect (model (). attributeExists (" todo ")) .andReturn (); TodoItem item = (TodoItem) result.getModelAndView (). GetModel (). Get ("todo"); assertTrue (StringUtils.isEmpty (item.getDescription ())); }} 

Također možemo potvrditi da naša prijava izdaje preusmjeravanje i da je sljedeći zahtjev za obrazac unaprijed popunjen s novo dodanim TodoItem:

@Test public void whenSubmit_thenSubsequentFormRequestContainsMostRecentTodo () baca izuzetak {mockMvc.perform (post ("/ scopedproxy / form") .param ("description", "newtodo")) .andExpect (status (). Is3xxRedirection ()) .and. ; Rezultat MvcResult = mockMvc.perform (get ("/ scopedproxy / form")) .andExpect (status (). IsOk ()) .andExpect (model (). AttributeExists ("todo")) .andReturn (); TodoItem item = (TodoItem) result.getModelAndView (). GetModel (). Get ("todo"); assertEquals ("newtodo", item.getDescription ()); }

4.3. Rasprava

Ključna značajka korištenja opsega proxy strategije je ta nema utjecaja na potpise metode mapiranja zahtjeva. To održava čitljivost na vrlo visokoj razini u odnosu na @SessionAttributes strategija.

Može biti korisno podsjetiti se da kontrolori imaju jednokrevetna zadani opseg.

To je razlog zašto moramo koristiti proxy umjesto da jednostavno ubrizgamo neproksirani grah s opsegom sesije. Ne možemo ubrizgati grah manjeg opsega u grah većeg opsega.

Pokušaj da se to učini, u ovom bi slučaju pokrenuo izuzetak s porukom koja sadrži: Opseg 'sesija' nije aktivan za trenutnu nit.

Ako smo spremni definirati svoj kontroler s opsegom sesije, mogli bismo izbjeći specificiranje a proxyMode. To može imati nedostataka, pogotovo ako je stvaranje kontrolera skupo jer bi se instanca kontrolera trebala stvoriti za svaku korisničku sesiju.

Imajte na umu da TodoList dostupan je drugim komponentama za injekcije. To može biti prednost ili nedostatak, ovisno o slučaju upotrebe. Ako je stavljanje graha na raspolaganje cijeloj aplikaciji problematično, instanca se umjesto toga može primijeniti na kontroler @SessionAttributes kao što ćemo vidjeti u sljedećem primjeru.

5. Korištenje @SessionAttributes Bilješka

5.1. Postaviti

U ovom postavljanju ne definiramo TodoList kao proljeće kojim se upravlja @Grah. Umjesto toga, mi proglasiti to kao @ModelAttribute i navedite @SessionAttributes napomena da bi se ona proširila na sesiju za kontroler.

Prvi put kad se pristupi našem kontroleru, Spring će instancirati instancu i smjestiti je u Model. Budući da i grah deklariramo u @SessionAttributes, Spring će pohraniti instancu.

Za detaljniju raspravu o @ModelAttribute u proljeće, pogledajte naš članak na tu temu.

Prvo deklariramo svoj grah pružajući metodu na kontroleru i označavamo je metodom @ModelAttribute:

@ModelAttribute ("todos") javni TodoList todos () {return new TodoList (); } 

Dalje, obavještavamo kontrolora da postupa s našim TodoList kao opseg sesije pomoću @SessionAttributes:

@Controller @RequestMapping ("/ sessionattributes") @SessionAttributes ("todos") javna klasa TodoControllerWithSessionAttributes {// ... ostale metode}

Konačno, da bismo koristili grah unutar zahtjeva, pružamo referencu na njega u potpisu metode a @RequestMapping:

@GetMapping ("/ form") javni String showForm (model modela, @ModelAttribute ("todos") TodoList todos) {if (! Todos.isEmpty ()) {model.addAttribute ("todo", todos.peekLast ()) ; } else {model.addAttribute ("todo", novi TodoItem ()); } return "sessionattributesform"; } 

U @PostMapping metodu, ubrizgavamo RedirectAttributes i nazovite addFlashAttribute prije povratka našeg RedirectView. Ovo je važna razlika u provedbi u odnosu na naš prvi primjer:

@PostMapping ("/ form") javni RedirectView create (@ModelAttribute TodoItem todo, @ModelAttribute ("todos") TodoList todos, RedirectAttributes attributes) {todo.setCreateDate (LocalDateTime.now ()); todos.add (todo); attributes.addFlashAttribute ("todos", todos); vrati novi RedirectView ("/ sessionattributes / todos.html"); }

Spring koristi specijaliziranu RedirectAttributes provedba Model za scenarije preusmjeravanja za podršku kodiranju parametara URL-a. Tijekom preusmjeravanja, svi atributi pohranjeni na Model obično bi bili dostupni okviru samo ako su bili uključeni u URL.

Pomoću addFlashAttribute govorimo okviru koji želimo svoje TodoList preživjeti preusmjeravanje bez potrebe za kodiranjem u URL-u.

5.2. Jedinstveno ispitivanje

Jedinstveno testiranje metode kontrole oblika obrasca identično je ispitivanju koje smo gledali u našem prvom primjeru. Test @PostMapping, međutim, malo je drugačije jer moramo pristupiti flash atributima kako bismo provjerili ponašanje:

@Test public void whenTodoExists_thenSubsequentFormRequestContainsesMostRecentTodo () baca izuzetak {FlashMap flashMap = mockMvc.perform (post ("/ sessionattributes / form") .param ("description", "newtodo")) .andExpect (status ()R. Is3 (status () R3). andReturn (). getFlashMap (); Rezultat MvcResult = mockMvc.perform (get ("/ sessionattributes / form") .sessionAttrs (flashMap)) .andExpect (status (). IsOk ()) .andExpect (model (). AttributeExists ("todo")) .andReturn ( ); TodoItem item = (TodoItem) result.getModelAndView (). GetModel (). Get ("todo"); assertEquals ("newtodo", item.getDescription ()); }

5.3. Rasprava

The @ModelAttribute i @SessionAttributes strategija za pohranu atributa u sesiji izravno je rješenje koje ne zahtijeva dodatnu konfiguraciju konteksta ili Spring-managed @Grahs.

Za razliku od našeg prvog primjera, potrebno je ubrizgati TodoList u @RequestMapping metode.

Osim toga, moramo koristiti flash atribute za scenarije preusmjeravanja.

6. Zaključak

U ovom smo članku razmotrili upotrebu opsega proxyja i @SessionAttributes kao 2 strategije za rad s atributima sesije u proljetnom MVC-u. Imajte na umu da će u ovom jednostavnom primjeru svi atributi pohranjeni u sesiji opstati samo tijekom života sesije.

Ako bismo trebali zadržati atribute između ponovnog pokretanja poslužitelja ili isteka vremena sesije, mogli bismo razmotriti upotrebu Spring Session za transparentno rukovanje spremanjem podataka. Za više informacija pogledajte naš članak o Proljetnom zasjedanju.

Kao i uvijek, sav kôd korišten u ovom članku dostupan je na GitHub-u.