web框架—spring MVC介绍

web框架—spring MVC介绍

Spring 的web框架是围绕分发器(DispatcherServlet)设计的,DispatcherServlet将请求分发到不同的处理器,框架还包括可配置的处理器映射,视图解析,本地化,主题解析,还支持文件上传。缺省的处理器是一个简单的控制器(Controller)接口,这个接口仅仅定义了ModelAndView handleRequest(request,response)方法。你可以实现这个接口生成应用的控制器,但是使用Spring提供的一系列控制器实现会更好一些,比如AbstractController,AbstractCommandController,和SimpleFormController。应用控制器一般都从它们继承。注意你需要选择正确的基类:如果你没有表单,你就不需要一个FormController。这是和Structs的一个主要区别。

你可以使用任何对象作为命令对象或表单对象:不必实现某个接口或从某个基类继承。Spring的数据绑定相当灵活,例如,它认为类型不匹配这样的错误应该是应用级的验证错误,而不是系统错误。所以你不需要为了处理无效的表单提交,或者正确地转换字符串,在你的表单对象中用字符串类型重复定义你的业务对象属性。你应该直接绑定表单到业务对象上。这是和Struts的另一个重要不同,Struts是围绕象Action和ActionForm这样的基类构建的,每一种行为都是它们的子类。

和WebWork 相比,Spring将对象细分成不同的角色:它支持的概念有控制器(Controller),可选的命令对象(Command Object)或表单对象(Form Object),以及传递到视图的模型(Model)。模型不仅包含命令对象或表单对象,而且也包含任何引用数据。但是,WebWork的Action将所有的这些角色都合并在一个单独的对象里。WebWork允许你在表单中使用现有的业务对象,但是只能把它们定义成不同Action类的bean属性。更重要的是,在运算和表单赋值时,使用的是同一个处理请求的Action实例。因此,引用数据也需要被定义成Action的bean属性。这样在一个对象就承担了太多的角色。

对于视图:Spring的视图解析相当灵活。一个控制器实现甚至可以直接输出一个视图作为响应,这需要使用null返回 ModelAndView。在一般的情况下,一个ModelAndView实例包含视图名字和模型映射表,模型映射表提供了bean的名字及其对象(比如命令对象或表单对象,引用数据等等)的对应关系。视图名解析的配置是非常灵活的,可以通过bean的名字,属性文件或者你自己的ViewResolver 来实现。抽象的模型映射表完全抽象了表现层,没有任何限制:JSP,Velocity,或者其它的技术——任何表现层都可以直接和Spring集成。模型映射表仅仅将数据转换成合适的格式,比如JSP请求属性或者Velocity模版模型。

12.1.1. MVC实现的可扩展性

许多团队努力争取在技术和工具方面能使他们的投入更有价值,无论是现有的项目还是新的项目都是这样。具体地说,Struts 不仅有大量的书籍和工具,而且有许多开发者熟悉它。因此,如果你能忍受Struts的架构性缺陷,它仍然是web层一个很好的选择。WebWork和其它 web框架也是这样。

如果你不想使用Spring的web MVC框架,而仅仅想使用Spring提供的其它功能,你可以很容易地将你选择的web框架和Spring结合起来。只要通过Spring的 ContextLoadListener启动一个Spring的根应用上下文,并且通过它的ServletContext属性(或者Spring的各种帮助方法)在Struts或WebWork的Action中访问。注意到现在没有提到任何具体的“plugins”,因此这里也没有提及如何集成:从web 层的角度看,你可以仅仅把Spring作为一个库使用,根应用上下文实例作为入口。

所有你注册的bean和Spring的服务可以在没有 Spring的web MVC下被访问。Spring并没有在使用方法上和Struts或WebWork竞争,它只是提供单一web框架所没有的功能,从bean的配置到数据访问和事务处理。所以你可以使用Spring的中间层和(或者)数据访问层来增强你的应用,即使你只是使用象JDBC或Hibernate事务抽象这样的功能。

12.1.2. Spring MVC框架的特点

如果仅仅关注于web方面的支持,Spring有下面一些特点:

  *

    清晰的角色划分:控制器,验证器,命令对象,表单对象和模型对象;分发器,处理器映射和视图解析器;等等。

  *

    直接将框架类和应用类都作为JavaBean配置,包括通过应用上下文配置中间层引用,例如,从web控制器到业务对象和验证器的引用。

  *

    可适应性,但不具有强制性:根据不同的情况,使用任何你需要的控制器子类(普通控制器,命令,表单,向导,多个行为,或者自定义的),而不是要求任何东西都要从Action/ActionForm继承。

  *

    可重用的业务代码,而不需要代码重复:你可以使用现有的业务对象作为命令对象或表单对象,而不需要在ActionForm的子类中重复它们的定义。

  *

    可定制的绑定和验证:将类型不匹配作为应用级的验证错误,这可以保存错误的值,以及本地化的日期和数字绑定等,而不是只能使用字符串表单对象,手动解析它并转换到业务对象。

  *

    可定制的处理器映射,可定制的视图解析:灵活的模型可以根据名字/值映射,处理器映射和视图解析使应用策略从简单过渡到复杂,而不是只有一种单一的方法。

  *

    可定制的本地化和主题解析,支持JSP,无论有没有使用Spring标签库,支持JSTL,支持不需要额外过渡的Velocity,等等。

  *

    简单而强大的标签库,它尽可能地避免在HTML生成时的开销,提供在标记方面的最大灵活性。

