Atribúty relácie na jar MVC

1. Prehľad

Pri vývoji webových aplikácií musíme často vo viacerých pohľadoch odkazovať na rovnaké atribúty. Napríklad môžeme mať obsah nákupného košíka, ktorý je potrebné zobraziť na viacerých stránkach.

Dobré miesto na uloženie týchto atribútov je v relácii používateľa.

V tejto príručke sa zameriame na jednoduchý príklad a preskúmajte 2 rôzne stratégie práce s atribútom relácie:

  • Používanie obmedzeného proxy servera
  • Používanie znaku @Atribúty relácie anotácia

2. Nastavenie Maven

Použijeme štartéry Spring Boot na zavedenie nášho projektu a na zavedenie všetkých potrebných závislostí.

Naše nastavenie vyžaduje vyhlásenie rodiča, webový štartér a štartovací štít brzlíka.

Do testov jednotky zahrnieme aj štartovací pružinový testér, ktorý nám poskytne ďalšiu užitočnosť:

 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 štartovacieho testu 

Najnovšie verzie týchto závislostí nájdete na serveri Maven Central.

3. Príklad použitia

Náš príklad bude implementovať jednoduchú aplikáciu „TODO“. Budeme mať formulár na vytváranie inštancií TodoItem a zoznamové zobrazenie, ktoré zobrazuje všetky TodoItems.

Ak vytvoríme a TodoItem pomocou formulára budú následné prístupy k formuláru vopred vyplnené hodnotami naposledy pridaných TodoItem. Použijeme tjeho vlastnosť demonštrovať, ako si „pamätať“ hodnoty formulárov ktoré sú uložené v rozsahu relácie.

Naše 2 modelové triedy sú implementované ako jednoduché POJO:

public class TodoItem {private String description; private LocalDateTime createDate; // zakladatelia a zakladatelia}
verejná trieda TodoList rozširuje ArrayDeque {}

Náš Zoznam úloh trieda predlžuje ArrayDeque nám umožňuje pohodlný prístup k naposledy pridanej položke prostredníctvom nakuknúťposledné metóda.

Budeme potrebovať 2 triedy ovládačov: 1 pre každú zo stratégií, na ktoré sa pozrieme. Budú mať jemné rozdiely, ale základná funkčnosť bude zastúpená v oboch. Každá bude mať 3 @RequestMappings:

