java - Spring MVC 中@SessionAttributes 对象的生命周期和注入(inject)

标签 java spring spring-boot spring-mvc

我不清楚使用 @SessionAttributes 的一些微妙之处通过 Spring Boot 2.3.3.RELEASE 在 Spring MVC 中。

  • 我有两个 Controller ,Step1ControllerStep2Controller .
  • 两个 Controller 都使用 @SessionAttributes("foobar")在类(class)层面。
  • Step1Controller在处理 @PostMapping 的请求期间添加一个特殊的FooBar使用 model.addAttribute("foobar", new FooBar("foo", "bar")) 将实例添加到模型中.
  • Step2Controller , 在完全独立的 HTTP POST 下调用,拿起FooBar @PostMapping 中的实例使用 doSomething(FooBar fooBar) 的服务方法.
  • 这一切都很棒!

  • 但我不清楚它为什么起作用的一些细节。@SessionAttributes API 文档部分说明:

    Those attributes will be removed once the handler indicates completion of its conversational session. Therefore, use this facility for such conversational attributes which are supposed to be stored in the session temporarily during the course of a specific handler's conversation. For permanent session attributes, e.g. a user authentication object, use the traditional session.setAttribute method instead.


  • 如果 @SessionAttributes仅在 HTTP session 中临时存储模型属性并在 session 结束时将其删除,为什么 foobar仍然出现在对 Step2Controller 的请求中?在我看来,仍然在 session 中。当文档提到“临时”和“处理程序的对话”时,我不明白文档的含义。它会出现 foobar正常存储在 session 中。
  • 看来,只需拥有 @SessionAttributes("foobar")Step1Controller , Spring 会自动复制 foobar处理请求后从模型到 session 。这在文档中有所暗示,但只有通过实验我才变得清楚。
  • 似乎通过放置 @SessionAttributes("foobar")Step2Controller , Spring 副本foobar在请求之前从 session 到模型。从文档中我根本不清楚。
  • 最后,请注意 Step2Controller.doSomething(FooBar fooBar)我在 FooBar 上根本没有任何注释。参数,除了 @SessionAttributes("foobar") (但那是在 Controller 类上)。 documentation似乎表明我需要添加 @ModelAttribute方法参数注解,如Step2Controller.doSomething(@ModelAttribute("foobar") FooBar fooBar)或至少 Step2Controller.doSomething(@ModelAttribute FooBar fooBar) .但是 Spring 似乎仍然可以找到 session 变量,即使参数上根本没有注释。为什么?我怎么会知道这个?

  • 这是 Spring 一直困扰我的事情之一:太多的事情“神奇地”发生,没有明确的文档说明预期会发生什么。我想那些使用 Spring 多年的人只是对哪些有效哪些无效有一种“感觉”;但是查看代码的新开发人员只需要相信它神奇地完成了它应该做的事情。
    有人可以澄清为什么我所描述的工作,特别是在第一个问题上启发我吗?也许这样我也可以发展这种“ Spring 的感觉”,本能地知道要唤起哪些咒语。谢谢你。

    最佳答案

    这个答案有两个部分

  • 提供一些关于 SessionAttributes 的一般信息
  • 通过问题本身
  • @SessionAttributes在 Spring
    @SessionAttributes 's javadoc声明它应该用于临时存储属性:

    use this facility for such conversational attributes which are supposed to be stored in the session temporarily during the course of a specific handler's conversation.


    这种“对话”的时间边界由程序员明确定义,或者更准确地说:程序员定义对话的完成 , 他们可以通过 SessionStatus . Here是文档和示例的相关部分:

    On the first request, when a model attribute with the name, pet, is added to the model, it is automatically promoted to and saved in the HTTP Servlet session. It remains there until another controller method uses a SessionStatus method argument to clear the storage, as the following example shows:


    @Controller
    @SessionAttributes("pet") 
    public class EditPetForm {
        @PostMapping("/pets/{id}")
        public String handle(Pet pet, BindingResult errors, SessionStatus status) {
            if (errors.hasErrors) {
                // ...
            }
            status.setComplete(); 
            // ...
        }
    }
    
    如果您想深入挖掘,可以研究以下源代码:
  • SessionAttributesHandler

  • 通过问题
    1. If @SessionAttributes only stores model attributes in the HTTP session temporarily and removes them at the end of the conversation, why does foobar still show up in the request to Step2Controller?

    因为,很可能您还没有定义对话完成。

    It appears to me to still be in the session.


    确切地

    I don't understand what the docs mean when they refer to "temporarily" and "handler's conversation".


    我猜它与 Spring WebFlow 有某种关系。 . (见 this 介绍性文章)

    It would appear foobar is stored in the session normally.


    是的,见 DefaultSessionAttributeStore
    你可以在这里问:是什么使某些 session 属性具有时间性,而另一些则不是?它们是如何区分的? .答案可以在源代码中找到:
    SessionAttributesHandler.java #L146 :
    /**
     * Remove "known" attributes from the session, i.e. attributes listed
     * by name in {@code @SessionAttributes} or attributes previously stored
     * in the model that matched by type.
     * @param request the current request
     */
    public void cleanupAttributes(WebRequest request) {
        for (String attributeName : this.knownAttributeNames) {
            this.sessionAttributeStore.cleanupAttribute(request, attributeName);
        }
    }
    
    1. It would appear that simply by having @SessionAttributes("foobar") on Step1Controller, Spring will automatically copy foobar from the model to the session after handling the request.

    Yes, it will
    1. It would appear that by placing @SessionAttributes("foobar") on Step2Controller, Spring copies foobar from the session to the model before the request.

    Also true
    1. And finally, note that in Step2Controller.doSomething(FooBar fooBar) I don't have any annotation at all on the FooBar parameter, other than the @SessionAttributes("foobar") (but that is on the controller class). The documentation seemed to indicate I need to add a @ModelAttribute annotation to the method parameter, such as Step2Controller.doSomething(@ModelAttribute("foobar") FooBar fooBar) or at least Step2Controller.doSomething(@ModelAttribute FooBar fooBar). But Spring still seems to find the session variable, even with no annotation at all on the parameter. Why? How would I have known this?

    Method Arguments部分:

    If a method argument is not matched to any of the earlier values in this table and it is a simple type (as determined by BeanUtils#isSimpleProperty, it is a resolved as a @RequestParam. Otherwise, it is resolved as a @ModelAttribute.


    This is on of the things that has always bugged me about Spring: too many things happen "magically", with no clear documentation of what is expected to happen. People who use Spring for years I suppose just get a "feel" for what works and doesn't; but a new developer looking at the code just has to trust that it magically does what it's supposed to.


    在这里,我建议您阅读引用文档,它可以提供一个线索,您如何描述 Spring 的某些特定行为

    2020 年 10 月 11 日更新 :

    Denis, does this ability to automatically apply an argument from the model as a method argument only work with interfaces? I've found that if FooBar is an interface, Step2Controller.doSomething(FooBar fooBar) works as discussed above . But if FooBar is a class, even if I have an instance of FooBar in the model, Step2Controller.doSomething(FooBar fooBar) results in a "No primary or default constructor found for class FooBar" exception. Even @ModelAttribute won't wor k. I have to use @ModelAttribute("foobar"). Why do classes work differently from interfaces in parameter substitution?


    在我看来,命名/@SessionAttributes#names 存在一些问题.
    我创建了一个 sample project证明问题可能隐藏在哪里。
    该项目有两个部分:
  • 尝试使用类
  • 尝试使用接口(interface)

  • 该项目的入口点是两个测试(见 ClassFooBarControllerTestInterfaceFooBarControllerTest )
    我留下评论来解释这里发生了什么

    关于java - Spring MVC 中@SessionAttributes 对象的生命周期和注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63649999/

    相关文章:

    java - 找不到 EnableOAuth2Sso 注释的导入

    Spring security session 管理和 Spring MVC View 解析器错误

    groovy - Spring Boot - 从 webjar 覆盖索引页面

    java - Jayway JsonPath读取长Java

    java - 类中的构造函数不能应用于 Spring Boot Controller 的给定类型

    java - 如何使用 Quarkus 在 Swagger 中生成请求示例

    java - 从泛型类调用枚举静态方法

    spring - 覆盖 PropertyPlaceholderConfigurer 中的属性值

    Java 256 位 AES 基于密码的加密

    java - 无法插入 MS Access 数据库