Sure, here is the translation: 生命周期和在 Spring MVC 中注入 @SessionAttributes 对象

huangapple go评论68阅读模式
英文:

Life span and injection of @SessionAttributes object in Spring MVC

问题

以下是您要翻译的内容:

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

  • 我有两个控制器,Step1ControllerStep2Controller
  • 两个控制器在类级别上都使用了@SessionAttributes("foobar")
  • 在其@PostMapping的请求处理过程中,Step1Controller使用model.addAttribute("foobar", new FooBar("foo", "bar"))将特殊的FooBar实例添加到模型中。
  • 在完全独立的HTTP POST 下调用的Step2Controller,在其@PostMapping服务方法中使用doSomething(FooBar fooBar)获取了FooBar实例。
  • 这一切都很好!

但我对其中的一些细节以及为什么它起作用感到不清楚。

@SessionAttributes API文档部分提到:

> 这些属性将在处理程序指示其会话完成后被删除。因此,对于那些在特定处理程序会话过程中应该在会话中临时存储的会话属性,请使用此功能。对于永久的会话属性,例如用户认证对象,请改为使用传统的session.setAttribute方法。

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

这是关于Spring一直让我困扰的事情之一:有太多的事情发生得“神奇”,没有清楚说明预期会发生什么。使用Spring多年的人可能会对什么有效果而不会产生什么效果有一种“感觉”,但是一个看着代码的新开发人员只能相信它会神奇地完成它应该做的事情。

为什么我所描述的能够起作用,特别是关于第一个问题?也许通过这种方式,我也可以培养出这种“Spring感”,本能地知道该调用哪些咒语。

英文:

I'm unclear on some subtleties of using @SessionAttributes in Spring MVC via Spring Boot 2.3.3.RELEASE.

  • I have two controllers, Step1Controller and Step2Controller.
  • Both controllers use @SessionAttributes("foobar") at the class level.
  • Step1Controller during its request handling for @PostMapping adds a special FooBar instance to the model using model.addAttribute("foobar", new FooBar("foo", "bar")).
  • Step2Controller, invoked under a completely independent HTTP POST, picks up the FooBar instance in its @PostMapping service method using doSomething(FooBar fooBar).
  • This all words great!

But I'm unclear on some details of why it works.

The @SessionAttributes API documentation says in part:

> 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.

  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". It would appear foobar is stored in the session normally.
  2. 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. That was sort of hinted at in the documentation, but it only became clear to me through experimentation.
  3. It would appear that by placing @SessionAttributes("foobar") on Step2Controller, Spring copies foobar from the session to the model before the request. This was not clear to me at all from the documentation.
  4. 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?

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.

Why does what I have described work, especially in reference to the first question? Maybe that way I too can develop this "Spring sense" to instinctively know which incantations to evoke.

答案1

得分: 3

@SessionAttributes在Spring中

@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(); 
        // ...
    }
}

如果您想深入了解,可以研究以下源代码:

浏览问题

> 1. 如果@SessionAttributes只是暂时将模型属性存储在HTTP会话中,并在对话结束时将其删除,为什么foobar仍然显示在发送到Step2Controller的请求中?

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

> 对我来说,它似乎仍然在会话中。

完全正确。

> 我不理解文档在提到“临时”和“处理程序对话”时是什么意思。

这可能与Spring WebFlow有关。(参见这篇文章的介绍)

> 似乎foobar通常存储在会话中。

是的,请参阅DefaultSessionAttributeStore

您可能会问:是什么使某些会话属性临时而另一些不是?它们如何区分? 答案可能可以在源代码中找到:

SessionAttributesHandler.java#L146

/**
 * 从会话中删除“已知”属性,即在{@code @SessionAttributes}中按名称列出的属性,或先前存储在模型中并按类型匹配的属性。
 * @param request 当前请求
 */
public void cleanupAttributes(WebRequest request) {
    for (String attributeName : this.knownAttributeNames) {
        this.sessionAttributeStore.cleanupAttribute(request, attributeName);
    }
}

> 2. 看起来只需在Step1Controller上使用@SessionAttributes("foobar"),Spring在处理请求后会自动将foobar从模型复制到会话中。

是的,它会

> 3. 看起来通过在Step2Controller上放置@SessionAttributes("foobar"),Spring会在处理请求之前将foobar从会话中复制到模型中。

同样正确

> 4. 最后,在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年11月10日更新

> 丹尼斯,仅将模型中的参数自动

英文:

this answer has two parts

  1. Giving some general information about SessionAttributes
  2. Going through the question itself

@SessionAttributes in Spring

The @SessionAttributes's javadoc states that it should be used to store attributes temporarily:

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

Temporal boundaries of such a "conversation" are defined by programmer explicitly, or to be more exact: programmer defines completion of conversation, they can do it via SessionStatus. Here is relevant part of documentation and 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 the following example shows:

@Controller
@SessionAttributes(&quot;pet&quot;) 
public class EditPetForm {
    @PostMapping(&quot;/pets/{id}&quot;)
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
        status.setComplete(); 
        // ...
    }
}

If you want to dig deep you can study the source code of:

Going through the question

> 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?

Because, most probably you have not defined conversation completion.

> It appears to me to still be in the session.

Exactly

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

I guess it's somehow related to the Spring WebFlow. (See this introductory article)

> It would appear foobar is stored in the session normally.

Yes, see DefaultSessionAttributeStore

You may ask here: What does make some session attributes temporal and some not? How are they distinguished?. The answer may be found in the source code:

SessionAttributesHandler.java#L146:

/**
 * Remove &quot;known&quot; 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);
	}
}

> 2. It would appear that simply by having @SessionAttributes(&quot;foobar&quot;) on Step1Controller, Spring will automatically copy foobar from the model to the session after handling the request.

Yes, it will

> 3. It would appear that by placing @SessionAttributes(&quot;foobar&quot;) on Step2Controller, Spring copies foobar from the session to the model before the request.

Also true

> 4. 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(&quot;foobar&quot;) (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(&quot;foobar&quot;) 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?

See Method Arguments section:

> 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.

Here I would suggest going through the reference documentation, it can give a clue how can you describe some specific behavior of Spring


10/11/2020 update:

> 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?

This sounds to me, that there is some issue with naming/@SessionAttributes#names.

I've created a sample project to demonstrate where the problem may be hidden.

The project has two parts:

  1. Attempts to use class
  2. Attempts to use an interface

The entry point to the project are the two tests (see ClassFooBarControllerTest and InterfaceFooBarControllerTest)

I've left comments to explain what is happening here

huangapple
  • 本文由 发表于 2020年8月30日 01:43:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/63649999.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定