Web 应用与MVC
目前比较好的MVC,老牌的有Struts、Webwork。新兴的MVC 框架有Spring MVC、Tapestry、JSF等。这些大多是著名团队的作品,另外还有一些边缘团队的作品,也相当出色,如Dinamica、VRaptor等。
这些框架都提供了较好的层次分隔能力。在实现良好的MVC 分隔的基础上,通过提供一些现成的辅助类库,同时也促进了生产效率的提高。
如何选择一个合适的框架?什么是考量一个框架设计是否优秀的标准?很难定夺的问题。旁观各大论坛中铺天盖地的论战,往往在这一点上更加迷茫。
从实际Web产品研发的角度而言(而非纯粹设计上,扩展性上,以及支持特性上的比较),目前Struts 也许是第一选择。作为一个老牌MVC Framework,Struts 拥有成熟的设计,同时,也拥有最丰富的信息资源和开发群体。换句实在点的话,产品基于Struts 构建,如果公司发生人事变动,那么,找个熟悉Struts的替代程序员成本最低。
从较偏向设计的角度出发,WebWork2 的设计理念更加先进,其代码与Servlet API 相分离,这使得单元测试更加便利,同时系统从BS结构转向CS接口也较为简单。另外,对于基于模板的表现层技术(Velocity、Freemarker和XSLT)的支持,也为程序员提供了除JSP之外的更多的选择(Struts也支持基于模板的表现层技术,只是实际中不太常用)。而对于Spring而言,首先,它提供了一个相当灵活和可扩展的MVC实现,与WebWork2相比,它在依赖注入方面、AOP 等方面更加优秀,但在MVC 框架与底层构架的分离上又与Webworks 存在着一定差距(Spring 的MVC 与Servlet API 相耦合,难于脱离Servlet容器独立运行,在这点的扩展性上,比Webwork2稍逊一筹)。
我们还要注意到,Spring对于Web应用开发的支持,并非只限于框架中的MVC部分。即使不使用其中的MVC实现,我们也可以从其他组件,如事务控制、ORM模板中得益。同时,Spring也为其他框架提供了良好的支持,如我们很容易就可以将Struts 与Spring 甚至WebWork与Spring 搭配使用(与WebWork 的搭配可能有些尴尬,因为两者相互覆盖的内容较多,如WebWork中的依赖注入机制、AOP机制等与Spring中的实现相重叠)。因此,对于Spring在Web应用中的作用,应该从一个更全面的角度出发。
第一个SpringMVC实例:
下面的实例,实现了一个常见的用户登录逻辑,即用户通过用户名和密码登录,系统对用户名和密码进行检测,如果正确,则在页面上显示几条通知信息。如果登录失败,则返回失败界面。
(示例中,表示层以JSP2.0实现。)
出于简洁考虑,这里的“用户名/密码”检测以及通知信息的生成均在代码中以硬编码实现。
首先来看登录界面:
对应的index.html:
<form action="login.do" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2" align="right"><input type="submit" value="提交" /></td>
</tr>
</table>
</form>
MVC 关键流程的第一步,即收集页面输入参数,并转换为请求数据对象。这个静态页面提供了一个基本的输入界面,下面这些输入的数据将被发送至何处,将如何被转换为请求数据对象?
现在来看接下来发生的事情:
当用户输入用户名密码提交之后,此请求被递交给Web 服务器处理,上面我们设定form提交目标为"/login.do",那么Web服务器将如何处理这个请求?
显然,标准Http 协议中,并没有以.do 为后缀的服务资源,这是我们自己定义的一种请求匹配模式。此模式在web.xml中设定:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<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>
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.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.JstlView</value>
</property>
</bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/login.do">LoginAction</prop>
</props>
</property>
</bean>
<bean id="LoginAction" class="com.hit.action.LoginAction">
<property name="commandClass">
<value>com.hit.action.LoginInfo</value>
</property>
<property name="fail_view">
<value>/WEB-INF/loginFail.jsp</value>
</property>
<property name="success_view">
<value>/WEB-INF/loginSuccess.jsp</value>
</property>
</bean>
</beans>
View Resolver的viewClass参数
这里我们使用JSP页面作为输出,因此,设定为:
org.springframework.web.servlet.view.JstlView
其余可选的viewClass还有:
Ø org.springframework.web.servlet.view.freemarker.FreeMarkerView(用于基于FreeMarker模板的表现层实现)
Ø org.springframework.web.servlet.view.velocity.VelocityView(用于基于velocity模板的表现层实现)等。
“urlMapping”关系映射
可以看到,这里我们将“/login.do”请求映射到处理单元LoginAction。<props>节点下可以有多个映射关系存在,目前我们只定义了一个。
LoginAction定义
这里定义了逻辑处理单元LoginAction 的具体实现,这里,LoginAction 的实现类为com.hit.action.LoginAction。
LoginAction的请求数据对象
commandClass 参数源于LoginAction 的基类BaseCommandController,BaseCommandControlle 包含了请求数据封装和验证方法( BaseCommandController.bindAndValidate ),它将根据传入的HttpServletRequest构造请求数据对象。
这里我们指定commandClass 为com.hit.action.LoginInfo,这是一个非常简单的Java Bean,它封装了登录请求所需的数据内容:
package com.hit.action;
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 会将LoginAction的返回数据与视图相融合,返回最终的显示界面。
LoginAction.java:
public class LoginAction extends SimpleFormController {
private String success_view;
private String fail_view;
public String getFail_view() {
return fail_view;
}
public void setFail_view(String fail_view) {
this.fail_view = fail_view;
}
public String getSuccess_view() {
retiurn success_view;
}
public void setSuccess_viiew(String success_view) {
this.success_view = success_view;
}
@Override
protected ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response,
Object obj,
BindException ex) throws Exception {
request.setAttribute("message", "success");
return new ModelAndView(this.getSuccess_view());
}
}
其中:
⑴ onSubmit方法
我们在子类中覆盖了父类的onSubmit方法;而onSubmit方法用于处理业务请求。
负责数据封装和请求分发的Dispatcher,将对传入的HttpServletRequest进行
封装,形成请求数据对象,之后根据配置文件,调用对应业务逻辑类的入口方法(这
里就是LoginAction)的onSubmit()方法,并将请求数据对象及其他相关资源引
用传入。
onSubmit方法包含了两个或者四个参数:Object obj和BindException ex。
前面曾经多次提到请求数据对象,这个名为obj的Object型参数,正是传入的请求
数据对象的引用。
BindException ex参数则提供了数据绑定错误的跟踪机制。它作为错误描述工具,
用于向上层反馈错误信息。
在Spring MVC中,BindException将被向上层表现层反馈,以便在表现层统一处
理异常情况(如显示对应的错误提示)