12.2. 分发器(DispatcherServlet)

Spring的web框架——象其它web框架一样——是一个请求驱动的web框架,其设计围绕一个能将请求分发到控制器的servlet,它也提供其它功能帮助web应用开发。然而,Spring的DispatcherServlet所做的不仅仅是这些。它和Spring的ApplicationContext完全集成在一起,允许你使用Spring的其它功能。

DispatcherServlet和其它servlet一样定义在你的web应用的web.xml文件里。DispatcherServlet处理的请求必须在同一个web.xml文件里使用url-mapping定义映射。

<web-app>

  ...

  <servlet>

    <servlet-name>example</servlet-name>

    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <load-on-startup>1</load-on-startup>

  </servlet>

  <servlet-mapping>

    <servlet-name>example</servlet-name>

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

  </servlet-mapping>  

</web-app>

在上面的例子里,所有以.form结尾的请求都会由DispatcherServlet处理。接下来需要配置DispatcherServlet本身。正如在第 3.10 节 “介绍ApplicationContext”中所描述的,Spring中的ApplicationContexts可以被限制在不同的作用域。在web框架中,每个DispatcherServlet有它自己的WebApplicationContext,它包含了DispatcherServlet配置所需要的bean。DispatcherServlet 使用的缺省BeanFactory是XmlBeanFactory,并且DispatcherServlet在初始化时会在你的web应用的WEB-INF目录下寻找[servlet-name]-servlet.xml文件。DispatcherServlet使用的缺省值可以使用servlet初始化参数修改(详细信息如下)。

WebApplicationContext仅仅是一个拥有web应用必要功能的普通ApplicationContext。它和一个标准的ApplicationContext的不同之处在于它能够解析主题(参考第 12.7 节 “主题使用”),并且它知道和那个servlet关联(通过到ServletContext的连接)。WebApplicationContext被绑定在ServletContext上,当你需要的时候,可以使用RequestContextUtils找到WebApplicationContext。

Spring 的DispatcherServlet有一组特殊的bean,用来处理请求和显示相应的视图。这些bean包含在Spring的框架里,(可选择)可以在 WebApplicationContext中配置,配置方式就象配置其它bean的方式一样。这些bean中的每一个都在下面被详细描述。待一会儿,我们就会提到它们,但这里仅仅是让你知道它们的存在以便我们继续讨论DispatcherServlet。对大多数bean,都提供了缺省值,所有你不必要担心它们的值。

表 12.1. WebApplicationContext中特殊的bean

名称     解释

处理器映射(handler mapping(s))     (第 12.4 节 “处理器映射”) 前处理器,后处理器和控制器的列表,它们在符合某种条件下才被执行(例如符合控制器指定的URL)

控制器(controller(s))     (第 12.3 节 “控制器”) 作为MVC三层一部分,提供具体功能(或者至少能够访问具体功能)的bean

视图解析器(view resolver)     (第 12.5 节 “视图与视图解析”) 能够解析视图名,在DispatcherServlet解析视图时使用

本地化信息解析器(locale resolver)     (第 12.6 节 “使用本地化信息”) 能够解析用户正在使用的本地化信息,以提供国际化视图

主题解析器(theme resolver)     (第 12.7 节 “主题使用”) 能够解析你的web应用所使用的主题,比如,提供个性化的布局

multipart解析器     (第 12.8 节 “Spring对multipart(文件上传)的支持”) 提供HTML表单文件上传功能

处理器异常解析器(handlerexception resolver)     (第 12.9 节 “处理异常”) 将异常对应到视图,或者实现某种复杂的异常处理代码

当DispatcherServlet被安装配置好,DispatcherServlet一接收到请求,处理就开始了。下面的列表描述了DispatcherServlet处理请求的全过程:

  1.

    搜索WebApplicationContext,并将它绑定到请求的一个属性上,以便控制器和处理链上的其它处理器能使用WebApplicationContext。缺省它被绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE这个关键字上

  2.

    绑定本地化信息解析器到请求上,这样使得处理链上的处理器在处理请求(显示视图,准备数据等等)时能解析本地化信息。如果你不使用本地化信息解析器,它不会影响任何东西,忽略它就可以了

  3.

    绑定主题解析器到请求上,使得视图决定使用哪个主题(如果你不需要主题,可以忽略它,解析器仅仅是绑定,如果你不使用它,不会影响任何东西)

  4.

    如果multipart解析器被指定,请求会被检查是否使用了multipart,如果是,multipart解析器会被保存在MultipartHttpServletRequest中以便被处理链中的其它处理器使用(下面会讲到更多有关multipart处理的内容)

  5.

    搜索合适的处理器。如果找到,执行和这个处理器相关的执行链(预处理器,后处理器,控制器),以便准备模型数据

  6.

    如果模型数据被返还,就使用配置在WebApplicationContext中的视图解析器,显示视图,否则(可能是安全原因,预处理器或后处理器截取了请求),虽然请求能够提供必要的信息,但是视图也不会被显示。

