分享

Spring MVC 学习案例

 BlazerOfIT 2012-03-09

Spring MVC 学习案例  

Spring MVC学习指南


以下这个学习案例是我最近学习Spring MVC时跟从一本书上的示例,原文中的示例代码有一些小错误,不过我在调试的过程中已经给予了修正,如还有其它错误,还请各位批评指正。

对于现有较成熟的Model-View-Control(MVC)框架而言,其解决的主要问题无外乎下

面几部分:

1. 将Web页面中的输入元素封装为一个(请求)数据对象。

2. 根据请求的不同,调度相应的逻辑处理单元,并将(请求)数据对象作为参数传入。

3. 逻辑处理单元完成运算后,返回一个结果数据对象。

4. 将结果数据对象中的数据与预先设计的表现层相融合并展现给用户。

各个MVC 实现固然存在差异,但其中的关键流程大致如上。结合一个实例,我们来看看这

几个关键流程在Spring MVC框架中的处理手法。

下面的实例,实现了一个常见的用户登录逻辑,即用户通过用户名和密码登录,系统对用

户名和密码进行检测,如果正确,则在页面上显示几条通知信息。如果登录失败,则返回失败

界面。

(示例中,表示层以JSP2.0实现。)

出于简洁考虑,这里的“用户名/密码”检测以及通知信息的生成均在代码中以硬编码实现。

首先来看登录界面

对应的index.html:

<html>

<body>

<form method="POST" action="login.do">

<p align="center">登录</p>

<br>

用户名:

<input type="text" name="username" >

<br>

密 码 :

<input type="password" name="password" >

<br>

<p>

<input type="submit" value="提交" name="B1">

<input type="reset" value="重置" name="B2">

</p>

</form>

</body>

</html>


很简单的一个登录界面,其中包含了一个用以输入用户名密码的form,针对此form的提

交将被发送到"login.do"

MVC 关键流程的第一步,即收集页面输入参数,并转换为请求数据对象。这个静态页面提

供了一个基本的输入界面,下面这些输入的数据将被发送至何处,将如何被转换为请求数据对

象?

现在来看接下来发发生的事情

当用户输入用户名密码提交之后,此请求被递交给Web 服务器处理,上面我们设定form

提交目标为"login.do",那么Web服务器将如何处理这个请求?

显然,标准Http 协议中,并没有以.do 为后缀的服务资源,这是我们自己定义的一种请

求匹配模式。此模式在web.xml中设定:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java./xml/ns/j2ee"

xmlns:xsi="http://www./2001/XMLSchema-instance"

xsi:schemaLocation="http://java./xml/ns/j2ee

http://java./xml/ns/j2ee/web-app_2_4.xsd"

version="2.4">

<servlet>

<servlet-name>Dispatcher</servlet-name>

<servlet-class>

org.springframework.web.servlet.DispatcherServlet

</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/Config.xml</param-value>

</init-param>

</servlet>

<servlet-mapping>

<servlet-name>Dispatcher</servlet-name>

<url-pattern>*.do</url-pattern>

</servlet-mapping>

</web-app>

Servlet定义

这里我们定义了请求分发Servlet,即:

org.springframework.web.servlet.DispatcherServlet

DispatcherServlet Spring MVC 中负责请_____调度的核心引擎,所有的请求将

由此Servlet 根据配置分发至各个逻辑处理单元。其内部同时也维护了一个

ApplicationContext实例。

我们在<init-param>节点中配置了名为“contextConfigLocation”的

Servlet参数,此参数指定了Spring配置文件的位置“/WEB-INF/Config.xml”。

如果忽略此设定,则默认为“/WEB-INF/<servlet name>-servlet.xml”,其

<servlet name>Servlet 名替换(在当前环境下,默认值也就是

/WEB-INF/Dispatcher-servlet.xml)。

⑵ 请求映射

我们将所有以.do结尾的请求交给Spring MVC进行处理。当然,也可以设为其他值,

