Spring MVC — сохранение объекта в сессии
Как сохранить объект в сессии при помощи Spring MVC? Например нам необходимо сохранять данные пользователя между запросами внутри сессии, чтобы не терять данные о сохраненных товарах в корзине. Для этого нужно хранить объект на протяжении всей сессии пользователя в нашем приложении.
1. Структура проекта
Проект полностью взят из Spring MVC – исправление проблем с русской кодировкой. Кодировка передачи параметров формы. Все настройки оттуда же. Приведу только скрин структуры:
2. Общая информация
Для сохранения объектов в сессии в Spring MVC предусмотрена специальная Type-Level аннотация @SessionAttributes. Она декларирует атрибуты сессии используемые определенным обработчиком (handler’ом). Обычно это список имен или типов модели атрибутов, которые должны быть явно сохранены в сессии. Общая форма записи:
1 2 3 4 |
@SessionAttributes(value = "user") //или @SessionAttributes("user") public class HomeController{ // ... } |
Мы указываем в аннотации @SessionAttributes перед классом какие объекты будут храниться в сессии. В примере указано, что мы будем хранить в сессии объект по имени user.
Сразу приведу листинг класса User, который и будет сохранятся в сессии:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package ru.javastudy.springMVC.model; import org.springframework.stereotype.Component; @Component public class User { private String name; private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } |
3. Контроллер Spring MVC, запись атрибута в сессии
Если вам необходимо сохранить данные объекта (объект) между запросами, то необходимо поместить этот объект в сессию. Рассмотрим пример вызова метода main() с входным атрибутом userJSP. Атрибут может быть передан, например, с формы представления (будет показана ниже в другом примере).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package ru.javastudy.springMVC.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView; import ru.javastudy.springMVC.model.User; @Controller @SessionAttributes(value = "userJSP") public class MainController { /*First method on start application*/ /*Попадаем сюда на старте приложения (см. параметры аннотации и настройки пути после деплоя) */ @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView main(@ModelAttribute("userJSP") User user) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("userJSP", new User()); modelAndView.setViewName("index"); return modelAndView; } @ModelAttribute("userJSP") public User createUser() { return new User(); } /*как только на index.jsp подтвердится форма <spring:form method="post" modelAttribute="userJSP" action="check-user">, то попадем вот сюда */ @RequestMapping(value = "/check-user") public ModelAndView checkUser(@ModelAttribute("userJSP") User user) { ModelAndView modelAndView = new ModelAndView(); //имя представления, куда нужно будет перейти modelAndView.setViewName("secondPage"); //записываем в атрибут userJSP (используется на странице *.jsp) объект user modelAndView.addObject("userJSP", user); return modelAndView; //после уйдем на представление, указанное чуть выше, если оно будет найдено. } } |
Здесь мы задали какое-то имя объекту user и далее передали его в новом ModelAndView. Объект userJSP будет сохранен в сессии и будет доступен между запросами.
4. Уничтожение объектов, объявленных в сессии
Если вам необходимо уничтожить объекты в сессии, то это можно сделать с помощью передачи в метод контроллера объекта SessionStatus sessionStatus, и вызова у него метода setComplete();
1 2 3 4 5 6 7 8 9 10 11 |
@Controller @SessionAttributes("userJSP") //Или @SessionAttributes(types = User.class) для записи по типам public class MainController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView main(@ModelAttribute("userJSP") User user, SessionStatus sessionStatus) { /*уничтожает объекты объявленные в @SessionAttributes(..) */ sessionStatus.setComplete(); return new ModelAndView("index", "userJSP", new User()); } |
После вызова метода setComplete() будет завершена Spring сессия и атрибуты, указанные в аннотации, будут удалены, но при этом сохранится HTTP сессия.
Дополнительный пример
Допустим у нас есть форма, куда вводится имя пользователя и пароль.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<!-- обратите внимание на spring тэги --> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags/form" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Index Page</title> </head> <body> <spring:form method="post" modelAttribute="userJSP" action="check-user"> Name: <spring:input path="name"/> (path="" - указывает путь, используемый в modelAttribute=''. в нашем случае User.name) <br/> Password: <spring:input path="password"/> <br/> <spring:button>Next Page</spring:button> </spring:form> </body> </html> |
После подтверждения формы и отправки данных, сработает метод checkUser() из контроллера MainController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Controller @SessionAttributes(value = "userJSP") public class MainController { //Остальной код не показан @RequestMapping(value = "/check-user") public ModelAndView checkUser(@ModelAttribute("userJSP") User user) { ModelAndView modelAndView = new ModelAndView(); //имя представления, куда нужно будет перейти modelAndView.setViewName("secondPage"); //записываем в атрибут userJSP (используется на странице *.jsp) объект user modelAndView.addObject("userJSP", user); return modelAndView; //после уйдем на представление, указанное чуть выше, если оно будет найдено. } |
После выхода из метода checkUser() вся форма (user.name и user.password) будет скопирована в http сессию, благодаря указанию аннотации у названия класса @SessionAttributes(value = «userJSP»).
5. Запуск приложения
Сначала запустим приложение с кодом из Spring MVC – Hello World
Открыв один и тот же адрес в разных окнах браузера мы увидим, что объект не сохранен.
Теперь то же самое с кодом из этой статьи:
Как видим объект сохранен и доступен на протяжении сессии.
Типичные ошибки
Ошибка HTTP Status 500 — Expected session attribute ‘имяАтрибута’ возникает когда контроллер не может найти сохраненные данные по имени атрибута.
1 2 3 4 5 6 7 |
HTTP Status 500 - Expected session attribute 'user' type Exception report message Expected session attribute 'user' description The server encountered an internal error that prevented it from fulfilling this request. exception org.springframework.web.HttpSessionRequiredException: Expected session attribute 'user' |
Например в нашем примере при первом заходе на «/» идет обращение к @ModelAttribute User user , но он ещё не инициализирован, поэтому происходит попытка найти это значение в сессии. Поскольку и там значения ещё нет, то вылазит вышеописанная ошибка. Если бы аннотации @SessionAttributes не было, то произошла бы инициализация объекта User user.
Чтобы убрать эту ошибку нужно сделать так, чтобы при обращении к атрибуту он уже был инициализирован (если до этого нигде не записывался, а в аннотации идет указание на этот атрибут). Это можно сделать так
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Controller @SessionAttributes("user") public class MainController { @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView main(@ModelAttribute User user, SessionStatus sessionStatus) { user.setName("userName"); return new ModelAndView("login", "user", new User()); } @ModelAttribute public User createUser(){ return new User(); } |
Последовательность вызова методов при запуске приложения будет такая.
Сначала идет обработка аннотации @RequestMapping(«/»..), внутри метода доходим до @ModelAttribute User user. Тут он не находит объект user и переходит к User createUser() где и происходит инициализация объекта. Затем подставляет значение в метод ModelAndView main() . *В этом примере кстати user создастся еще раз на строчке return new..
Может быть интересно
На сайте есть другая статья по работе с объектами в различных областях видимости, которая демонстрирует другой подход по сохранения объекта в сессии или запросе.