Spring 的 Web MVC 框架是围绕 DispatcherServlet 设计的,它把请求分派给处理程序,同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上载文件支持。默认的处理程序是非常简单的 Controller 接口,只有一个方法 ModelAndView handleRequest(request, response)。Spring 提供了一个控制器层次结构,可以派生子类。如果应用程序需要处理用户输入表单,那么可以继承 AbstractFormController。如果需要把多页输入处理到一个表单,那么可以继承 AbstractWizardFormController。
Spring MVC对于现在较成熟的Model-View-Control框架而言,其解决的主要问题无外乎下面几部分:
1》将web页面中的输入元素封装为一个(请求)数据对象。
2》根据请求的不同,调度相应的逻辑处理单元,并将(请求)数据对象作为参数传入。
3》逻辑处理单元完成运算后,返回一个结果数据对象。
4》将结果数据对象中的数据与预先设计的表现层相融合并展现给用户。
开发步骤:
首先新建web Project项目:MySpringMvc
1.加载项目所需要的jar包;
spring.jar -------------------------这个在spring2.5.6资源包的dist下面
spring-webmvc.jar---------------这个在spring2.5.6资源包的dist/module下面
2.配置web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
- <SPAN style="FONT-SIZE: large"><web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
- <servlet>
- <servlet-name>dd</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <!-- <init-param>-->
- <!-- <param-name>contextConfigLocation</param-name>-->
- <!-- <param-value>/WEB-INF/applicationContext.xml</param-value>-->
- <!-- </init-param>-->
- </servlet>
- <!--
- applicationContext.xml文件代表示应用程序服务的配置和 bean 配置。如果想装入多个配置文件,可以在
- <param-value>标记中用逗号作分隔符。
- springmvc配置文件与spring配置的servlet名称有关[如本配置中是dd]
- 通常springmvc配置文件名称结构为:[servlet-name]-servlet.xml,
- 如果你没有指定init-param里面contextCofigLocation的值中对应的XML文件的话
- (也就是applicationContext全局配置文件没有配置在web.xml中的话),那么像本
- 段代码对应在springmvc中的配置文件就应该是/WEB-INF/dipatcher-servlet.xml这样的文件,
- 否则如果配置了applicaitonContext.xml这样的spring全局配置文件,如本配置那么就必须为/WEB-INF/dd-servlet.xml
- -->
- <servlet-mapping>
- <servlet-name>dd</servlet-name>
- <url-pattern>*.do</url-pattern>
- </servlet-mapping>
- </web-app></SPAN>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>dd</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- <init-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value>/WEB-INF/applicationContext.xml</param-value>-->
<!-- </init-param>-->
</servlet>
<!--
applicationContext.xml文件代表示应用程序服务的配置和 bean 配置。如果想装入多个配置文件,可以在
<param-value>标记中用逗号作分隔符。
springmvc配置文件与spring配置的servlet名称有关[如本配置中是dd]
通常springmvc配置文件名称结构为:[servlet-name]-servlet.xml,
如果你没有指定init-param里面contextCofigLocation的值中对应的XML文件的话
(也就是applicationContext全局配置文件没有配置在web.xml中的话),那么像本
段代码对应在springmvc中的配置文件就应该是/WEB-INF/dipatcher-servlet.xml这样的文件,
否则如果配置了applicaitonContext.xml这样的spring全局配置文件,如本配置那么就必须为/WEB-INF/dd-servlet.xml
-->
<servlet-mapping>
<servlet-name>dd</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
2.由于上面的初始化参数中没有指定名字的XML文件,因此在WEB-INF下面建立
dispatcher-servlet.xml
- <SPAN style="FONT-SIZE: medium"><SPAN style="FONT-SIZE: large"><?xml version="1.0" encoding="UTF-8"?>
- <!--看到下面的beans这个元素标签没有,必须有标签的声明-->
- <beans
- xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
- <!-- URL Mapping -->
- <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
- <property name="mappings">
- <props>
- <prop key="/regAction.do">regAction</prop>
- </props>
- </property>
- </bean>
- <!-- definition of View Resolver -->
- <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="viewClass">
- <value>org.springframework.web.servlet.view.JstlView</value>
- </property>
- <property name="prefix">
- <value>/view/</value>
- </property>
- <property name="suffix">
- <value>.jsp</value>
- </property>
- </bean>
- <!-- formController ,这个formController可以配置也可以不配置-->
- <bean id="formController"
- class="org.springframework.web.servlet.mvc.ParameterizableViewController">
- <property name="viewName">
- <value>form</value>
- </property>
- </bean>
- <!-- Action Definition -->
- <bean id="regAction" class="org.lee.springmvc.demo.RegAction">
- <!--在MySpringMvc这个项目中就没有配置这个commandClass,
- 因为它提前调用了setCommandClass(LoginForm.class)这个方法;这样跟下面效果一样
- 不过还是建议配成下面这样的更好
- -->
- <property name="commandClass">
- <value>org.lee.springmvc.demo.RegInfo</value>
- </property>
- <property name="error_view">
- <value>error</value>
- </property>
- <property name="success_view">
- <value>success</value>
- </property>
- <property name="commandName">
- <value>myCommand</value>
- </property>
- </bean>
- </beans></SPAN></SPAN>
<?xml version="1.0" encoding="UTF-8"?>
<!--看到下面的beans这个元素标签没有,必须有标签的声明-->
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<!-- URL Mapping -->
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/regAction.do">regAction</prop>
</props>
</property>
</bean>
<!-- definition of View Resolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
<property name="prefix">
<value>/view/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<!-- formController ,这个formController可以配置也可以不配置-->
<bean id="formController"
class="org.springframework.web.servlet.mvc.ParameterizableViewController">
<property name="viewName">
<value>form</value>
</property>
</bean>
<!-- Action Definition -->
<bean id="regAction" class="org.lee.springmvc.demo.RegAction">
<!--在MySpringMvc这个项目中就没有配置这个commandClass,
因为它提前调用了setCommandClass(LoginForm.class)这个方法;这样跟下面效果一样
不过还是建议配成下面这样的更好
-->
<property name="commandClass">
<value>org.lee.springmvc.demo.RegInfo</value>
</property>
<property name="error_view">
<value>error</value>
</property>
<property name="success_view">
<value>success</value>
</property>
<property name="commandName">
<value>myCommand</value>
</property>
</bean>
</beans>
3.建立JSP文件
form.jsp
- <SPAN style="FONT-SIZE: medium"><SPAN style="FONT-SIZE: large"><%@page contentType="text/html"%>
- <%@page pageEncoding="UTF-8"%>
- <%@taglib prefix="spring"
- uri="http://www.springframework.org/tags"%>
- <%@taglib prefix="c"
- uri="http://java.sun.com/jsp/jstl/core"%>
- <html>
- <head>
- <meta http-equiv="Content-Type"
- content="text/html; charset=UTF-8">
- <title>Login Form</title>
- </head>
- <body>
- <h1>登入表单</h1>
- <spring:bind path="command.*">
- <font color="red">
- <b>${status.errorMessage}</b>
- </font><br>
- </spring:bind>
- 请输入使用者名称与密码:<p>
- <form name="loginform" action="login.do" method="post">
- <spring:bind path="command.userName">
- 名称 <input type="text" name="${status.expression}" value="${status.value}"/>
- <font color="red"><c:out value="${status.errorMessage}" /></font><br/>
- </spring:bind>
- <spring:bind path="command.password">
- 密码 <input type="password" name="${status.expression}" value="${status.value}"/>
- <font color="red"><c:out value="${status.errorMessage}" /></font><br/>
- </spring:bind>
- <input type="submit" value="确定"/>
- </form>
- 注意:输入错误会再回到这个页面中。
- </body>
- </html></SPAN></SPAN>
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib prefix="spring"
uri="http://www.springframework.org/tags"%>
<%@taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<title>Login Form</title>
</head>
<body>
<h1>登入表单</h1>
<spring:bind path="command.*">
<font color="red">
<b>${status.errorMessage}</b>
</font><br>
</spring:bind>
请输入使用者名称与密码:<p>
<form name="loginform" action="login.do" method="post">
<spring:bind path="command.userName">
名称 <input type="text" name="${status.expression}" value="${status.value}"/>
<font color="red"><c:out value="${status.errorMessage}" /></font><br/>
</spring:bind>
<spring:bind path="command.password">
密码 <input type="password" name="${status.expression}" value="${status.value}"/>
<font color="red"><c:out value="${status.errorMessage}" /></font><br/>
</spring:bind>
<input type="submit" value="确定"/>
</form>
注意:输入错误会再回到这个页面中。
</body>
</html>
4.建立jsp文件
success.jsp
- <SPAN style="FONT-SIZE: medium"><SPAN style="FONT-SIZE: large"><%@page contentType="text/html"%>
- <%@page pageEncoding="GBK"%>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=GBK">
- <title>登入成功</title>
- </head>
- <body>
- <H1>哈啰! ${welcomeuser}!!</H1>
- 这是您的神秘礼物!^o^<a href="login.do">退出登录</a>
- </body>
- </html></SPAN></SPAN>
<%@page contentType="text/html"%>
<%@page pageEncoding="GBK"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<title>登入成功</title>
</head>
<body>
<H1>哈啰! ${welcomeuser}!!</H1>
这是您的神秘礼物!^o^<a href="login.do">退出登录</a>
</body>
</html>
5.建立一个java bean LoginForm.java
- <SPAN style="FONT-SIZE: medium"><SPAN style="FONT-SIZE: large">package zz.it.beans;
- public class LoginForm {
- 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;
- }
- }
- </SPAN></SPAN>
package zz.it.beans;
public class LoginForm {
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;
}
}
6.建立controller LoginController.java
- <SPAN style="FONT-SIZE: medium"><SPAN style="FONT-SIZE: large">package zz.it.controller;
- import org.springframework.validation.BindException;
- import org.springframework.web.servlet.ModelAndView;
- import org.springframework.web.servlet.mvc.SimpleFormController;
- import zz.it.beans.LoginForm;
- public class LoginController extends SimpleFormController {
- /**
- * 构造方法
- */
- public LoginController() {
- // TODO Auto-generated constructor stub
- //setCommandClass(LoginForm.class);
- //这句话要是不写的话,那么在dd-servlet.xml中的loginController里面配置上如下:
- // <property name="commandClass">
- //<value>zz.it.beans.LoginForm</value>
- //</property>
- //这样效果也是一样的
- }
- public ModelAndView onSubmit(Object cmd, BindException errors) {
- LoginForm loginForm = (LoginForm) cmd;
- if (loginForm.getUserName().equals("test")
- && loginForm.getPassword().equals("test")) {
- return new ModelAndView(getSuccessView(), "welcomeuser", loginForm
- .getUserName());
- } else {
- errors.reject("ccc", "用户名或密码有误!");
- errors.rejectValue("userName", "nameErr", null, "用户名错误");
- errors.rejectValue("password", "passErr", null, "密码错误");
- return new ModelAndView(getFormView(), errors.getModel());
- }
- }
- }
- </SPAN></SPAN>
package zz.it.controller;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import zz.it.beans.LoginForm;
public class LoginController extends SimpleFormController {
/**
* 构造方法
*/
public LoginController() {
// TODO Auto-generated constructor stub
//setCommandClass(LoginForm.class);
//这句话要是不写的话,那么在dd-servlet.xml中的loginController里面配置上如下:
// <property name="commandClass">
//<value>zz.it.beans.LoginForm</value>
//</property>
//这样效果也是一样的
}
public ModelAndView onSubmit(Object cmd, BindException errors) {
LoginForm loginForm = (LoginForm) cmd;
if (loginForm.getUserName().equals("test")
&& loginForm.getPassword().equals("test")) {
return new ModelAndView(getSuccessView(), "welcomeuser", loginForm
.getUserName());
} else {
errors.reject("ccc", "用户名或密码有误!");
errors.rejectValue("userName", "nameErr", null, "用户名错误");
errors.rejectValue("password", "passErr", null, "密码错误");
return new ModelAndView(getFormView(), errors.getModel());
}
}
}
7.最后,整合部署,访问http://localhost:1234/MySpringMvc/login.do
效果图
初始登录界面
登录成功页面
登录失败页面
我估计初学者,最关心的大都有这两个问题
1.这个controller是怎样像struts那样进行封装数据的
2.<spring:bind>为什么这么用,为什么取值只能是command.xxx
1.controller是怎样进行数据封装的
要说这个问题,我不得不说SimpleFormController了
SimpleFormController是AbstractFormController的具体实现,允许你在配置文件里通过successView和formView属性来配置成功视图(表单成功提交后要转向的页面)和表单视图(显示表单的页面);如果提交不合法(有三种可能:1.validator出错。2.bind错误,也就是说从请求中提取参数封装到command的过程中出现了类型转化错误,比如将一个含字母字符串转换为Integer。3.onBindAndValidate()方法出错),则会重新返回到表单视图;如果提交合法,onSubmit()方法的默认实现会转向成功页面,当然你可以覆写该方法在转向之前填充一些你想返回的信息。
SimpleFormController的工作流与AbstractFormController差不多,唯一的不同是你不必自己去实现showForm()和processFormSubmission()。showForm()这个方法已经被类SimpleFormController实现了并被限定为final,你不可以在继承SimpleFormController的子类里覆写这个类。processFormSubmission()这个方法尽管可以去覆写但由于它几乎可以满足所有的要求,因此一般也不会有人去重写它。
它的处理流程是这样的:
get请求来到时,这样处理:
1) 请求传递给一个controller对象
2) 调用formBackingObject()方法,创建一个command对象的实例。
3) 调用initBinder(),注册需要的类型转换器
4) 调用showForm()方法,返回准备呈现给用户的视图 ,如果“bindOnNewForm”属性设为true,则ServletRequestDataBinder会将初始请求参数填入一个新的表单对象,并且执行onBindOnNewForm()方法。
5) 调用referenceData()方法,准备给用户显示相关的数据。如用户登录需要选择的年度信息
6) 返回formView指定的视图
post请求来到时,这样处理:
1) 如果sessionForm属性没有设定,则调用formBackingObject()方法,创建一个command对象的实例。否则从session中取得表单对象
2) 将请求传来的参数写入command对象,看它的源代码,会发现它是这样来做的:
ServletRequestDataBinder binder = createBinder(request, command);
binder.bind(request);
3)执行onBind()方法,在绑定数据之后,验证数据之前对表单数据进行一些自制的修改动作。
4) 如果设置为要求验证(validateOnBinding属性被设定),则调用validator类进行数据验证
5) 调用onBindAndValidate()方法,该方法允许自定义数据绑定和校验处理
6)执行processFormSubmission()检验 Errors对象中含不含错误,如果含有错误则执行showForm()返回到填写表单页面;否则执行onSubmit()方法,进行提交表单,然后转向成功页面。
2.<spring:kind>的用法
在Spring框架体系下,可以说规约最少,最不受限制的就是表现层技术了。不像Struts,改定了好多的标签,而且有些功能还和标签绑定了。Sping也定义了一些标签,但这些标签只是给使用者提供了一些方便,并不会提供额外的功能或效果。
<sping:bind>的path属性制定了与表单中的那个属性绑定,这样,${status.expression}就代表了那个属性的名称,${status.expression}代表那个属性的值。如果path属性为“XXX.*”则与那个表单的所有属性绑定。
上面的例子表单有两个属性username和password。实际上绑定方式有两种,第一种就像我的上一篇http://javacrazyer.iteye.com/blog/790834文章讲的的那样,第二种如下:
......
<spring:bind path="loginForm">
用户名:<INPUT name="userName" type="text" value="${command.userName}"/><br>
密码:<INPUT name="password" type="password" value="${command.password}"/>
</spring:bind>
这样尽管也行,但在错误信息处理上,针对用户名和密码的报错信息不会像原来那样显示出来了
所以验证我下面要说的话:
使用<sping:bind>标签,在初次进入表单页面时并不会有什么作用,而是当表单提交后,如果有BindException错误时再返回这个页面时,可以把先前的输入显示在input里。
说到错误信息处理,我又要许多要说了,来看看我前一篇文章http://javacrazyer.iteye.com/blog/790834中controller中的
errors.reject("ccc", "用户名或密码有误!");
return new ModelAndView(getFormView(), errors.getModel());
它调用了BindException的reject方法,这样,再调用BindException的getModel()方法,就把错误连同表单等信息一并返回到表单页面用以显示。
reject方法的第一个参数是错误码,如果设定了国际化资源,则显示资源文件中该错误码对应的错误条目,如果没有设定了国际化资源,则显示reject方法的第二个参数。
reject方法的不足之处是在表现层不能区分错误消息属于那个字段,即不能说明是username不对呢还是password不对。解决这种情况可以使用rejectValue方法,这也是更一般使用的方法。rejectValue方法定义如下:
rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage)
第一个参数指定表单的域,即username或password,这样就可以分辨到底是那块出了问题;第二个参数和reject方法的第一个参数一样,制定错误码;第三个参选数制定了资源文件中的占位符;第四个参数和和reject方法的第二个参数一样。rejectValue方法还有一个简化的定义
rejectValue(String field, String errorCode, String defaultMessage)
上面是在Controller里使用的方法,使用上述方法后,若果出现BindException错误,返回表单页面时就会显示错误信息,那么如何在页面里显示错误信息呢?
上面/WEB-INF/jsp/login.jsp里由于在controller里使用的是reject方法,所以只能那么显示,如果我们使用rejectValue方法,例如改动LoginController:
errors.rejectValue("userName", "nameErr", null, "用户名错误");
errors.rejectValue("password", "passErr", null, "密码错误");
这样,就可以把页面改为如之前最后的形式
<spring:bind path="command.userName">
名称 <input type="text" name="${status.expression}" value="${status.value}"/>
<font color="red"><c:out value="${status.errorMessage}" /></font><br/>
</spring:bind>
<spring:bind path="command.password">
密码 <input type="password" name="${status.expression}" value="${status.value}"/>
<font color="red"><c:out value="${status.errorMessage}" /></font><br/>
</spring:bind>
这样错误的消息就绑定到相应的字段了。当然也可以不制定某个字段,一股脑都输出
到此,大家还是没看到我是怎样讲解command这个值的,至于为什么非要是command而不是其他值
这是因为setCommandClass这个方法是AbstractController中的一个方法,而这个
方法使用到的一个默认值public static final java.lang.String DEFAULT_COMMAND_NAME = "command";public static final java.lang.String DEFAULT_COMMAND_NAME = "command";
看到了没有,就是叫做command,所以在标签中就敢大胆的用啦
3.最后还有几个小问题
(1)一个常见的错误:
不通过controller直接访问含有spring:bind标签的JSP页面会出现下面的错误:
javax.servlet.ServletException: Neither Errors instance nor plain target object for bean name 'person' available as request attribute
解决办法:
http://spring.jactiongroup.net/viewtopic.php?p=5482
(2)星号(*)的意思
global and all field errors,
## use wildcard (*) in place of the property name
<spring:bind path="company.*">
<c:forEach items="${status.errorMessages}" var="error">
c:out value="${error}"/><br/>
</c:forEach>
</spring:bind>
(3)command的意思
3-1 commandClass
相当于struts中的ActionForm,用来封装V中的数据,方便在C中使用。
3-2 commandName
用来指定JSP中的数据需要绑定到哪个对象。默认为command
比如下面的配置中,commandName就是command
<spring:bind path='command.email'>
<td><input type='text' name='${status.expression}'
value='${status.value}' size='30'
maxlength='100'></td></tr>
</spring:bind>
因为是缺省值,所以它就不需要再在Controller中显示声明
如果在Controller中设置了setCommandName("me");则上面的配置文件需要改为:
<spring:bind path='me.email'>
<td><input type='text' name='${status.expression}'
value='${status.value}' size='30'
maxlength='100'></td></tr>
</spring:bind>
简单吧。
(4)一个要注意的问题(原文链接)
一个普通的<spring.bind>的使用类似于:
<spring:bind path="user.age">
<input type="text" name="age" value="${status.value}">
<font color="red">${status.errorMessage}</font>
</spring:bind>
需要注意的是:<input>的name属性值必须与<spring:bind>的path属性的匹配,否则就绑定不了!
例如下面的代码就绑定不了
<spring:bind path="user.age">
<input type="text" name="theAge" value="${status.value}">
<font color="red">${status.errorMessage}</font>
</spring:bind>
为了避免手误,强烈推荐下列方法来绑定:
<spring:bind path="user.age">
<input type="text" name="${status.expression}" value="${status.value}">
<font color="red">${status.errorMessage}</font>
</spring:bind>