理解Spring MVC Model Attribute和Session Attribute

澳门新葡亰3522平台游戏 7

作为一名 Java Web 应用开发者,你已经快速学习了
request(HttpServletRequest)和
session(HttpSession)作用域。在设计和构建 Java Web
应用时,理解这些作用域,如何将数据与对象和这些作用域交互是十分重要的。【在
StackOverflow
上有一篇文章可以帮助你快速了解
request 和 session 作用域】

SPRING MVC 作用域

当我开始用 Spring MVC 编写 Web 应用时,我发现 Spring model 和 session
attribute 有一点神秘,尤其当它们与我熟知的 HTTP request 和 session
作用域交互时。一个 Spring model 元素可以从我的 session 或者 request
中找到吗?如果是这样的话,我该如何控制?在这篇文章中,我希望讲解清楚
Spring MVC 的 model 与 session 是如何工作的。

澳门新葡亰3522平台游戏,SPRING 的 @MODELATTRIBUTE

有几种方法将数据或对象添加到 Spring 的 model
中。一般来说,数据或对象是通过 controller 层的一个注解添加进 Spring 的
model 中。在下面的例子中,使用 @ModelAttribute 添加一个名为
MyCommandBean 的实例给 key 值为『myRequestObject』的 model。

@CONTROLLER
PUBLIC CLASS MYCONTROLLER {

    @MODELATTRIBUTE("MYREQUESTOBJECT")
    PUBLIC MYCOMMANDBEAN ADDSTUFFTOREQUESTSCOPE() {
        SYSTEM.OUT.PRINTLN("INSIDE OF ADDSTUFFTOREQUESTSCOPE");
        MYCOMMANDBEAN BEAN = NEW MYCOMMANDBEAN("HELLO WORLD",42);
        RETURN BEAN;
    }

    @REQUESTMAPPING("/DOSOMETHING")
    PUBLIC STRING REQUESTHANDLINGMETHOD(MODEL MODEL, HTTPSERVLETREQUEST REQUEST) {
        SYSTEM.OUT.PRINTLN("INSIDE OF DOSOMETHING HANDLER METHOD");

        SYSTEM.OUT.PRINTLN("--- MODEL DATA ---");
        MAP MODELMAP = MODEL.ASMAP();
        FOR (OBJECT MODELKEY : MODELMAP.KEYSET()) {
            OBJECT MODELVALUE = MODELMAP.GET(MODELKEY);
            SYSTEM.OUT.PRINTLN(MODELKEY + " -- " + MODELVALUE);
        }

        SYSTEM.OUT.PRINTLN("=== REQUEST DATA ===");
        JAVA.UTIL.ENUMERATION REQENUM = REQUEST.GETATTRIBUTENAMES();
        WHILE (REQENUM.HASMOREELEMENTS()) {
            STRING S = REQENUM.NEXTELEMENT();
            SYSTEM.OUT.PRINTLN(S);
            SYSTEM.OUT.PRINTLN("==" + REQUEST.GETATTRIBUTE(S));
        }

        RETURN "NEXTPAGE";
    }

         //  ... THE REST OF THE CONTROLLER
}

在一个到达的 request 中,任何被 @ModelAttribute 注解的方法都会在
controller handler method 之前调用(就像上面例子中的
requestHandlingMethod 一样)。这些方法会赶在 handler method
执行之前将数据添加进一个 java.util.Map,然后加入 Spring model
中。可以用一个示例操作展示出来。我创建了两个 JSP 页面:index.jsp 和
nextpage.jsp。index.jsp 上的一个链接用于向 MyController 中的
requestHandlingMethod() 应用触发器发送一个
request。上面的代码中,requestHandlingMethod()
将『nextpage』作为下个视图的逻辑名返回,其在这个例子中会处理为
nextpage.jsp。

澳门新葡亰3522平台游戏 1

当这个小小的网址被修改为这种形式后,controller 的 System.out.println
展现了 @ModelAttribute 方法是如何在 handler method
之前执行的。同时也展现了 MyCommandBean 创建和加入 Spring model,并在
handler method 中可用的过程。

