我不清楚通过Spring Boot 2.3.3.RELEASE在Spring MVC中使用@SessionAttributes
的一些微妙之处。
- 我有两个控制器,
Step1Controller
和Step2Controller
。 - 两个控制器都在类级别使用
@SessionAttributes("foobar")
。 Step1Controller
在对@PostMapping
的请求处理期间,使用model.addAttribute("foobar", new FooBar("foo", "bar"))
向模型添加一个特殊的FooBar
实例。Step2Controller
,在完全独立的HTTPPOST
下调用,使用doSomething(FooBar fooBar)
在其@PostMapping
服务方法中获取FooBar
实例。- 这句话都很棒!
但我不清楚它为什么有效的一些细节。
@SessionAttributes
API文档部分说:
处理程序指示完成其会话后,将删除这些属性。因此,将此工具用于此类会话属性,这些属性应在特定处理程序的会话过程中临时存储在会话中。对于永久会话属性,例如用户身份验证对象,请改用传统的
session.setAttribute
方法。
- 如果
@SessionAttributes
仅暂时将模型属性存储在 HTTP 会话中并在会话结束时将其删除,为什么foobar
仍显示在Step2Controller
请求中?在我看来,它仍然在会议中。我不明白文档在提到"临时"和"处理程序的对话"时是什么意思。看起来foobar
正常存储在会话中。 - 看起来,只要在
Step1Controller
上@SessionAttributes("foobar")
,Spring 就会在处理请求后自动将foobar
从模型复制到会话。这在文档中有所暗示,但直到通过实验我才清楚。 - 看起来,通过将
@SessionAttributes("foobar")
放在Step2Controller
上,Spring 在请求之前将会话中的foobar
复制到模型中。从文档中我根本不清楚这一点。 - 最后,请注意,在
Step2Controller.doSomething(FooBar fooBar)
中,除了@SessionAttributes("foobar")
之外,我对FooBar
参数没有任何注释(但这是在控制器类上)。文档似乎表明我需要向方法参数添加@ModelAttribute
注释,例如Step2Controller.doSomething(@ModelAttribute("foobar") FooBar fooBar)
或至少Step2Controller.doSomething(@ModelAttribute FooBar fooBar)
。但是 Spring 似乎仍然找到了会话变量,即使参数上根本没有注释。为什么?我怎么会知道呢?
这是一直困扰我关于春天的事情:太多的事情"神奇"发生,没有明确的记录预期会发生什么。我想使用Spring多年的人只是"感觉"什么有效,什么无效;但是,查看代码的新开发人员只需要相信它神奇地完成了它应该做的事情。
为什么我所描述的有用,特别是关于第一个问题?也许这样我也可以发展出这种"春天的感觉",本能地知道要唤起哪些咒语。
这个答案有两个部分
-
提供有关
SessionAttributes
的一些一般信息 - 通过问题本身
春季@SessionAttributes
@SessionAttributes
的javadoc声明它应该用于临时存储属性:
将此工具用于此类会话属性,这些属性应在特定处理程序的会话过程中临时存储在会话中。
这种"对话"的时间边界是由程序员明确定义的,或者更准确地说:程序员定义对话的完成,他们可以通过SessionStatus
来完成。以下是文档和示例的相关部分:
在第一个请求中,当名称为
pet
的模型属性添加到模型中时,它会自动提升并保存在 HTTP Servlet 会话中。它一直保留在那里,直到另一个控制器方法使用SessionStatus
方法参数来清除存储,如以下示例所示:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete();
// ...
}
}
如果你想深入挖掘,你可以研究以下源代码:
SessionAttributesHandler
通过问题
- 如果
@SessionAttributes
仅暂时将模型属性存储在 HTTP 会话中并在会话结束时将其删除,为什么foobar
仍显示在Step2Controller
请求中?
因为,很可能您尚未定义会话完成。
在我看来,它仍然在会议中。
完全
我不明白文档在提到"临时"和"处理程序的对话"时是什么意思。
我想这与Spring WebFlow有某种关系。(请参阅此介绍性文章)
看起来
foobar
正常存储在会话中。
是的,请参阅DefaultSessionAttributeStore
您可能会在这里问:是什么使某些会话属性具有时间性,而某些属性不是?它们是如何区分的?答案可以在源代码中找到:
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);
}
}
看起来,
- 只要在
Step1Controller
上@SessionAttributes("foobar")
,Spring 就会在处理请求后自动将foobar
从模型复制到会话。
是的,它会
似乎
- 通过将
@SessionAttributes("foobar")
放在Step2Controller
上,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的一些特定行为
10/11/2020 更新:
Denis,这种自动将模型中的参数作为方法参数应用的功能仅适用于接口吗?我发现如果FooBar是一个接口,Step2Controller.doSomething(FooBar fooBar)的工作方式如上所述。但是,如果 FooBar 是一个类,即使我在模型中有一个 FooBar 的实例,Step2Controller.doSomething(FooBar fooBar) 也会导致"找不到类 FooBar 的主构造函数或默认构造函数"异常。即使@ModelAttribute也不会担心k。我必须使用@ModelAttribute("foobar")。为什么类的工作方式与参数替换中的接口不同?
这听起来,命名/@SessionAttributes#names
存在一些问题。
我创建了一个示例项目来演示问题可能隐藏的位置。
该项目分为两部分:
- 尝试使用类
- 尝试使用接口
项目的入口点是两个测试(请参阅ClassFooBarControllerTest
和InterfaceFooBarControllerTest
)
我留下了评论来解释这里发生的事情