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>