INSIDE OF ADDSTUFFTOREQUESTSCOPE
INSIDE OF DOSOMETHING HANDLER METHOD
--- MODEL DATA ---
MYREQUESTOBJECT -- MYCOMMANDBEAN [SOMESTRING=HELLO WORLD, SOMENUMBER=42]
=== REQUEST DATA ===
ORG.SPRINGFRAMEWORK.WEB.SERVLET.DISPATCHERSERVLET.THEME_SOURCE
==WEBAPPLICATIONCONTEXT FOR NAMESPACE 'DISPATCHER-SERVLET': STARTUP DATE [SUN OCT 13 21:40:56 CDT 2013]; ROOT OF CONTEXT HIERARCHY
ORG.SPRINGFRAMEWORK.WEB.SERVLET.DISPATCHERSERVLET.THEME_RESOLVER
==ORG.SPRINGFRAMEWORK.WEB.SERVLET.THEME.FIXEDTHEMERESOLVER@204AF48C
ORG.SPRINGFRAMEWORK.WEB.SERVLET.DISPATCHERSERVLET.CONTEXT
==WEBAPPLICATIONCONTEXT FOR NAMESPACE 'DISPATCHER-SERVLET': STARTUP DATE [SUN OCT 13 21:40:56 CDT 2013]; ROOT OF CONTEXT HIERARCHY
ORG.SPRINGFRAMEWORK.WEB.SERVLET.HANDLERMAPPING.PATHWITHINHANDLERMAPPING
==DOSOMETHING.REQUEST
ORG.SPRINGFRAMEWORK.WEB.SERVLET.HANDLERMAPPING.BESTMATCHINGPATTERN
==/DOSOMETHING.*
ORG.SPRINGFRAMEWORK.WEB.SERVLET.DISPATCHERSERVLET.LOCALE_RESOLVER
==ORG.SPRINGFRAMEWORK.WEB.SERVLET.I18N.ACCEPTHEADERLOCALERESOLVER@18FD23E4

现在问题变为了『Spring model 的数据存储在哪里?』是存储在标准 Java
request 作用域中么?答案是 ——
对的。。。就最终而言的话。就像你从上面的输出中读到的,MyCommandBean 在
model 中,但当 handler method 执行时还不在 request 对象中。的确,handler
method 执行之后,下个视图(本例中为 nextpage.jsp)显示之前为止,Spring
都没有将 model 数据作为 attribute 加入 request 中。

澳门新葡亰3522平台游戏 2

也可以通过输出存储在 index.jsp 与 nextpage.jsp 的 HttpServletRequest
中的 attribute 展现出来。我在两个页面中都布置了一个 JSP
代码块(如下面所示),用以展现 HttpServletRequest 的 attribute。

<HR />
<H3>REQUEST SCOPE (KEY==VALUES)</H3>
<%
    JAVA.UTIL.ENUMERATION<STRING> REQENUM = REQUEST.GETATTRIBUTENAMES();
    WHILE (REQENUM.HASMOREELEMENTS()) {
        STRING S = REQENUM.NEXTELEMENT();
        OUT.PRINT(S);
        OUT.PRINTLN("==" + REQUEST.GETATTRIBUTE(S));
%><BR />
<%
    }
%>

当应用启动,index.jsp 加载完毕,你可以看到在 request 作用域中没有
attribute。

澳门新葡亰3522平台游戏 3

在本例中,当『do something』被点击时执行 MyController 的 handler
method,然后会跳转并展示 nextpage.jsp。而 nextpage.jsp
中已经编写了相同的 JSP 代码块,同样提供了 request 作用域中的
attribute。瞧,当 nextpage.jsp 渲染后,显示出在 controller 中创建的
MyCommandBean model 被加进 HttpServletRequest 作用域中了!Spring model
attribute 的键值『myRequestObject』被复制后用作 request attribute
的键值。

澳门新葡亰3522平台游戏 4

所以下一个视图呈现之前,Spring model 数据已经在 handler method
执行之前(或者之间)被拷贝给了 HttpServletRequest。

使用 SPRING MODEL 与 REQUEST 的原因

你或许想知道为什么 Spring 使用 model attribute。为何不直接把数据加到
request 对象里?我在 Rod Johnson 等人的书籍《Professional Java
Development with the Spring
Framework》中找到了答案。这本书关于
Spring API 的部分有一点过时(基于 Spring 2.0
编写),但是我发现该书提供了一些对于 Spring
引擎运行的扩展解释。下面是书中 model 元素部分的引用:

直接将元素加入 HttpServletRequest(像 request attributes
一样)看起来就像在服务同样的目标。这样做的理由是当看到我们为 MVC
框架设置的 requirements
时,能够更明确。它应尽可能与视图无关,这意味着我们可以合并视图技术,并不受
HttpServletRequest 的束缚。

SPRING 的 @SESSIONATTRIBUTES

所以现在你知道了 Spring 如何管理 model 数据,与如何连接标准的 Http
request attribute 数据。那么关于 Spring 的 session 数据呢?

Spring 的 @SessionAttribute 在 controller 中用来指定哪一个 model
attributes 需要存储到 session。事实上,Spring
文档声明了
@SessionAttributes 注解『列举需要显式地存储 session
或一些交互用的存储空间内的 model attributes
名称。』另外说一下,『一些交互存储空间』表明了 Spring MVC
试图保持与技术无关联的设计思想。

事实上,@SessionAttributes 允许你做的就是告诉 Spring 哪一个 model
attributes 将在视图展现之前一同拷贝给
HttpSession。关于这一点同样可以用一个简短的代码来展示。

在 index.jsp 和 nextpage.jsp 中,我添加了额外的 JSP 代码块,使其显示
HttpSession attributes。