  • @GetMapping („/ formulár“) - Táto metóda bude zodpovedná za inicializáciu formulára a vykreslenie zobrazenia formulára. Metóda vopred vyplní formulár s naposledy pridanými TodoItem ak Zoznam úloh nie je prázdny.
  • @PostMapping („/ formulár“) - Táto metóda bude zodpovedná za pridanie odoslaného TodoItem do Zoznam úloh a presmerovanie na adresu URL zoznamu.
  • @GetMapping („/ todos.html“) - Táto metóda jednoducho pridá Zoznam úloh do Model na zobrazenie a vykreslenie zoznamu.

4. Použitie zástupcu s rozsahom

4.1. Nastaviť

V tomto nastavení je náš Zoznam úloh je nakonfigurovaný ako rozsah relácie @Bean ktorý je podporovaný splnomocnencom. Skutočnosť, že @Bean je proxy znamená, že sme schopní ho vložiť do nášho singletonového rozsahu @ Kontrolór.

Pretože pri inicializácii kontextu neexistuje žiadna relácia, Spring vytvorí proxy servera Zoznam úloh vstreknúť ako závislosť. Cieľová inštancia Zoznam úloh budú podľa potreby inštancované podľa potreby.

Podrobnejšiu diskusiu o rozsahu fazule na jar nájdete v našom článku na túto tému.

Najskôr definujeme našu fazuľu v rámci a @ Konfigurácia trieda:

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

Ďalej deklarujeme fazuľu ako závislosť pre @ Kontrolór a vstreknite to rovnako ako do akejkoľvek inej závislosti:

@Controller @RequestMapping ("/ scopedproxy") verejná trieda TodoControllerWithScopedProxy {private TodoList todos; // konštruktor a mapovanie požiadaviek} 

Nakoniec, použitie fazule v žiadosti jednoducho zahŕňa volanie jej metód:

@GetMapping ("/ form") public String showForm (Model model) {if (! Todos.isEmpty ()) {model.addAttribute ("todo", todos.peekLast ()); } else {model.addAttribute ("todo", new TodoItem ()); } návrat "scopedproxyform"; }

4.2. Testovanie jednotiek

Aby sme mohli otestovať našu implementáciu pomocou servera s rozsahom, najskôr nakonfigurujeme a SimpleThreadScope. To zabezpečí, že naše jednotkové testy presne simulujú runtime podmienky kódu, ktorý testujeme.

Najskôr definujeme a TestConfig a a CustomScopeConfigurer:

@Configuration verejná trieda TestConfig {@Bean public CustomScopeConfigurer customScopeConfigurer () {CustomScopeConfigurer configurer = nový CustomScopeConfigurer (); configurer.addScope ("relácia", nová SimpleThreadScope ()); konfigurátor návratu; }}

Teraz môžeme začať testovaním, či počiatočná žiadosť formulára obsahuje neinicializáciu TodoItem:

@RunWith (SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc @Import (TestConfig.class) verejná trieda TodoControllerWithScopedProxyIntegrationTest {// ... @Test public void whenFirstRequest_thenContainsContpedsVýsledok = () formulár ")) .andExpect (status (). isOk ()) .andExpect (model (). attributeExists (" todo ")) .andReturn (); TodoItem item = (TodoItem) result.getModelAndView (). GetModel (). Get ("todo"); assertTrue (StringUtils.isEmpty (item.getDescription ())); }} 

Môžeme tiež potvrdiť, že náš príspevok vydáva presmerovanie a že následná žiadosť o formulár je vopred vyplnená novo pridaným TodoItem:

@Test public void whenSubmit_thenSubsequentFormRequestContainsMostRecentTodo () vyvolá výnimku {mockMvc.perform (post ("/ scopedproxy / form") .param ("description", "newtodo")). AndExpect (status (). Is3xxRedirection ()) ; MvcResult result = 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. Diskusia

Kľúčovou vlastnosťou použitia stratégie proxy s rozsahom je to nemá to žiadny vplyv na podpisy metódy mapovania požiadavky. Toto udržuje čitateľnosť na veľmi vysokej úrovni v porovnaní s @SessionAttributes stratégia.

Môže byť užitočné pripomenúť, že kontrolóri majú singleton rozsah v predvolenom nastavení.

To je dôvod, prečo musíme použiť proxy server namiesto toho, aby sme jednoducho vstrekli neexxikovanú fazuľu s rozsahom relácie. Fazuľu s menším rozsahom nemôžeme vpichnúť do fazule s väčším rozsahom.

Pokus o to by v tomto prípade vyvolal výnimku so správou obsahujúcou: „Relácia“ rozsahu nie je pre aktuálne vlákno aktívna.

Ak sme ochotní definovať náš radič s rozsahom relácie, mohli by sme sa vyhnúť zadaniu a proxyMode. To môže mať nevýhody, najmä ak je vytváranie radiča nákladné, pretože pre každú reláciu používateľa by bolo potrebné vytvoriť inštanciu radiča.

Poznač si to Zoznam úloh je dostupný pre ďalšie komponenty na injekciu. Môže to byť výhoda alebo nevýhoda v závislosti od prípadu použitia. Ak je sprístupnenie fazule pre celú aplikáciu problematické, inštanciu je možné namiesto toho určiť pomocou radiča @SessionAttributes ako uvidíme v nasledujúcom príklade.

5. Pomocou @SessionAttributes Anotácia

5.1. Nastaviť

V tomto nastavení nedefinujeme Zoznam úloh ako riadený jarom @Bean. Namiesto toho sme vyhlásiť za a @ModelAttribute a uveďte @SessionAttributes anotácia, ktorá ho rozšíri na reláciu pre kontrolóra.

Pri prvom prístupe k nášmu radiču vytvorí Spring inštanciu a umiestni ju do Model. Keďže fazuľu deklarujeme aj dovnútra @SessionAttributes, Spring uloží inštanciu.

Pre hlbšiu diskusiu o @ModelAttribute na jar si pozrite náš článok o tejto téme.

Najskôr deklarujeme našu fazuľu poskytnutím metódy na radiči a metódu anotujeme @ModelAttribute:

@ModelAttribute ("todos") verejné TodoList todos () {vrátiť nový TodoList (); } 

Ďalej informujeme kontrolóra, aby s nami zaobchádzal Zoznam úloh ako rozsah relácie pomocou @SessionAttributes:

@Controller @RequestMapping ("/ sessionattributes") @SessionAttributes ("todos") verejná trieda TodoControllerWithSessionAttributes {// ... ďalšie metódy}

Nakoniec, aby sme fazuľu mohli použiť v žiadosti, poskytneme na ňu odkaz v podpise metódy a @RequestMapping:

@GetMapping ("/ form") public String showForm (Model model, @ModelAttribute ("todos") TodoList todos) {if (! Todos.isEmpty ()) {model.addAttribute ("todo", todos.peekLast ()) ; } else {model.addAttribute ("todo", new TodoItem ()); } návrat "sessionattributesform"; } 

V @PostMapping metóda, vstrekujeme RedirectAttributes a zavolaj addFlashAttribute pred vrátením našej RedirectView. Toto je dôležitý rozdiel v implementácii v porovnaní s našim prvým príkladom:

@PostMapping ("/ form") public RedirectView create (@ModelAttribute TodoItem todo, @ModelAttribute ("todos") TodoList todos, RedirectAttributes atribúty) {todo.setCreateDate (LocalDateTime.now ()); todos.add (todo); attributes.addFlashAttribute ("todos", todos); vrátiť nový RedirectView ("/ sessionattributes / todos.html"); }

Jar používa špecializované RedirectAttributes implementácia Model pre scenáre presmerovania na podporu kódovania parametrov adresy URL. Počas presmerovania sa všetky atribúty uložené na serveri Model by boli normálne dostupné rámcu, iba ak by boli zahrnuté do adresy URL.

Používaním addFlashAttribute hovoríme rámcu, ktorý chceme Zoznam úloh prežiť presmerovanie bez nutnosti kódovania do adresy URL.

5.2. Testovanie jednotiek

Jednotkové testovanie metódy radiča formulárového zobrazenia je totožné s testom, na ktorý sme sa pozreli v našom prvom príklade. Skúška @PostMapping, sa však trochu líši, pretože na overenie správania musíme získať prístup k atribútom flash:

@Test public void whenTodoExists_thenSubsequentFormRequestContainsesMostRecentTodo () vyvolá výnimku {FlashMap flashMap = mockMvc.perform (post ("/ sessionattributes / form") .param ("description", "newtodo")) .andExpect (status (). Is3xx andReturn (). getFlashMap (); MvcResult result = 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. Diskusia

The @ModelAttribute a @SessionAttributes Stratégia pre ukladanie atribútov v relácii je priamym riešením nevyžaduje žiadnu ďalšiu konfiguráciu kontextu alebo spravovanú na jar @Beans.

Na rozdiel od nášho prvého príkladu je potrebné podať injekciu Zoznam úloh v @RequestMapping metódy.

Okrem toho musíme pre scenáre presmerovania využívať atribúty flash.

6. Záver

V tomto článku sme sa pozreli na použitie rozsahovaných serverov proxy a @SessionAttributes ako 2 stratégie pre prácu s atribútmi relácie v jarnom MVC. Upozorňujeme, že v tomto jednoduchom príklade všetky atribúty uložené v relácii prežijú iba po celú dobu trvania relácie.

Ak by sme potrebovali zachovať atribúty medzi reštartmi servera alebo časovými limitmi relácií, mohli by sme zvážiť použitie Spring Session na transparentné zvládnutie ukladania informácií. Viac informácií nájdete v našom článku o jarnom zasadnutí.

Všetky kódy použité v tomto článku sú ako vždy k dispozícii na serveri GitHub.