Session Attributes in Spring MVC – Spring MVC中的会话属性

最后修改: 2018年 3月 16日


1. Overview


When developing web applications, we often need to refer to the same attributes in several views. For example, we may have shopping cart contents that need to be displayed on multiple pages.


A good location to store those attributes is in the user’s session.


In this tutorial, we’ll focus on a simple example and examine 2 different strategies for working with a session attribute:


  • Using a scoped proxy
  • Using the @SessionAttributes annotation

2. Maven Setup


We’ll use Spring Boot starters to bootstrap our project and bring in all necessary dependencies.

我们将使用Spring Boot启动器来引导我们的项目并引入所有必要的依赖。

Our setup requires a parent declaration, web starter, and thymeleaf starter.


We’ll also include the spring test starter to provide some additional utility in our unit tests:

我们还将包括spring test starter,以在我们的单元测试中提供一些额外的效用。


The most recent versions of these dependencies can be found on Maven Central.


3. Example Use Case


Our example will implement a simple “TODO” application. We’ll have a form for creating instances of TodoItem and a list view that displays all TodoItems.

我们的例子将实现一个简单的 “TODO “应用程序。我们将有一个用于创建TodoItem实例的表单和一个显示所有TodoItem的列表视图。

If we create a TodoItem using the form, subsequent accesses of the form will be prepopulated with the values of the most recently added TodoItem. We’ll use this feature to demonstrate how to “remember” form values that are stored in session scope.

如果我们使用表单创建了一个TodoItem,那么随后对该表单的访问就会预先填充最近添加的TodoItem的值。我们将使用这一功能来演示如何 “记住 “存储在会话范围中的表单值。

Our 2 model classes are implemented as simple POJOs:


public class TodoItem {

    private String description;
    private LocalDateTime createDate;

    // getters and setters
public class TodoList extends ArrayDeque<TodoItem>{


Our TodoList class extends ArrayDeque to give us convenient access to the most recently added item via the peekLast method.


We’ll need 2 controller classes: 1 for each of the strategies we’ll look at. They’ll have subtle differences but the core functionality will be represented in both. Each will have 3 @RequestMappings:


  • @GetMapping(“/form”) – This method will be responsible for initializing the form and rendering the form view. The method will prepopulate the form with the most recently added TodoItem if the TodoList is not empty.
  • @PostMapping(“/form”) – This method will be responsible for adding the submitted TodoItem to the TodoList and redirecting to the list URL.
  • @GetMapping(“/todos.html”) – This method will simply add the TodoList to the Model for display and render the list view.

4. Using a Scoped Proxy


4.1. Setup


In this setup, our TodoList is configured as a session-scoped @Bean that is backed by a proxy. The fact that the @Bean is a proxy means that we are able to inject it into our singleton-scoped @Controller.


Since there is no session when the context initializes, Spring will create a proxy of TodoList to inject as a dependency. The target instance of TodoList will be instantiated as needed when required by requests.


For a more in-depth discussion of bean scopes in Spring, refer to our article on the topic.

有关 Spring 中 Bean 作用域的更深入讨论,请参考我们关于该主题的文章

First, we define our bean within a @Configuration class:


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

Next, we declare the bean as a dependency for the @Controller and inject it just as we would any other dependency:


public class TodoControllerWithScopedProxy {

    private TodoList todos;

