Spring MVC 中@SessionAttributes对象的生命周期和注入



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

  • 我有两个控制器,Step1ControllerStep2Controller
  • 两个控制器都在类级别使用@SessionAttributes("foobar")
  • Step1Controller在对@PostMapping的请求处理期间,使用model.addAttribute("foobar", new FooBar("foo", "bar"))向模型添加一个特殊的FooBar实例。
  • Step2Controller,在完全独立的HTTPPOST下调用,使用doSomething(FooBar fooBar)在其@PostMapping服务方法中获取FooBar实例。
  • 这句话都很棒!

但我不清楚它为什么有效的一些细节。

@SessionAttributesAPI文档部分说:

处理程序指示完成其会话后,将删除这些属性。因此,将此工具用于此类会话属性,这些属性应在特定处理程序的会话过程中临时存储在会话中。对于永久会话属性,例如用户身份验证对象,请改用传统的session.setAttribute方法。

  1. 如果@SessionAttributes仅暂时将模型属性存储在 HTTP 会话中并在会话结束时将其删除,为什么foobar仍显示在Step2Controller请求中?在我看来,它仍然在会议中。我不明白文档在提到"临时"和"处理程序的对话"时是什么意思。看起来foobar正常存储在会话中。
  2. 看起来,只要在Step1Controller@SessionAttributes("foobar"),Spring 就会在处理请求后自动将foobar从模型复制到会话。这在文档中有所暗示,但直到通过实验我才清楚。
  3. 看起来,通过将@SessionAttributes("foobar")放在Step2Controller上,Spring 在请求之前将会话中的foobar复制到模型中。从文档中我根本不清楚这一点。
  4. 最后,请注意,在Step2Controller.doSomething(FooBar fooBar)中,除了@SessionAttributes("foobar")之外,我对FooBar参数没有任何注释(但这是在控制器类上)。文档似乎表明我需要向方法参数添加@ModelAttribute注释,例如Step2Controller.doSomething(@ModelAttribute("foobar") FooBar fooBar)或至少Step2Controller.doSomething(@ModelAttribute FooBar fooBar)。但是 Spring 似乎仍然找到了会话变量,即使参数上根本没有注释。为什么?我怎么会知道呢?

这是一直困扰我关于春天的事情:太多的事情"神奇"发生,没有明确的记录预期会发生什么。我想使用Spring多年的人只是"感觉"什么有效,什么无效;但是,查看代码的新开发人员只需要相信它神奇地完成了它应该做的事情。

为什么我所描述的有用,特别是关于第一个问题?也许这样我也可以发展出这种"春天的感觉",本能地知道要唤起哪些咒语。

这个答案有两个部分

  1. 提供有关SessionAttributes的一些一般信息
  2. 通过问题本身

春季@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

通过问题

  1. 如果@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);
}
}
    看起来,
  1. 只要在Step1Controller@SessionAttributes("foobar"),Spring 就会在处理请求后自动将foobar从模型复制到会话。

是的,它会

    似乎
  1. 通过将@SessionAttributes("foobar")放在Step2Controller上,Spring 会在请求之前将会话中的foobar复制到模型。

也是真的

  1. 最后,请注意,在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存在一些问题。

我创建了一个示例项目来演示问题可能隐藏的位置。

该项目分为两部分:

  1. 尝试使用类
  2. 尝试使用接口

项目的入口点是两个测试(请参阅ClassFooBarControllerTestInterfaceFooBarControllerTest)

我留下了评论来解释这里发生的事情

最新更新