.action.action等。

通过以上设定,Web 服务器将把登录界面提交的请求转交给Dispatcher 处理,

Dispatcher将提取请求(HttpServletRequest)中的输入数据,分发给对应的处理单元,

各单元处理完毕后,将输出页面返回给Web服务器,再由Web服务器返回给用户浏览器。

Dispatcher 根据什么分发这些请求?显然,我们还需要一个配置文件加以设定。这也就

是上面提及的Config.xml,此文件包含了所有的“请求/处理单元”关系映射设定,以及返回

时表现层的一些属性设置。

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

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

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

<beans>

<!--Definition of View Resolver -->

<bean id="viewResolver"

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

rceViewResolver">

<property name="viewClass">

<value>

org.springframework.web.servlet.view.JstlView

</value>

</property>

<property name="prefix">

<value>

/WEB-INF/view/

</value>

</property>

<property name="suffix">

<value>.jsp</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>

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

</beans>

Resolver设定

Resolver将把输出结果与输出界面相融合,为表现层提供呈现资源。

View ResolverviewClass参数

这里我们使用JSP页面作为输出,因此,设定为:

org.springframework.web.servlet.view.JstlView

其余可选的viewClass还有:

org.springframework.web.servlet.view.freemarker.FreeMarker

View(用于基于FreeMarker模板的表现层实现)

org.springframework.web.servlet.view.velocity.VelocityView

(用于基于velocity模板的表现层实现)

等。

⑶⑷ View Resolverprefixsuffix参数

指定了表现层资源的前缀和后缀,运行时,Spring 将为指定的表现层资源自动追加

前缀和后缀,以形成一个完整的资源路径。另参见

⑸ “请求/处理单元”关系映射

可以看到,这里我们将“/login.do”请求映射到处理单元LoginAction

<props>节点下可以有多个映射关系存在,目前我们只定义了一个。

LoginAction定义

这里定义了逻辑处理单元LoginAction 的具体实现,这里,LoginAction 的实现

类为net.xiaxin.action.LoginAction

LoginAction的请求数据对象

commandClass 参数源于LoginAction 的基类BaseCommandController

BaseCommandControlle 包含了请求数据封装和验证方法

BaseCommandController.bindAndValidate ) , 它将根据传入的

HttpServletRequest构造请求数据对象。

这里我们指定commandClass net.xiaxin.action.LoginInfo,这是一个非

常简单的Java Bean,它封装了登录请求所需的数据内容:

public class LoginInfo {

private String username;

private String password;

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

}

Spring会根据LoginActioncommandClass定义自动加载对应的LoginInfo

实例。

之后,对Http 请求中的参数进行遍历,并查找LoginInfo 对象中是否存在与之同

名的属性,如果找到,则将此参数值复制到LoginInfo对象的同名属性中.

请求数据转换完成之后,我们得到了一个封装了所有请求参数的Java 对象,并将此

对象作为输入参数传递给LoginAction

返回视图定义

对于这里的LoginAction 而言,有两种返回结果,即登录失败时返回错误界面,登

录成功时进入系统主界面。

对应我们配置了fail_viewsuccess_view两个自定义参数。

参数值将由Resolver进行处理,为其加上前缀后缀,如对于fail_view而言,实

际的视图路径为/WEB-INF/view/loginfail.jsp

之后,Resolver 会将LoginAction的返回数据与视图相融合,返回最终的显示界

面。

业务逻辑处理单元:

LoginAction.java

