这个答案有两部分
- 提供关于SessionAttributes的一些通用信息
- 解答问题本身
Spring中的@SessionAttributes
@SessionAttributes
的javadoc指出,它应该被用来暂时存储属性:
使用这个功能来存储那些在特定处理程序对话期间暂时存储在会话中的对话属性。
Temporal boundaries of such a "conversation" are defined by the programmer explicitly, or more precisely, the programmer defines the completion of the conversation. They can do this through
SessionStatus
.
Here is the relevant part of the documentation and an example:
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 shown in the following example.
@Controller
@SessionAttributes("pet")
public class EditPetForm {
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
}
status.setComplete();
}
}
如果你想深入了解,可以研究以下源代码:
浏览问题
- 如果
@SessionAttributes
仅在HTTP会话中临时存储模型属性,并在对话结束时将其删除,为什么foobar
仍然显示在对Step2Controller
的请求中?
因为很可能你没有定义对话完成。
在我看来,它仍然存在于会话中。
完全正确。
我不明白文档中提到的“临时”和“处理程序的对话”是什么意思。
我猜这与
Spring WebFlow有关。(请参见
this介绍性文章)
看起来foobar
通常存储在会话中。
是的,请参见
DefaultSessionAttributeStore
你可能会问:使一些会话属性具有临时性,而另一些则没有,是什么原因?它们如何区分?答案可以在源代码中找到:
SessionAttributesHandler.java
#L146:
public void cleanupAttributes(WebRequest request) {
for (String attributeName : this.knownAttributeNames) {
this.sessionAttributeStore.cleanupAttribute(request, attributeName);
}
}
- 看起来只要在
Step1Controller
上有@SessionAttributes("foobar")
,Spring就会在处理请求后自动将foobar
从模型复制到会话中。
是的,它会这样做
- 看起来只要在
Step2Controller
上放置@SessionAttributes("foobar")
,Spring就会在请求之前将foobar
从会话中复制到模型中。
同样正确
最后,注意在
Step2Controller.doSomething(FooBar fooBar)
中,除了控制器类上的
@SessionAttributes("foobar")
之外,
我在FooBar
参数上没有任何注释。文档似乎表明我需要在方法参数上添加一个
@ModelAttribute
注释,例如
Step2Controller.doSomething(@ModelAttribute("foobar") FooBar fooBar)
或至少
Step2Controller.doSomething(@ModelAttribute FooBar fooBar)
。但是,即使在参数上没有任何注释,Spring仍然会找到会话变量。为什么?我怎么知道这个呢?请参见
方法参数部分:
如果方法参数未与此表中较早的任何值匹配,并且它是一个简单类型(由BeanUtils#isSimpleProperty确定),则将其解析为@RequestParam。否则,它将被解析为@ModelAttribute。
这是我一直对Spring感到困扰的事情之一:太多的事情发生在“神奇”的情况下,没有清晰的文档说明预期会发生什么。使用Spring多年的人可能只是对什么有效、什么无效有一个“感觉”,但是新开发人员看着代码只能相信它会神奇地完成它应该做的事情。在这里,我建议阅读参考文档,它可以给出如何描述Spring的某些具体行为的线索。
2020年10月11日更新:
Denis,这个自动将模型中的参数作为方法参数应用的功能只适用于接口吗?我发现如果FooBar是一个接口,Step2Controller.doSomething(FooBar fooBar)按照上面讨论的方式工作。但是如果FooBar是一个类,即使我在模型中有FooBar的实例,Step2Controller.doSomething(FooBar fooBar)会导致“找不到类FooBar的主要或默认构造函数”异常。即使使用@ModelAttribute也不起作用。我必须使用@ModelAttribute(“foobar”)。为什么类和接口在参数替换方面有不同的工作方式?
这听起来像是命名/ @SessionAttributes#names
有些问题。
我创建了一个 示例项目 来演示问题可能隐藏的位置。
该项目分为两部分:
- 尝试使用类
- 尝试使用接口
该项目的入口点是两个测试(请参见ClassFooBarControllerTest
和InterfaceFooBarControllerTest
)
我已经留下了注释来解释这里发生的事情。