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 Resolver的viewClass参数 这里我们使用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 Resolver的prefix和suffix参数 指定了表现层资源的前缀和后缀,运行时,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会根据LoginAction的commandClass定义自动加载对应的LoginInfo 实例。 之后,对Http 请求中的参数进行遍历,并查找LoginInfo 对象中是否存在与之同 名的属性,如果找到,则将此参数值复制到LoginInfo对象的同名属性中. 请求数据转换完成之后,我们得到了一个封装了所有请求参数的Java 对象,并将此 对象作为输入参数传递给LoginAction。 ⑻ 返回视图定义 对于这里的LoginAction 而言,有两种返回结果,即登录失败时返回错误界面,登 录成功时进入系统主界面。 对应我们配置了fail_view、success_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 cmd和BindException ex。 前面曾经多次提到请求数据对象,这个名为cmd的Object型参数,正是传入的请求 数据对象的引用。 BindException ex参数则提供了数据绑定错误的跟踪机制。它作为错误描述工具, 用于向上层反馈错误信息。 在Spring MVC中,BindException将被向上层表现层反馈,以便在表现层统一处 理异常情况(如显示对应的错误提示),这一机制稍后在“输入参数合法性校验”部 分再具体探讨。 onSubmit还有另外一个签名版本: protected ModelAndView onSubmit( HttpServletRequest request, HttpServletResponse response, Object cmd, BindException ex ) 可以看到,类似Servlet的doGet/doPost方法,此版本的onSubmit方法签名中 包含了Servlet规范中的HttpServletRequest、HttpServletResponse以提 供与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> 页面逻辑非常简单,首先显示当前登录用户的用户名。然后循环显示当前用户的通知消息 “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 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} 结果为表达式计算结果,即整数值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,循环范围是从0到9,每次步长为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>改造后的输出页面: |
|
来自: BlazerOfIT > 《spring》