SPRING MVC 介绍

1. 介绍

最近很多同学也开始使用Spring框架(http://springframework.org)做一些网站了,之前给大家推荐的时候好像都还不感冒,甚至都不知道是什么框架,仍然埋头 StrutsHibernate 的学习。

Spring作为当前J2EE编程实践的替代方案而受到欢迎。实际上,Spring提供了在J2EE之外的很多通用功能,用于促进良好的编程实践,例如设计接口,单元测试和灵活性(例如,避免依赖性,可配置性)。

Spring的核心是通过配置文件(通常是XML)来组装组件的系统,这个简单的概念被称为控制反转(IoC),好莱坞原则(“不要找我,我会找你”)和依赖注入。

例如,考虑一下我们的 DAO 对象所需要的Connection对象,通常,DAO 将在 JNDI 或其他服务中查找此资源。它通常是主动的去从某个地方去“拉”取这个依赖的资源,这种方法强制了 DAO 对于某个特定资源的依赖,必须很明确的知道这个资源来自哪里,怎么实例化(如果是单例的,还要考虑怎么从工厂方法中引入),从而限制DAO的重用性,灵活性和可测试性。当然,可以使用诸如JNDI之类的让这个查找过程简单一点,但仍然必须手动的去管理这些资源的依赖。

那现在,不妨考虑一种场景,如果我们不让 DAO 自己去“拉”这些依赖的资源,反过来,我们通过其它的一些手段将它需要的资源“推”送给它呢,DAO 本身不需要管理这些依赖资源,只需要关注本身的业务逻辑,是不是更通用一些呢。实际上,这个就是Spring提供的核心理念,让这种资源的依赖管理方式反过来,由Spring容器来主动的推送资源到目标中去,而不需要目标自己去管理,又被称为“依赖注入”。

Spring有时被称为轻量级容器,但Spring实际上非常大(大约有1000个类/接口)。轻量级术语更多地指的是它以最少的依赖性集成技术的能力(与需要实现特定接口的许多J2EE解决方案相反)。

使用依赖注入框架作为基础,Spring增加了对应用程序功能的许多常见方面的支持,包括持久化,事务控制,AOP,错误处理和分布式计算等。今天主要简单的给大家讲解一下 Spring MVC 相关的知识,好让其它感兴趣的同学能更清晰的认知Spring,加入到基于Spring的应用开发过程中来,提高效率。

2. SPRING MVC

Struts等其它 MVC 框架类似,Spring也采用了熟悉的模型 - 视图 - 控制器架构,如各种基于Web的框架所示, 一个Front ControllerServlet接收系统的所有HTTP请求,并使用定义的映射路由到实际的业务处理程序。这些处理程序通常是Controller接口的实现,但可以是符合框架规范的任何类。在SpringMVC 中,DispatcherServlet作为Front Controller,调用其他Controllers。它在处理请求的过程中执行若干其他任务(所有这些都以高度可配置的方式提供),包括视图映射和专门的异常处理。本文仅简要介绍这些领域,重点介绍Controllers的使用。

控制器类似于Struts Action。它处理请求并返回一个ModelAndView对象,该对象包含一个表示Model(用于表示的数据,类似 Map,实际上它的底层实现就是Map)和一个表示展现层的ViewModel大家都很熟悉,也很好理解,View则是 Spring的一个抽象概念,表示一个视图对象,Controller通常会返回一个String(由可配置解析ViewResolverString解析成真正的页面)或者实例View(直接使用)来表示要展示的页面,空返回表示控制器已完成请求的处理,无需额外的View操作。

Spring框架中存在许多控制器,如下图所示:

Spring Framework Diagram

其中SimpleFormController是这些Controller类中最常用的。虽然名称翻译过来是简单表单控制器,但SimpleFormController 具有非平凡的生命周期和相关功能 - 正如上面显示的深层继承层次结构所暗示的那样。我们将依次讨论其基类来详细说明其功能。

AbstractController在将请求处理委托给派生类的handleRequestInternal()方法之前,提供一些称之为“低级别”的配置和功能。它生成一些诸如控制缓存的头信息cacheSeconds的属性,根据配置拒绝GET或POST请求,并确保在标记为必需时存在会话,还可以在会话上同步请求以防止同一客户端进行多线程访问等。请注意,SpringStruts类似,绝大部分Controller是单例重复使用的。(或者,ThrowawayController为每个请求实例化控制器实现。)

BaseCommandController将请求参数映射到一个配置好的命令类(类似的StrutsActionForm,但它是一个非常简单的JavaBean,没有框架的依赖关系)。有时还会注册一个 Validator 验证器 来检查请求内容。注意,该类并未实现 handleRequestInternal()(处理实际的业务逻辑),而是将这部分工作留给它的派生类。它定义了 final bindAndValidate() 来将请求参数绑定到Command Object并验证它的内容。提供了几个钩子来定制这个过程:

  • initBinder(request, binder)- 可以覆盖以添加PropertyEditors到binder以处理特殊类型
  • onBind(request, command) - 在绑定后调用,但在验证之前调用。可以重写以执行专门的映射,正确的映射等。
  • onBindAndValidate(request, command) - 验证后调用。例如,可以覆盖以执行其他验证。

AbstractFormController继承了BaseCommandController并覆盖handleRequestInternal()用于处理GET和POST请求。此类将Command Object(from BaseCommandController)称为表单对象,因此下面将使用此术语。对于POST请求,可以创建表单(命令)对象的默认实例,也可以从会话中检索预先存在的实例(然后删除)。派生AbstractWizardFormController利用会话方法进行多屏输入。会话范围的Command Object也可以用作通过将控制器配置为要求其存在来防止重复表单提交的手段(有关详细信息,请参阅相关的javadocs)。可以通过覆盖formBackingObject()方法来定义初始Command Object

接下来,使用请求参数填充表单对象BaseCommandController.bindAndValidate()AbstractFormController没有定义上面提到的任何相应的抽象方法(initBind等等)。最后,业务逻辑处理被委托给抽象方法:

1
2
3
4
ModelAndView processFormSubmission(HttpServletRequest request,	
HttpServletResponse response,
Object command,
BindException errors)

通过获取Command Object(表单)开始GET请求处理formBackingObject()。这可以被覆盖以提供填充有来自数据库的数据的对象。如果启用,则请求参数然后绑定到Command Object对象,(同样,initBinder()可以专门处理)。最后,处理委托给:

1
2
3
4
protected abstract ModelAndView showForm(
HttpServletRequest request,
HttpServletResponse response,
BindException errors) throws Exception;

具体的类SimpleFormController又增强了一些AbstractFormController功能。可以配置表单和成功视图 - 验证失败将用户返回到表单视图,同时成功验证和后续操作将导致成功视图。通常,派生类只需要覆盖几种onSubmit()方法中的一种来处理表单提交(例如保存到数据库)。

3. MVC 示例

现在我们来测试验证一下。我们将创建一个简单的表单,用户输入他们的名字并选择他们喜欢的水果。

3.1 实体对象:Fruit

Fruit类是一个相当标准的JDK 1.5 Java枚举类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Fruit {
private static Map instanceMap = new HashMap();

public static final Fruit APPLE = new Fruit(0, "Apple");
public static final Fruit ORANGE = new Fruit(1, "Orange");

private long id;
private String name;

private Fruit(Long id, String name) {
this.id = id;
this.name = name;
instanceMap.put(new Long(id), this);
}
......
}

当然,这也可以作为查找表存储在数据库中。由于这些对象对应于HTML <OPTION>标签的字符串值属性,因此主要问题是将下拉列表映射到服务器管理的对象列表。

要将基于字符串的引用转换为枚举值(反之亦然),我们定义一个专门的java.beans.PropertyEditor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private class FruitSupport extends PropertyEditorSupport {
public String getAsText() {
Object value = getValue();
return value == null ? "" : ((Fruit)value).getId().toString();
}

public void setAsText(String string)
throws IllegalArgumentException {
try {
Long id = Long.valueOf(string);
Fruit f = Fruit.getById(id);
if (f == null) {
throw new IllegalArgumentException("Invalid id for Fruit: " + string);
}
setValue(f);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid id for Fruit: " + string);
}
}
}

3.2 Command (Form) 类

我们还需要一个JavaBean来表示表单的内容(名称和结果):

1
2
3
4
5
6
public class FormBean {
private String name;
private Fruit fruit = Fruit.APPLE;

.. getters and setters
}

注意,这是一个非常非常简单的 JavaBean ,除了属性及 Getter/Setter外没有任何其它的依赖。

3.3 Spring 配置

首先我们添加DispatcherServletweb.xml中,来接管所有的 HTTP 请求(这个就是我们之前提到的 Front Controller Servlet):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>

请注意,所有.htmURL都将路由到Springservlet。除了配置MVC架构的其他方面之外,该spring-servlet.xml文件还定义了DispatcherServlet委托给各种处理程序(此处为 Controller)的映射。下面的部分定义了Controller相关的类及其到URL的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<bean id="formController" 
class="com.ociweb.spring.MyFormController">
<property name="commandName"><value>formBeanId</value></property>
<property name="commandClass">
<value>com.ociweb.spring.FormBean</value></property>
<!-- <property name="validator"><ref local="MyValidator"></ref></property> -->
<property name="formView"><value>form</value></property>
<property name="successView"><value>form</value></property>
</bean>

<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/form.htm">formController</prop>
</props>
</property>
</bean>

实例MyFormController给出一个idformController,并由urlMappingbean 中的id引用,以将URL映射/form.htm到控制器实例。MyFormControllerr的属性如下:

  • commandName 定义Command Object的 id
  • commandClass 将我们的表单bean定义为 FormBean的实例
  • formView 如果表单提交失败,则定义要调用的视图
  • successView 如果表单提交成功,则定义要调用的视图

请注意,这个例子中formViewsuccessView引用了相同的视图,实际的场景中通常不是这样的,这里只是举例演示用法。

3.4 Controller

我们演示用的MyFormController比较简单,扩展SimpleFormController并定义了一个默认构造函数来支持Spring实例化。实现initBinder()方法注册PropertyEditor到binder对象以支持我们前面提到的Fruit枚举类型。referenceData()方法获取所有Fruit实例,并将它们放在具有唯一键的映射中,这些内容将在 JSP 页面中使用。

最后,该onSubmit()方法处理来自表单Command Object的数据。虽然存在许多更简单的onSubmit()方法并且可以被覆盖,但在这种情况下需要完整的参数集(RequestResponse等)。复杂性在于返回表单页面,同时保持提供的项目列表referenceData()。在成功提交表单时,不会调用此方法,从而产生一个空列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MyFormController extends SimpleFormController {
public MyFormController() {
}

protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder)
throws Exception {
binder.registerCustomEditor(Fruit.class, new FruitSupport());
}

protected Map referenceData(HttpServletRequest request)
throws Exception {
Map retval = new HashMap();
retval.put("fruits", Fruit.getAll());
return retval;
}

protected ModelAndView onSubmit(
HttpServletRequest request,
HttpServletResponse response,
Object command,
BindException errors)
throws Exception {

FormBean form = (FormBean)command;

// Act on form submission (e.g. write to database)

// Redirect back to form
// This will ensure referenceData is set, etc.
// This is needed when submit action fails despite success of earlier validation.

return showForm(request, response, errors);
}
}

