• 沒有找到結果。

返回处理结果

在文檔中 Spring 开发指南 (頁 38-48)

Spring MVC

⑶ 返回处理结果

ModelAndView 类包含了逻辑单元返回的结果数据集和表现层信息。ModelAndView 本身起到关系保存的作用。它将被传递给 Dispatcher,由 Dispatcher 根据其中 保存的结果数据集和表现层设定合成最后的界面。

这里我们用到了两种签名版本的 ModelAndView 构造方法:

Ø public ModelAndView(String viewname) 返回界面无需通过结果数据集进行填充。

Ø public ModelAndView(String viewname, Map model)

返回界面由指定的结果数据集加以填充。可以看到,结果数据集采用了 Map 接口 实现的数据类型。其中包含了返回结果中的各个数据单元。关于结果数据集在界 面中的填充操作,可参见下面关于返回界面的描述。

上面这两个版本的构造子中,通过 viewname 指定了表示层资源。

另外,我们也可以通过传递 View 对象指定表示层资源。

Ø public ModelAndView(View view)

Ø public ModelAndView(View view, Map model) 我们可以结合 RedirectView 完成转向功能,如:

return new ModelAndView(

new RedirectView(“/redirected.jsp”

));

当然,我们也可以在带有 HttpServletRequest 参数的 onSubmit 方法实现中,通 过 HttpServletRequest/HttpServletResponse 完成 forward/redirect 功 能,这两种途径可以达到同样的效果。

最后,来看返回界面:

错误返回界面 loginfail.jsp 只是个纯 html 文件(为了与 View Resolver 中设 定的后缀相匹配,因此以.jsp 作为文件后缀),这里就不再浪费篇幅。

再看成功登录后的页面 main.jsp:

界面显示效果如下:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>

<html>

<body>

<p>Login Success!!!</p>

<p>Current User:

<c:out value="${logininfo.username}"/><br>

</p>

<p>Your current messages:</p>

<c:forEach items="${messages}"

var="item"

begin="0"

end="9"

step="1"

varStatus="var">

<c:if test="${var.index % 2 == 0}">

*

</c:if>

${item}<br>

</c:forEach>

</body>

</html>

页面逻辑非常简单,首先显示当前登录用户的用户名。然后循环显示当前用户的通知消息

“messages”。如果当前循环索引为奇数,则在消息前追加一个“*”号(这个小特性在这里 似乎有些多余,但却为不熟悉 JSTL 的读者提供了如何使用 JSTL Core taglib 进行循环和 逻辑判断的样例)。

实际上这只是个普通 JSTL/JSP 页面,并没有任何特殊之处,如果说有些值得研究的技术,

也就是其中引用的 JSTL Core Taglib

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>

上面这句话申明了页面中所引用的 taglib,指定其前缀为“c”,也就是说,在页面中,

所有以“c”为前缀,形同<c:xxxx>的节点都表明是此 taglib 的引用,在这里,也就是对 JSTL Core Lib 的引用。