    // constructor and request mappings

Finally, using the bean in a request simply involves calling its methods:


public String showForm(Model model) {
    if (!todos.isEmpty()) {
        model.addAttribute("todo", todos.peekLast());
    } else {
        model.addAttribute("todo", new TodoItem());
    return "scopedproxyform";

4.2. Unit Testing


In order to test our implementation using the scoped proxy, we first configure a SimpleThreadScope. This will ensure that our unit tests accurately simulate runtime conditions of the code we are testing.


First, we define a TestConfig and a CustomScopeConfigurer:


public class TestConfig {

    public CustomScopeConfigurer customScopeConfigurer() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("session", new SimpleThreadScope());
        return configurer;

Now we can start by testing that an initial request of the form contains an uninitialized TodoItem:


public class TodoControllerWithScopedProxyIntegrationTest {

    // ...

    public void whenFirstRequest_thenContainsUnintializedTodo() throws Exception {
        MvcResult result = mockMvc.perform(get("/scopedproxy/form"))

        TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");

We can also confirm that our submit issues a redirect and that a subsequent form request is prepopulated with the newly added TodoItem:


public void whenSubmit_thenSubsequentFormRequestContainsMostRecentTodo() throws Exception {
      .param("description", "newtodo"))

    MvcResult result = mockMvc.perform(get("/scopedproxy/form"))
    TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
    assertEquals("newtodo", item.getDescription());

4.3. Discussion


A key feature of using the scoped proxy strategy is that it has no impact on request mapping method signatures. This keeps readability on a very high level compared to the @SessionAttributes strategy.


It can be helpful to recall that controllers have singleton scope by default.


This is the reason why we must use a proxy instead of simply injecting a non-proxied session-scoped bean. We can’t inject a bean with a lesser scope into a bean with greater scope.


Attempting to do so, in this case, would trigger an exception with a message containing: Scope ‘session’ is not active for the current thread.

在这种情况下,试图这样做会触发一个异常,其中包含一个消息。Scope ‘session’对当前线程不活跃

If we’re willing to define our controller with session scope, we could avoid specifying a proxyMode. This can have disadvantages, especially if the controller is expensive to create because a controller instance would have to be created for each user session.


Note that TodoList is available to other components for injection. This may be a benefit or a disadvantage depending on the use case. If making the bean available to the entire application is problematic, the instance can be scoped to the controller instead using @SessionAttributes as we’ll see in the next example.


5. Using the @SessionAttributes Annotation


5.1. Setup


In this setup, we don’t define TodoList as a Spring-managed @Bean. Instead, we declare it as a @ModelAttribute and specify the @SessionAttributes annotation to scope it to the session for the controller.


The first time our controller is accessed, Spring will instantiate an instance and place it in the Model. Since we also declare the bean in @SessionAttributes, Spring will store the instance.


For a more in-depth discussion of @ModelAttribute in Spring, refer to our article on the topic.


First, we declare our bean by providing a method on the controller and we annotate the method with @ModelAttribute:


public TodoList todos() {
    return new TodoList();

Next, we inform the controller to treat our TodoList as session-scoped by using @SessionAttributes:


public class TodoControllerWithSessionAttributes {
    // ... other methods

Finally, to use the bean within a request, we provide a reference to it in the method signature of a @RequestMapping:


public String showForm(
  Model model,
  @ModelAttribute("todos") TodoList todos) {
    if (!todos.isEmpty()) {
        model.addAttribute("todo", todos.peekLast());
    } else {
        model.addAttribute("todo", new TodoItem());
    return "sessionattributesform";

In the @PostMapping method, we inject RedirectAttributes and call addFlashAttribute before returning our RedirectView. This is an important difference in implementation compared to our first example:


public RedirectView create(
  @ModelAttribute TodoItem todo, 
  @ModelAttribute("todos") TodoList todos, 
  RedirectAttributes attributes) {
    attributes.addFlashAttribute("todos", todos);
    return new RedirectView("/sessionattributes/todos.html");

Spring uses a specialized RedirectAttributes implementation of Model for redirect scenarios to support the encoding of URL parameters. During a redirect, any attributes stored on the Model would normally only be available to the framework if they were included in the URL.


By using addFlashAttribute we are telling the framework that we want our TodoList to survive the redirect without needing to encode it in the URL.


5.2. Unit Testing


The unit testing of the form view controller method is identical to the test we looked at in our first example. The test of the @PostMapping, however, is a little different because we need to access the flash attributes in order to verify the behavior:


public void whenTodoExists_thenSubsequentFormRequestContainsesMostRecentTodo() throws Exception {
    FlashMap flashMap = mockMvc.perform(post("/sessionattributes/form")
      .param("description", "newtodo"))

    MvcResult result = mockMvc.perform(get("/sessionattributes/form")
    TodoItem item = (TodoItem) result.getModelAndView().getModel().get("todo");
    assertEquals("newtodo", item.getDescription());

5.3. Discussion


The @ModelAttribute and @SessionAttributes strategy for storing an attribute in the session is a straightforward solution that requires no additional context configuration or Spring-managed @Beans.


Unlike our first example, it’s necessary to inject TodoList in the @RequestMapping methods.

与我们的第一个例子不同,有必要在@RequestMapping methods中注入TodoList

In addition, we must make use of flash attributes for redirect scenarios.


6. Conclusion


In this article, we looked at using scoped proxies and @SessionAttributes as 2 strategies for working with session attributes in Spring MVC. Note that in this simple example, any attributes stored in session will only survive for the life of the session.

在这篇文章中,我们研究了使用范围代理和@SessionAttributes作为Spring MVC中处理会话属性的两种策略。请注意,在这个简单的例子中,存储在会话中的任何属性都只在会话的有效期内存在。

If we needed to persist attributes between server restarts or session timeouts, we could consider using Spring Session to transparently handle saving the information. Have a look at our article on Spring Session for more information.

如果我们需要在服务器重启或会话超时之间持续保存属性,我们可以考虑使用Spring Session来透明地处理保存信息。请看我们关于Spring Session的文章以了解更多信息。

As always, all code used in this article’s available over on GitHub.