3.5 JSP 页面

最后,JSP页面用于/form.htm定义ViewSpring为许多不同的View选项提供了轻松集成,包括JSP,XSLT,Velocity和FreeMarker。视图可以很容易地与不同的处理程序集成。关于JSP,Spring只支持少量自定义标签,只有一个spring:bind是常用的。我们写的form.jsp页面同时演示了它与JSTL的用法。

本质上,spring:bind标记创建一个本地上下文,其中 status 变量与HTML标签中引用的变量的绑定相关联。 status 包含上次验证尝试的结果,值绑定和属性的标识符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<form method="post">
<spring:bind path="formBeanId.name">
<br/>Name : <input name="name" value="<c:out value="${status.value}"></c:out>"><br>
<br/>
</spring:bind>

<spring:bind path="formBeanId.fruit">
Favorite Fruit:
<select name="fruit">
<c:forEach var="fruitType" items="${fruits}">
<option value="<c:out value="${fruitType.id}"></option>"
<c:if test="${fruitType.id == status.value}"> selected="selected"</c:if>
>
<c:out value="${fruitType.name}"></c:out>
</option>
</c:forEach>
</select>
</spring:bind>
<input type="submit" name="Submit" value="Submit">
<br/><br />
</form>

4. 摘要

本文仅涉及SpringMVC框架的简单使用方式,并没有涉及 Spring的其它部分,实际上Spring在系统的其他层提供了许多好处,包括与JDO和Hibernate的集成。作为一种通用方法,它促进并简化了测试驱动开发(TDD),它促进了许多有益的设计和开发实践。

Spring正在非常积极的发展和工具整合不断改进。例如,计划的增强功能包括与JSF,JMX和AspectJ的新集成或改进集成。XDoclet支持是可用的,还有一个Eclipse插件。鼓励大家学习研究一个这个Spring框架。