<H3>SESSION SCOPE (KEY==VALUES)</H3>
<%
  JAVA.UTIL.ENUMERATION<STRING> SESSENUM = REQUEST.GETSESSION()
    .GETATTRIBUTENAMES();
  WHILE (SESSENUM.HASMOREELEMENTS()) {
    STRING S = SESSENUM.NEXTELEMENT();
    OUT.PRINT(S);
    OUT.PRINTLN("==" + REQUEST.GETSESSION().GETATTRIBUTE(S));
%><BR />
<%
  }
%>

我使用 @SessionAttributes 注解 MyController,使其将同一个 model
attributes(myRequestObject)放入 Spring session 中。

@CONTROLLER
@SESSIONATTRIBUTES("MYREQUESTOBJECT")
PUBLIC CLASS MYCONTROLLER {
  ...
}

另外在 controller 的 handler method 中添加代码显示 HttpSession 中的
attributes(就像显示 HttpServletRequest 中的 attributes 一样)。

@SUPPRESSWARNINGS("RAWTYPES")
@REQUESTMAPPING("/DOSOMETHING")
PUBLIC STRING REQUESTHANDLINGMETHOD(MODEL MODEL, HTTPSERVLETREQUEST REQUEST, HTTPSESSION SESSION) {
  SYSTEM.OUT.PRINTLN("INSIDE OF DOSOMETHING HANDLER METHOD");

  SYSTEM.OUT.PRINTLN("--- MODEL DATA ---");
  MAP MODELMAP = MODEL.ASMAP();
  FOR (OBJECT MODELKEY : MODELMAP.KEYSET()) {
    OBJECT MODELVALUE = MODELMAP.GET(MODELKEY);
    SYSTEM.OUT.PRINTLN(MODELKEY + " -- " + MODELVALUE);
  }

  SYSTEM.OUT.PRINTLN("=== REQUEST DATA ===");
  JAVA.UTIL.ENUMERATION<STRING> REQENUM = REQUEST.GETATTRIBUTENAMES();
  WHILE (REQENUM.HASMOREELEMENTS()) {
    STRING S = REQENUM.NEXTELEMENT();
    SYSTEM.OUT.PRINTLN(S);
    SYSTEM.OUT.PRINTLN("==" + REQUEST.GETATTRIBUTE(S));
  }

  SYSTEM.OUT.PRINTLN("*** SESSION DATA ***");
  ENUMERATION<STRING> E = SESSION.GETATTRIBUTENAMES();
  WHILE (E.HASMOREELEMENTS()){
    STRING S = E.NEXTELEMENT();
    SYSTEM.OUT.PRINTLN(S);
    SYSTEM.OUT.PRINTLN("**" + SESSION.GETATTRIBUTE(S));
  }

  RETURN "NEXTPAGE";
}

现在,我们可以看见加上 @SessionAttributes 注解后,Spring MVC 处理一个
HTTP 请求之前、之间和之后的 session 对象情况。下面显示了结果。首先,当
index.jsp 显示时(请求被 Spring MVC 发送和处理之前),我们可以看见
HttpServletRequest 和 HttpSession 都没有 attribute 数据。

澳门新葡亰3522平台游戏 5

handler method 执行时(requestHandlingMethod),你可以看见 MyCommandBean
被添加进 Spring model attributes,但是还没有加入 HttpServletRequest 或
HttpSession 作用域。

澳门新葡亰3522平台游戏 6

但是 handler method 执行后和 nextpage.jsp 显示时,你可以看见 model
attribute 数据(MyCommandBean)已经作为一个 attribute 被复制给了
HttpServletRequest 和 HttpSession(拥有相同的 attribute key)。

澳门新葡亰3522平台游戏 7

控制 SESSION ATTRIBUTES

现在你已经理解了 Spring model 和 session attribute 数据如何添加进
HttpServletRequest 与 HttpSession。或许又开始关心怎么管理 Spring session
中的数据。Spring 提供了一个方法移除 Spring session
attributes,同时也会从 HttpSession 中移除(不需要删除整个
HttpSession)。简单地将一个 Spring SessionStatus 对象作为参数加入一个
controller handler method 中。在此方法中,使用 SessionStatus
对象结束这个 Spring session。

@REQUESTMAPPING("/ENDSESSION")
PUBLIC STRING NEXTHANDLINGMETHOD2(SESSIONSTATUS STATUS){
  STATUS.SETCOMPLETE();
  RETURN "LASTPAGE";
}

总结

希望这篇文章能够帮助你理解 Spring model 和 session
attributes。这并不神奇,仅仅是一个理解 HttpSession 和 HttpServletRequest
如何存储 Spring model 和 session attributes
的问题。我已经将展示用的代码放在了 Intertech Web site
上。如果你对继续探索与理解 Spring model 和 session
感兴趣,尽管从这里下载吧。