public class LoginAction extends SimpleFormController {

private String fail_view;

private String success_view;

protected ModelAndView onSubmit(

Object cmd,

BindException ex

)throws Exception {

LoginInfo loginInfo = (LoginInfo) cmd;

HashMap result_map = new HashMap();

if (login(loginInfo) == 0) {

result_map.put("logininfo", loginInfo);

List msgList = new LinkedList();

msgList.add("msg1");

msgList.add("msg2");

msgList.add("msg3");

result_map.put("messages", msgList);

return new

ModelAndView(this.getSuccess_view(), result_map);

} else {

result_map.put("failmsg", new String("Sorry, you input the wrong username or password!"));

return new ModelAndView(this.getFail_view(), result_map);

}

}

private int login(LoginInfo loginInfo) {

if ("Erica".equalsIgnoreCase(loginInfo.getUsername())

&& "mypass".equals(loginInfo.getPassword())) {

return 0;

}

return 1;

}

public String getFail_view() {

return fail_view;

}

public String getSuccess_view() {

return success_view;

}

public void setFail_view(String string) {

fail_view = string;

}

public void setSuccess_view(String string) {

success_view = string;

}

}

其中:

onSubmit方法

我们在子类中覆盖了父类的onSubmit方法;而onSubmit方法用于处理业务请求。

负责数据封装和请求分发的Dispatcher,将对传入的HttpServletRequest进行

封装,形成请求数据对象,之后根据配置文件,调用对应业务逻辑类的入口方法(这

里就是LoginAction)的onSubmit()方法,并将请求数据对象及其他相关资源引

用传入。

protected ModelAndView onSubmit(

Object cmd,

BindException ex

)

onSubmit方法包含了两个参数:Object cmdBindException ex

前面曾经多次提到请求数据对象,这个名为cmdObject型参数,正是传入的请求

数据对象的引用。

BindException ex参数则提供了数据绑定错误的跟踪机制。它作为错误描述工具,

用于向上层反馈错误信息。

Spring MVC中,BindException将被向上层表现层反馈,以便在表现层统一处

理异常情况(如显示对应的错误提示),这一机制稍后在“输入参数合法性校验”部

分再具体探讨。

onSubmit还有另外一个签名版本:

protected ModelAndView onSubmit(

HttpServletRequest request,

HttpServletResponse response,

Object cmd,

BindException ex

)

可以看到,类似ServletdoGet/doPost方法,此版本的onSubmit方法签名中

包含了Servlet规范中的HttpServletRequestHttpServletResponse以提

供与Web服务器的交互功能(如Session的访问)。此参数类型的onSubmit方法

的调用优先级较高。也就是说,如果我们在子类中同时覆盖了这两个不同参数的

onSubmit方法,那么只有此版本的方法被执行,另一个将被忽略。

将输入的请求数据对象强制转型为预定义的请求对象类型。

返回处理结果

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


登录失败后的页面loginfail.jsp:

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

<html>

<body>

<p>Login Fail!!!</p><c:out value="${failmsg}" />

</body>

</html>

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

messages”。如果当前循环索引为奇数,则在消息前追加一个“*”号(这个小特性在这里

似乎有些多余,但却为不熟悉JSTL 的读者提供了如何使用JSTL Core taglib 进行循环和

逻辑判断的样例)。

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

也就是其中引用的JSTL Core Taglib

<%@ taglib prefix="c" uri="http://java./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./taglibs/index.html)

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

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

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

${……}JSP2.0 中的Expression LanguageEL)的语法。它定义了一个表达式,

其中的表达式可以是一个常量(如上),也可以是一个具体的表达语句(如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,如果没有找到则依次到RequestSession

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}

${12}

结果为表达式计算结果,即整数值3

${i>1}

如果变量值i>1的话,将返回bool类型true。与上例比较,可以_____EL会自

动根据表达式计算结果返回不同的数据类型。

表达式的写法与java代码中的表达式编写方式大致相同。

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

var="item"

begin="0"

end="9"

step="1"

varStatus="var">

……

</c:forEach>

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

item,循环范围是从09,每次步长为1。而varStatus则定义了一个循环状态变量var

循环状态变量中保存了循环进行时的状态信息,包括以下几个属性:

属性 类型 说明

index int 当前循环索引号

count int 成员总数

first boolean 当前成员是否首位成员

last boolean 当前成员是否末尾成员

再看:

<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序列图:


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多