在请求处理过程中抛出的异常可以被任何定义在WebApplicationContext中的异常解析器所获取。使用这些异常解析器,你可以在异常抛出时定义特定行为。

Spring的DispatcherServlet也支持返回Servlet API定义的last-modification-date,决定某个请求最后修改的日期很简单。DispatcherServlet会首先寻找一个合适的处理器映射,检查处理器是否实现了LastModified接口,如果是,将long getLastModified(request)的值返回给客户端。

你可以在web.xml文件中添加上下文参数或servlet初始化参数定制Spring的DispatcherServlet。下面是一些可能的参数。

表 12.2. DispatcherServlet初始化参数

参数     解释

contextClass     实现WebApplicationContext的类,当前的servlet用它来实例化上下文。如果这个参数没有指定,使用XmlWebApplicationContext

contextConfigLocation     传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符)来支持多个上下文(在多上下文的情况下,被定义两次的bean中,后面一个优先)

namespace     WebApplicationContext命名空间。缺省是[server-name]-servlet

12.3. 控制器

控制器的概念是MVC设计模式的一部分。控制器定义了应用的行为,至少能使用户访问到这些行为。控制器解释用户输入,并将其转换成合理的模型数据,从而可以进一步地由视图展示给用户。Spring以一种抽象的方式实现了控制器概念,这样使得不同类型的控制器可以被创建。Spring包含表单控制器,命令控制器,执行向导逻辑的控制器等等。

Spring控制器架构的基础是org.springframework.mvc.Controller接口。

public interface Controller {

  /**

  * Process the request and return a ModelAndView object which the DispatcherServlet

  * will render.

  */

  ModelAndView handleRequest(

    HttpServletRequest request,

    HttpServletResponse response)

  throws Exception;

}

你可以发现Controller接口仅仅声明了一个方法,它能够处理请求并返回合适的模型和视图。Spring MVC实现的基础就是这三个概念:ModelAndView和Controller。 因为Controller接口是完全抽象的,Spring提供了许多已经包含一定功能的控制器。控制器接口仅仅定义了每个控制器提供的共同功能:处理请求并返回一个模型和一个视图。

12.3.1. AbstractController 和 WebContentGenerator

当然,就一个控制器接口并不够。为了提供一套基础设施,所有的Spring控制器都从 AbstractController 继承,AbstractController 提供缓存和其它比如 mimetype 的设置的功能。

表 12.3. AbstractController提供的功能

功能     解释

supportedMethods     指定这个控制器应该接受什么样的请求方法。通常它被设置成GET和POST,但是你可以选择你想支持的方法。如果控制器不支持请求发送的方法,客户端会得到通知(ServletException)

requiresSession     指定这个控制器是否需要会话。这个功能提供给所有控制器。如果控制器在没有会话的情况下接收到请求,用户被通知ServletException

synchronizeSession     如果你需要使控制器同步访问用户会话,使用这个参数。具体地说,继承的控制器要重载handleRequestInternal方法,如果你指定了这个变量,控制器就被同步化。

cacheSeconds     当你需要控制器在HTTP响应中生成缓存指令,用这参数指定一个大于零的整数。缺省它被设置为-1,所以就没有生成缓存指令

useExpiresHeader     指定你的控制器使用HTTP 1.0兼容的"Expires"。缺省为true,所以你可以不用修改它

useCacheHeader     指定你的控制器使用HTTP 1.0兼容的"Cache-Control"。缺省为true,所以你也可以不用修改它

最后的两个属性是WebContentGenerator定义的,WebContentGenerator是AbstractController的超类……

当使用AbstractController作为你的控制器基类时(一般不推荐这样做,因为有许多预定义的控制器你可以选择),你只需要重载handleRequestInternal(HttpServletRequest, HttpServletResponse)这个方法,实现你自己的逻辑,并返回一个ModelAndView对象。下面这个简单例子包含一个类和在web应用上下文中的定义。

package samples;

public class SampleController extends AbstractController {

  public ModelAndView handleRequestInternal(

    HttpServletRequest request,

    HttpServletResponse response)

  throws Exception {

    ModelAndView mav = new ModelAndView("foo", new HashMap());

  }

}

<bean >实现HandlerExceptionResolver需要实现resolveException(Exception, Handler)方法并返回ModelAndView,除了HandlerExceptionResolver,你还可以使用SimpleMappingExceptionResolver。这个解析器使你能够获取任何抛出的异常的类名,并将它映射到视图名。这和servlet API的异常映射在功能上是等价的,但是它还为不同的处理器抛出的异常做更细粒度的映射提供可能。