这里需要注意的是,笔者所采用的 Web 容器为 Tomcat 5(支持 Servlet 2.4/JSP2.0 规范)以及 Apache JSTL 2.0(http://jakarta.apache.org/taglibs/index.html)。

<c:out value="${logininfo.username}"/>

<c:out>将 value 中的内 容输 出到当 前位置 ,这里 也就是 把 logininfo 对 象的

username 属性值输出到页面当前位置。

${ ……}

是 JSP2.0 中的 Expression Language(EL)的语法。它定义了一个表达式,

其中的表达式可以是一个常量(如上),也可以是一个具体的表达语句(如

forEach

循环体中 的情况)。典型案例如下:

Ø

${logininfo.username}

这表明引用 logininfo 对象的 username 属性。我们可以通过“.”操作符引 用对象的属性,也可以用“[]”引用对象属性,如${logininfo[username]}

与${logininfo.username}达到了同样的效果。

“[]”引用方式的意义在于,如果属性名中出现了特殊字符,如“.”或者“-”,

此时就必须使用“[]”获取属性值以避免语法上的冲突(系统开发时应尽量避免 这一现象的出现)。

与之等同的 JSP Script 大致如下:

LoginInfo logininfo =

(LoginInfo)session.getAttribute(“logininfo”);

String username = logininfo.getUsername();

可以看到,EL 大大节省了编码量。

这里引出的另外一个问题就是,EL 将从哪里找到 logininfo 对象,对于

${logininfo.username}这样的表达式而言,首先会从当前页面中寻找之前是

否定义了变量 logininfo,如果没有找到则依次到 Request、Session、

Application 范围内寻找,直到找到为止。如果直到最后依然没有找到匹配的 变量,则返回 null.

如果我们需要指定变量的寻找范围,可以在 EL 表达式中指定搜寻范围:

${pageScope.logininfo.username}

${requestScope.logininfo.username}

${sessionScope.logininfo.username}

${applicationScope.logininfo.username}

在 Spring 中,所有逻辑处理单元返回的结果数据,都将作为 Attribute 被放 置到 HttpServletRequest 对象中返回(具体实现可参见 Spring 源码中 org.springframework.web.servlet.view.InternalResourceView.

exposeModelAsRequestAttributes 方法的实现代码),也就是说 Spring MVC 中,结果数据对象默认都是

requestScope。因此,在 Spring MVC 中,

以下寻址方法应慎用:

${sessionScope.logininfo.username}

${applicationScope.logininfo.username}

Ø

${1+2}

<c:forEach items="${messages}"

var="item"

begin="0"

end="9"

step="1"

varStatus="var">

……

</c:forEach>

上面这段代码的意思是针对 messages 对象进行循环,循环中的每个循环项的引用变量为

<c:if test="${var.index % 2 == 0}">

*

</c:if>

这段代码演示了判定 Tag

<c:if>的使用方法。可以看到,其 test 属性指明了判定条件,

判定条件一般为一个 EL 表达式。

<c:if>并没有提供 else 子句,使用的时候可能有些不便,此时我们可以通过<c:choose>

tag 来达到类似的目的:

<c:choose>

<c:when test="${var.index % 2 == 0}">

*

</c:when>

<c:otherwise>

!

</c:otherwise>

</c:choose>

类似 Java 中的 switch 语句,<c:choose>提供了复杂判定条件下的简化处理手法。其

<c:when>子句类似 case 子句,可以出现多次。上面的代码,在奇数行时输出“*”号,

而偶数行时输出“!”。

通过<c:choose>改造后的输出页面:

至此,一个典型的请求/响应过程结束。通过这个过程,我们也了解了 Spring MVC 的核 心实现机制。对其进行总结,得到以下 UML 序列图:

基于模板的Web表示层技术 模板技术,如 Velocity 和 Freemarker 难以达到的。

笔者在 2001 年在一个原型项目中采用了 XSLT 作为表现层实现,由于当时 XSLT 尚不

笔者在项目开发中所用的 XSLT 编辑器为 StylusStudio 和 XmlSpy,目前这两款编 辑器可以算是 XSLT 开发的首选,提供了丰富的特性和可视化编辑功能。但即便如此,XLST 繁杂苛刻的语法和调试上的难度也为开发工作带来了极大的障碍。

此外,也许是最重要的一点,xslt 在性能上的表现尚不尽如人意。经过多年的发展,

XSLT 解析/合成器的性能相对最初已经大为改观,但依然与其他模板技术存在着较大差距。

据实地测试,FreeMarker 和 Velocity 对于同等复杂度的表现层逻辑,平均处理速度是

XSLT 的 10 倍以上,这是一个不得不正视的性能沟壑。同时,XSLT 的内存占用也是 FreeMarker 和 Velocity 的数倍有余(XSLT 中,每个节点都是一个 Java 对象,大量 对象的存储对内存占用极大,同时大量对象的频繁创建和销毁也对 JVM 垃圾收集产生了较 大负面影响)。在上述项目中,由于硬件上的充分冗余(8G RAM, 4CPU),才使得这些 性能上的影响相对微弱。

因此,目前在项目中大量引入 XSLT 技术尚需仔细考量。

2. Velocity

Velocity 是 Apache Jakarta 项目中的一个子项目,它提供了丰富强大的模板功能。

作为目前最为成熟的模板支持实现,Velocity 在诸多项目中得到了广泛应用,不仅 限于 Web 开发,在众多代码生成系统中,我们也可以看到 Velocity 的身影(如 Hibernate 中的代码生成工具)。

3. FreeMarker

FreeMarker 是 Velocity 之外的另一个模板组件。

与 Velocity 相 比 , FreeMarker 对 表 现 逻 辑 和 业 务 逻 辑 的 划 分 更 为 严 格 , Freemarker 在模板中不允许对 Servlet API 进行直接操作(而 Velocity 可以),

如 FreeMarker 中禁止对 HttpServletRequest 对象直接访问(但可以访问 HttpServletRequest 对象中的 Attribute)。通过更加严格的隔离机制,牵涉逻 辑处理的操作被强制转移到逻辑层。从而完全保证了层次之间的清晰性。

另外一个 Velocity 无法实现的特性,也是最具备实际意义的特性:FreeMarker 对 JSP Tag 提供了良好支持。这一点可能存在一点争议,JSP 技术的最大问题就是容易 数众多的成熟 Taglib,如 DisplayTag、Struts Menu 等,这些功能丰富,成熟可 靠的 Taglib,将为产品开发提供极大的便利。另一方面,这也为代码重用提供了另一 个可选途径,避免了大部分模板实现在这一点上的不足。

就笔者的经验,对于 Web 开发而言,FreeMarker 在生产效率和学习成本上更具优势,

而 Velocity 的相对优势在于更多第三方工具的支持和更广泛的开发和用户团体(然 而对于一个轻量级模板类库而言,这样的优势并不是十分明显)。

如果没有 Velocity 的技术储备,而又需要通过技术上的限定解决视图/模型的划分问

题,这里推荐采用 FreeMarker 作为 Spring MVC 中的表现层实现。以获得最好的(学 习、开发)成本受益。

下面,我们将对之前的 JSP DEMO 进行改造,以展示基于 FreeMarker 的 Spring MVC 应用技术。至于 XSLT 和 Velocity,Spring 官方文档中已经有了很全面的介绍,这里暂 且掠过,如果有兴趣,可以自行参阅 Spring Reference。(笔者完成此文时 Spring 版 本为 1.0.2,在 Spring 1.1 之后,官方文档中也增添了 FreeMarker 的相关介绍)

对于上面例子中的几个文件,需要进行改编的是 Config.xml 和登录后的主页面

(main.jsp,改编后为 main.ftl,后缀名 ftl 是 freemarker 模板的习惯命名方式)。

改编后的 Config.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="viewResolver"

class="org.springframework.web.servlet.view.freemarker.

FreeMarkerViewResolver">

<property name="viewClass">

<value>

org.springframework.web.servlet.view.freemarker.FreeMar kerView

</value>

</property>

<property name="cache"><value>false</value></property>

<property name="suffix"><value>.ftl</value></property>

</bean>

<bean id="freemarkerConfig"

class="org.springframework.web.servlet.view.freemarker.

FreeMarkerConfigurer">

<property name="templateLoaderPath">

<value>WEB-INF/view/</value>

</property>

</bean>

<!---Action Definition-->

<bean id="LoginAction" class="net.xiaxin.action.LoginAction">

<property name="commandClass">

<value>net.xiaxin.action.LoginInfo</value>

</property>

<property name="fail_view">

<value>loginfail</value></property>

<property name="success_view">

<value>main</value>

</property>

</bean>

<!--Request Mapping -->

<bean id="urlMapping"

class="org.springframework.web.servlet.handler.SimpleUr lHandlerMapping">

<property name="mappings">

<props>

<prop key="/login.do">LoginAction</prop>

</props>

</property>

</bean>

</beans>

可以看到,配置文件进行了局部修改:

由于我们采用 FreeMarker 作为表现层技术。原有的

viewResolver

的定义发 生了变化。

viewResolver class 被修改为:

org.springframework ……FreeMarkerConfigurer

view 属性被修改为:

org.springframework ……FreeMarkerView

suffix 属性被修改为 FreeMarker 模板文件默认后缀“.ftl”。

prefix 属性被取消,因为下面的

freemarkerConfig

中已经定义了模板路径。

增加了

freemarkerConfig

定义,并通过

templateLoaderPath

属性设定了 模板文件的存放路径(相对 Web Application 根目录)。

模板文件 main.ftl:

<html>

<head>

<title>Welcome!</title>

</head>

<body>

<h1>Current User: ${logininfo.username}</h1>

<#list messages as msg>

<#if msg_index % 2=0>

*

<#else>

!

</#if>

${msg}<br>

</#list>

</body>

</html>

在文檔中 Spring 开发指南 (頁 38-48)