`
pengjianbo1
  • 浏览: 228894 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

Spring Web框架

阅读更多

Chapter 13. Web框架

13.1. 介绍

Spring的web框架是围绕DispatcherServlet来进行设计的。DispatcherServlet的作用是将请求分发到不同的处理器。Spring的web框架包括可配置的处理器(handler)映射、视图(view)解析、本地化(local)解析、主题(theme)解析以及对上传文件解析。处理器是对Controller接口的实现,该接口仅仅定义了ModelAndView handleRequest(request, response)方法。你可以通过实现这个接口来生成自己的控制器(也可以称之为处理器),但是从Spring提供的一系列控制器继承会更省事,比如AbstractControllerAbstractCommandControllerSimpleFormController。注意,你需要选择正确的基类:如果你没有表单,你就不需要一个FormController。这是和Structs的一个主要区别。

你可以使用任何对象作为命令对象(或表单对象):不必实现某个接口或从某个基类继承。Spring的数据绑定相当灵活,例如,它认为类型不匹配这样的错误应该是应用级的验证错误,而不是系统错误。所以你不需要为了保证表单内容的正确提交,而重复定义一个和业务对象有相同属性的表单对象来处理简单的无类型字符串或者对字符串进行转换。这也是和Struts相比的另一个重要区别,Struts是围绕象ActionActionForm这样的基类构建的。

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

Spring的视图解析相当灵活。一个控制器甚至可以直接向response输出一个视图(此时控制器返回ModelAndView的值必须是null)。在一般的情况下,一个ModelAndView实例包含一个视图名字和一个类型为Map的model,一个model是一些以bean的名字为key,以bean对象(可以是命令或form,也可以是其他的JavaBean)为value的名值对。对视图名称的解析处理也是高度可配置的,可以通过bean的名字、属性文件或者自定义的ViewResolver实现来进行解析。实际上基于Map的model(也就是MVC中的M))是高度抽象的,适用于各种表现层技术。也就是说,任何表现层都可以直接和Spring集成,无论是JSP、Velocity还是其它表现层技术。Map model可以被转换成合适的格式,比如JSP request attribute或者Velocity template model。

13.1.1. 与其他web框架的集成

由于种种原因,许多团队倾向于使用其他的web框架。比如,某些团队已经在其他的技术和工具方面进行了投入,他们希望充分利用已有的经验。另外,Struts不仅有大量的书籍和工具,而且有许多开发者熟悉它。因此,如果你能忍受Struts的架构性缺陷,它仍然是web层一个不错的选择。WebWork和其它的web框架也是这样。

如果你不想使用Spring的web MVC框架,但仍希望使用Spring提供的其它功能,你可以很容易地将你选择的web框架和Spring结合起来。只需通过Spring的ContextLoadListener启动一个root application context,你就可以在Struts或WebWork的Action中,通过ServletContext属性(或者Spring提供的相应辅助方法)进行访问。请注意我们没有提到任何具体的“plugins”,因此也不必提及如何集成。从web层的角度看,你可以以root application context实例为入口,把Spring作为一个library使用。

即便你不使用Spring的web框架,经注册的所有bean和所有Spring服务仍然可以使用。从这个用法上来讲,Spring并没有和Struts或WebWork竞争,它只是提供这些纯粹的web框架所没有的功能,例如:bean的配置、数据访问和事务处理。因此你可以使用Spring的中间层或者数据访问层来增强你的应用,即便你只是需要使用像JDBC或Hibernate事务抽象这样的功能。

13.1.2. Spring Web MVC框架的特点

Spring Web MVC框架提供了大量独特的功能,包括:

  • 清晰的角色划分:控制器(controller)、验证器(validator)、命令对象(command object)、表单对象(form object)、模型对象(model object)、Servlet分发器(DispatcherServlet)、处理器映射(handler mapping)、视图解析器(view resolver)等等。 每一个角色都可以由一个专门的对象来实现。

  • 强大而直接的配置方式:将框架类和应用类都作为JavaBean配置,支持在一个context中引用其他context的中JavaBean,例如,在web控制器中对业务对象和验证器(validator)的引用。

  • 可适配、非侵入的controller:你可以根据不同的应用场景,选择合适的控制器子类(simple型、command型、form型、wizard型、multi-action型或者自定义),而不是从单一控制器(比如Action/ActionForm)继承。

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

  • 可定制的绑定(binding) 和验证(validation):比如将类型不匹配作为应用级的验证错误,这可以保存错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象,需要手动解析它并转换到业务对象。

  • 可定制的handler mapping和view resolution:Spring提供从最简单的的URL映射,到复杂的、专用的定制策略。与某些MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。灵活。

  • 灵活的model转换: 在Springweb框架中,使用基于Map的名/值对来达到轻易地与各种视图技术的集成。

  • 可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。

  • 简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(theme)之类的许多功能。它提供在标记方面的最大灵活性。如欲了解详情,请参阅附录Appendix D, spring.tld

  • 新增加的JSP表单标签库:在Spring2.0中刚刚引入的表单标签库,使得在JSP中编写表单更加容易。如欲了解详情,请参阅附录Appendix E, spring-form.tld

  • Spring Bean的生命周期可以被限制在当前的HTTP Request或者HTTP Session。准确的说,这并非Spring MVC框架本身特性,而应归属于Sping MVC使用的WebApplicationContext容器。该功能在Section 3.4.3, “其他作用域”有详细描述。

13.2. DispatcherServlet

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

下图展示了DispatcherServlet对请求的处理流程。熟悉设计模式的读者可能会发现DispatcherServlet应用了“Front Controller”这个模式(很多其他的主流web框架也都用到了这个模式)。

 

Spring Web MVC处理请求的工作流程

 

DispatcherServlet实际上是一个Servlet(它从HttpServlet继承而来)。和其它Servlet一样,DispatcherServlet定义在web应用的web.xml文件里。DispatcherServlet处理的请求必须在同一个web.xml文件里使用url-mapping定义映射。下面的例子演示了如何配置DispatcherServlet

<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结尾的请求都会由名为exampleDispatcherServlet处理。这只是配置Spring Web MVC的第一步。接下来需要配置DispatcherServlet本身和Spring Web MVC 框架用到的其他的bean。

正如在Section 3.8, “ApplicationContext”中所描述的,Spring中的ApplicationContext可以被限制在不同的作用域(scope)中。在web MVC框架中,每个DispatcherServlet有它自己的WebApplicationContext,这个context继承了根 WebApplicationContext的所有bean定义。这些继承的bean也可以在每个serlvet自己的所属的域中被覆盖(override),覆盖后的bean可以被设置成只有这个servlet实例自己才可以使用的属性。

 

Spring Web MVC中的Context体系

 

DispatcherServlet的初始化过程中,Spring会在web应用的WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml的配置文件,生成文件中定义的bean。这些bean会覆盖在全局范围(global cope)中定义的同名的bean。

下面这个例子展示了在web.xmlDispatcherServlet的配置:

<web-app>
    ...
    <servlet>
        <servlet-name>golfing</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>golfing</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

要进行如上的servlet配置,你还需要配置/WEB-INF/golfing-servlet.xml这样一个文件。golfing-servlet.xml这个文件应该声明你在Spring Web MVC 框架中需要的bean。 这个文件的路径也可以通过web.xml中servlet的初始化参数来更改。(详情见下面的例子。)

WebApplicationContext仅仅是一个拥有web应用必要功能的普通ApplicationContext。它与一个标准的ApplicationContext的不同之处在于,它能够解析theme(参考Section 13.7, “使用主题”),并且它知道自己与哪个servlet相关联(通过ServletContext)。WebApplicationContext被绑定在ServletContext上,当你需要的时候,可以使用RequestContextUtils提供的静态方法找到WebApplicationContext

Spring的DispatcherServlet有一组特殊的bean,用来处理请求和渲染相应的视图。这些bean包含在Spring的框架里,可以在WebApplicationContext中配置,配置方式与配置其它bean相同。这些bean中的每一个都在下文作详细描述。此刻读者只需知道它们的存在,便继续对DispatcherServlet进行讨论。对大多数bean,Spring都提供了合理的缺省值,所以在开始阶段,你不必担心如何对其进行配置。

Table 13.1. WebApplicationContext中特殊的bean

名称 描述
控制器(Controller) 控制器 实现的是MVC中C 那个组成部分。
处理器映射(Handler mapping) 处理器映射包含预处理器(pre-processor),后处理器(post-processor)和控制器的列表,它们在符合某种条件时才被执行(例如符合控制器指定的URL)。
视图解析器(View resolvers) 视图解析器 可以将视图名解析为对应的视图。
本地化解析器(Locale resolver) 本地化解析器能够解析用户正在使用的本地化设置,以提供国际化视图。
主题解析器(Theme resolver) 主题解析器能够解析你的web应用所使用的主题,以提供个性化的布局。
上传文件解析器(multipart file resolver) 上传文件解析器提供HTML表单文件上传功能。
处理器异常解析器(Handler exception resolver(s)) 处理器异常解析器可以将异常对应到视图,或者实现更加复杂的异常处理代码。

DispatcherServlet配置好以后,DispatcherServlet接收到与其对应的请求之时,处理就开始了。下面的列表描述了DispatcherServlet处理请求的全过程:

  1. 找到WebApplicationContext并将其绑定到请求的一个属性上,以便控制器和处理链上的其它处理器能使用WebApplicationContext。默认的属性名为DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE。

  2. 将本地化解析器(localResolver)绑定到请求上,这样使得处理链上的处理器在处理请求(准备数据、显示视图等等)时能进行本地化处理。若不使用本地化解析器,也不会有任何副作用,因此如果不需要本地化解析,忽略它就可以了。

  3. 将主题解析器绑定到请求上,这样视图可以决定使用哪个主题。如果你不需要主题,可以忽略它。

  4. 如果上传文件解析器被指定,Spring会检查每个接收到的请求是否存在上传文件,如果是,这个请求将被封装成MultipartHttpServletRequest以便被处理链中的其它处理器使用。(关于文件上传的更多内容请参考Section 13.8.2, “使用MultipartResolver”。)

  5. 找到合适的处理器,执行和这个处理器相关的执行链(预处理器,后处理器,控制器),以便为视图准备模型数据。

  6. 如果模型数据被返回,就使用配置在WebApplicationContext中的视图解析器显示视图,否则视图不会被显示。有多种原因可以导致返回的数据模型为空,比如预处理器或后处理器可能截取了请求,这可能是出于安全原因,也可能是请求已经被处理过,没有必要再处理一次。

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

Spring的DispatcherServlet也支持返回Servlet API定义的last-modification-date。决定某个请求最后修改的日期很简单:DispatcherServlet会首先寻找一个合适的handler mapping,检查从中取得指定的处理器是否实现了LastModified接口,如果是,将调用long getLastModified(request)方法,并将结果返回给客户端。

你可以通过两种方式定制Spring的DispatcherServlet:在web.xml文件中增加添加context参数,或servlet初始化参数。下面是目前支持的参数。

Table 13.2. DispatcherServlet初始化参数

参数 描述
contextClass 实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定,默认使用XmlWebApplicationContext
contextConfigLocation 传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符)来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)。
namespace WebApplicationContext命名空间。默认值是[server-name]-servlet

13.3. 控制器

控制器的概念是MVC设计模式的一部分(确切地说,是MVC中的C)。应用程序的行为通常被定义为服务接口,而控制器使得用户可以访问应用所提供的服务。控制器解析用户输入,并将其转换成合理的模型数据,从而可以进一步由视图展示给用户。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实现的基础就是这三个概念:Mdel、View(ModelAndView)以及 Controller。虽然Controller接口是完全抽象的,但Spring也提供了许多你可能会用到的控制器。Controller接口仅仅定义了每个控制器都必须提供的基本功能:处理请求并返回一个模型和一个视图。

13.3.1. AbstractControllerWebContentGenerator

为了提供一套基础设施,所有的Spring控制器都继承了 AbstractControllerAbstractController 提供了诸如缓存支持和mimetype设置这样的功能。

Table 13.3. AbstractController提供的功能

功能 描述
supportedMethods 指定这个控制器应该接受什么样的请求方法。通常它被设置成同时支持GET和POST,但是你可以选择你想支持的方法。如果控制器不支持请求发送的方法,客户端会得到通知(通常是抛出一个ServletException)。
requiresSession 指定这个控制器是否需要HTTP session才能正常工作。如果控制器在没有session的情况下接收到请求,客户端会因为抛出ServletException而得到通知。
synchronizeSession 指定controller是否同步用户的HTTP session。
cacheSeconds 指定controller通知客户端对数据内容缓存的秒数,一般为大于零的整数。默认值为-1,即不缓存。
useExpiresHeader 指定Controller在响应请求时是否兼容HTTP 1.0 Expires header。缺省值为true
useCacheHeader 指定Controller在相应请求时是否兼容HTTP 1.1 Cache-Control header。默认值为true

当从AbstractController继承时,需要实现handleRequestInternal(HttpServletRequest, HttpServletResponse)抽象方法,该方法将用来实现自己的逻辑,并返回一个ModelAndView对象。下面这个简单的例子演示了如何从AbstractController继承以及如何在applicationContext.xml中进行配置

package samples;

public class SampleController extends AbstractController {

    public ModelAndView handleRequestInternal(
        HttpServletRequest request,
        HttpServletResponse response) throws Exception {

        ModelAndView mav = new ModelAndView("hello");
        mav.addObject("message", "Hello World!");
        return mav;        
    }
}
<bean id="sampleController" class="samples.SampleController">
    <property name="cacheSeconds" value="120"/>
</bean>

该controller返回的ModelAndView使用了硬编码的视图名(尽管这样做不好),并通知客户端将响应数据缓存2分钟。除了通过以上方式创建和配置controller之外,还需要配置handler mapping(请参考Section 13.4, “处理器映射(handler mapping)”),这样该controller就可以工作了。

13.3.2. 其它的简单控制器

尽管可以继承AbstractController来实现自己的控制器,不过Spring提供的众多控制器减轻了我们开发简单MVC应用时的负担。ParameterizableViewController基本上和上面例子中的一样,不同的是,你可以在applicationContext.xml配置中指定返回视图名从而避免了在Java代码中的硬编码。

UrlFilenameViewController会检查URL,获取文件请求的文件名,并把它作为视图名加以使用。。例如,http://www.springframework.org/index.html对应的视图文件名是index

13.3.3. MultiActionController

MultiActionController将多个行为(action)合并在一个控制器里,这样可以把相关功能组合在一起。MultiActionController位于org.springframework.web.mvc.multiaction包中,它通过将请求映射到正确的方法名来调用方法。当在一个控制器存在大量公共的行为,但是有多个调用入口时,使用MultiActionController就特别方便。

Table 13.4. MultiActionController提供的功能

功能 描述
delegate MultiActionController有两种使用方式。第一种是你继承MultiActionController,并在子类中指定由MethodNameResolver解析的方法(这种情况下不需要这个delegate参数)。第二种是你定义一个代理对象,由它提供MethodNameResolver解析出来的方法(这种情况下,你必须使用这个配置参数定义代理对象)。
methodNameResolver MultiActionController需要一种策略,使其可以通过解析请求信息来获得要调用的方法。这个解析策略由MethodNameResolver这个接口定义的。这个参数允许你实现MethodNameResolver接口,然后在控制器中使用你的策略。

MultiActionController所支持的方法需要符合下列格式:

// anyMeaningfulName can be replaced by any methodname
public [ModelAndView | Map | void] anyMeaningfulName(HttpServletRequest, HttpServletResponse [, Exception | AnyObject]);

注意:在此不允许方法重载,因为MultiActionController无法分辨出重载(overloading)了的方法。此外,你可以定义exception handler来处理方法中抛出的异常。

Exception 参数是可选的,它可以是任何异常,只要它是java.lang.Exceptionjava.lang.RuntimeException的子类。AnyObject参数也是可选的,它可以是任何对象。HTTP Request中的参数会存在这个对象中,以便使用。

下面几个例子示范了MultiActionController正确的方法定义。

标准格式(跟Controller接口定义的一样)。

public ModelAndView doRequest(HttpServletRequest, HttpServletResponse)

下面这个方法支持Login参数, 这个参数中包含从请求中抽取出来的信息。

public ModelAndView doLogin(HttpServletRequest, HttpServletResponse, Login)

下面这个方法可以处理Exception

public ModelAndView processException(HttpServletRequest, HttpServletResponse, IllegalArgumentException)

下面这个方法不返回任何数值。 (请参考后面的章节 Section 13.11, “惯例优先原则(convention over configuration)”)

public void goHome(HttpServletRequest, HttpServletResponse)

This signature has a Map return type (see the section entitled Section 13.11, “惯例优先原则(convention over configuration)” below).

下面这个方法返回一个Map。 (请参考后面的章节Section 13.11, “惯例优先原则(convention over configuration)”)

public Map doRequest(HttpServletRequest, HttpServletResponse)

MethodNameResolver负责从请求中解析出需要调用的方法名称。下面是Spring中内置的三个MethodNameResolver 实现。

  • ParameterMethodNameResolver - 解析请求参数,并将它作为方法名。(对应http://www.sf.net/index.view?testParam=testIt的请求,会调用 testIt(HttpServletRequest,HttpServletResponse)方法)。使用paramName配置参数,可以设定要检查的参数。

  • InternalPathMethodNameResolver -从路径中获取文件名作为方法名 (http://www.sf.net/testing.view的请求会调用testing(HttpServletRequest,HttpServletResponse)方法。

  • PropertiesMethodNameResolver - 使用用户自定义的属性对象,将请求的URL映射到方法名。当属性中包含/index/welcome.html=doIt时,发到/index/welcome.html 的请求会调用doIt(HttpServletRequest, HttpServletResponse)这个方法。 这个方法名解析器可以和PathMatcher一起工作,比如上边那个URL写成/**/welcom?.html也是可以的。

我们来看一组例子。首先是一个使用ParameterMethodNameResolver和代理(delegate)属性的例子,它接受包含参数名"method"的请求,调用方法retrieveIndex

<bean id="paramResolver" class="org....mvc.multiaction.ParameterMethodNameResolver">
  <property name="paramName" value="method"/>
</bean>

<bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController">
  <property name="methodNameResolver" ref="paramResolver"/>
  <property name="delegate" ref="sampleDelegate"/>
</bean>

<bean id="sampleDelegate" class="samples.SampleDelegate"/>

## together with

public class SampleDelegate {

    public ModelAndView retrieveIndex(HttpServletRequest req, HttpServletResponse resp) {

        return new ModelAndView("index", "date", new Long(System.currentTimeMillis()));
    }
}

当使用上面的代理对象时,我们也可以使用PropertiesMethodNameRseolver来匹配一组URL,将它们映射到我们定义的方法上:

<bean id="propsResolver" class="org....mvc.multiaction.PropertiesMethodNameResolver">
  <property name="mappings">
    <value>
        /index/welcome.html=retrieveIndex
        /**/notwelcome.html=retrieveIndex
        /*/user?.html=retrieveIndex
    </value>
  </property>
</bean>

<bean id="paramMultiController" class="org....mvc.multiaction.MultiActionController">
    <property name="methodNameResolver" ref="propsResolver"/>
    <property name="delegate" ref="sampleDelegate"/>
</bean>

13.3.4. 命令控制器

Spring的CommandController是Spring MVC的重要部分。命令控制器提供了一种和数据对象交互的方式,并动态地将来自HttpServletRequest的参数绑定到你指定的数据对象上。它的功能和Struts中的ActionForm有点像,不过在Spring中,你不需要实现任何接口来实现数据绑定。首先,让我们看一下有哪些可以使用的命令控制器:

  • AbstractCommandController --你可以使用该抽象命令控制器来创建自己的命令控制器,它能够将请求参数绑定到你指定的命令对象。这个类并不提供任何表单功能,但是它提供验证功能,并且让你在子类中去实现如何处理由请求参数产生的命令对象。

  • AbstractFormController--一个支持表单提交的抽象控制器类。使用这个控制器,你可以定义表单,并使用从控制器获取的数据对象构建表单。当用户输入表单内容,AbstractFormController将用户输入的内容绑定到命令对象,验证表单内容,并将该对象交给控制器,完成相应的操作。它支持的功能有防止重复提交、表单验证以及一般的表单处理流程。子类需要实现自己的方法来指定采用哪个视图来显示输入表单,哪个视图显示表单正确提交后的结果。如果你需要表单,但不想在应用上下文中指定显示给用户的视图,就使用这个控制器。

  • SimpleFormController --这是一个form cotnroller,当需要根据命令对象来创建相应的form的时候,该类可以提供更多的支持。你可以为其指定一个命令对象,显示表单的视图名,当表单提交成功后显示给用户的视图名等等。

  • AbstractWizardFormController --这是一个抽象类,继承这个类需要实现validatePage()processFinish()processCancel() 方法。

    你有可能也需要写一个构造器,它至少需要调用setPages()setCommandName()方法。setPages()的参数是一个String数组,这个数组包含了组成向导的视图名。setCommandName()的参数是一个String,该参数将用来在视图中调用你的命令对象。

    AbstractFormController的实现一样, 你需要使用命令对象(其实就是一个JavaBean, 这个bean中包含了表单的信息)。你有两个选择:在构造函数中调用setCommandClass()方法(参数是命令对象的类名),或者实现formBackingObject()方法。

    AbstractWizardFormController 有几个你可以复写(override)的方法。最有用的一个是referenceData(..)。这个方法允许你把模型数据以Map的格式传递给视图;getTargetPage() 允许你动态地更改向导的页面顺序,或者直接跳过某些页面;onBindAndValidate() 允许你复写内置的绑定和验证流程。

    最后,我们有必要提一下setAllowDirtyBack()setAllowDirtyForward()两个方法。 你可以在getTargetPage()中调用这两个方法,这两个方法将决定在当前页面验证失败时,是否允许向导前移或后退。

    AbstractWizardFormController的更详细内容请参考JavaDoc。在Spring附带的例子jPetStore中,有一个关于向导实现的例子: org.springframework.samples.jpetstore.web.spring.OrderFormController

13.4. 处理器映射(handler mapping)

通过处理器映射,你可以将web请求映射到正确的处理器(handler)上。Spring内置了很多处理器映射策略,例如:SimpleUrlHandlerMapping或者BeanNameUrlHandlerMapping。现在我们先来看一下HandlerMapping的基本概念。

HandlerMapping的基本功能是将请求传递到HandlerExecutionChain上。首先,这个HandlerExecutionChain必须包含一个能处理该请求的处理器。其次,这个链也可以包含一系列可以拦截请求的拦截器。当收到请求时,DispatcherServlet将请求交给处理器映射,让它检查请求并找到一个适当的HandlerExecutionChain。然后,DispatcherServlet执行定义在链中的处理器和拦截器(interceptor)。

在处理器映射中通过配置拦截器(包括处理器执行前、执行后、或者执行前后运行拦截器)将使其功能更强大。同时也可以通过自定义HandlerMapping来支持更多的功能。比如,一个自定义的处理器映射不仅可以根据请求的URL,而且还可以根据和请求相关的session状态来选择处理器。

下面我们将讲述Spring中最常用的两个处理器映射。它们都是AbstractHandlerMapping的子类,同时继承了下面这些属性:

  • interceptors: 在映射中使用的拦截器列表。HandlerInterceptor将在Section 13.4.3, “拦截器(HandlerInterceptor)”这一节讲述。

  • defaultHandler: 默认的处理器。当没有合适的处理器可以匹配请求时,这个处理器就会被使用。

  • order: 根据每个映射的order属性值 (由org.springframework.core.Ordered 接口定义),Spring 将上下文中可用的映射进行排序,然后选用第一个和请求匹配的处理器。

  • alwaysUseFullPath:如果这个属性被设成true,Spring 将会使用绝对路径在当前的servlet context中寻找合适的处理器。 这个属性的默认值是false,在这种情况下,Spring会使用当前servlet context中的相对路径。例如,如果一个servlet在servlet-mapping中用的值是/testing/*,当alwaysUseFullPath 设成true时,处理器映射中的URL格式应该使用/testing/viewPage.html,当这个属性设成false, 同一个URL应该写成 /viewPage.html

  • urlPathHelper:指定在分析URL时使用的UrlPathHelper。通常使用其默认值。

  • urlDecode: 这个属性的默认值是false。HttpServletRequest返回未解码的访问URL和URI。HttpServletRequest中请求的URL和URI还保留在HTTP协议所定义编码状态,如果你想在HandlerMapping使用它们发现合适的处理器之前对URL进行解码,你应该把这个属性设成true (注意这需要JDK 1.4的支持)。解码方法会选用HTTP请求中指定的编码格式,或缺省的ISO-8859-1编码方法。 HTTP请求中一般会声明编码的格式,如果没有的话,默认值是ISO-8859-1。Spring会使用相应的解码算法。

  • lazyInitHandlers: 这个属性允许你设置是否延迟singleton处理器的初始化工作(prototype处理器的初始化都是延迟的)。 这个属性的默认值是false.

(注意:最后四个属性只有org.springframework.web.servlet.handler.AbstractUrlHandlerMapping的子类才有。)

13.4.1. BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping是一个简单但很强大的处理器映射,它将收到的HTTP请求映射到bean的名字上(这些bean需要在web应用上下文中定义)。例如,为了实现一个用户新建账号的功能,我们提供了FormController (关于CommandController和FormController请参考Section 13.3.4, “命令控制器”)和显示表单的JSP视图(或Velocity模版)。当使用BeanNameUrlHandlerMapping时,我们用如下方式将包含http://samples.com/editaccount.form的访问请求映射到指定的FormController上:

<beans>
  <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

  <bean name="/editaccount.form" class="org.springframework.web.servlet.mvc.SimpleFormController">
    <property name="formView" value="account"/>
    <property name="successView" value="account-created"/>
    <property name="commandName" value="account"/>
    <property name="commandClass" value="samples.Account"/>
  </bean>
<beans>

所有对/editaccount.form的请求就会由上面的FormController处理。当然我们得在web.xml中定义servlet-mapping,接受所有以.form结尾的请求。

<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

   <!-- maps the sample dispatcher to *.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>
    ...
</web-app>
Note
[Note]

要使用BeanNameUrlHandlerMapping,无须(如上所示)在web应用上下文中定义它。缺省情况下,如果在上下文中没有找到处理器映射,DispatcherServlet会为你创建一个BeanNameUrlHandlerMapping

13.4.2. SimpleUrlHandlerMapping

另一个更强大的处理器映射是SimpleUrlHandlerMapping。它在应用上下文中可以进行配置,并且有Ant风格的路径匹配功能。(请参考org.springframework.util.PathMatcher的JavaDoc)。下面几个例子可以帮助理解:

<web-app>
    ...
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- maps the sample dispatcher to *.form -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.form</url-pattern>
    </servlet-mapping>

    <!-- maps the sample dispatcher to *.html -->
    <servlet-mapping>
        <servlet-name>sample</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    ...
</web-app>

The above web.xml configuration snippet enables all requests ending with .html and .form to be handled by the sample dispatcher servlet.

上面的web.xml设置允许所有以.html.form结尾的请求都由这个sample DispatcherServlet处理。

<beans>
        
    <!-- no 'id' required, HandlerMapping beans are automatically detected by the DispatcherServlet -->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /*/account.form=editAccountFormController
                /*/editaccount.form=editAccountFormController
                /ex/view*.html=helpController
                /**/help.html=helpController
            </value>
        </property>
    </bean>

    <bean id="helpController"
          class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>

    <bean id="editAccountFormController"
          class="org.springframework.web.servlet.mvc.SimpleFormController">
        <property name="formView" value="account"/>
        <property name="successView" value="account-created"/>
        <property name="commandName" value="Account"/>
        <property name="commandClass" value="samples.Account"/>
    </bean>
<beans>

这个处理器映射首先将对所有目录中文件名为help.html的请求传递给helpControllerhelpController是一个UrlFilenameViewController (要了解更多关于控制器的信息,请参阅 Section 13.3, “控制器”)。对ex目录中所有以view开始,以.html 结尾的请求都会被传递给helpController。 同样的,我们也为editAccountFormController定义了两个映射。

13.4.3. 拦截器(HandlerInterceptor

Spring的处理器映射支持拦截器。当你想要为某些请求提供特殊功能时,例如对用户进行身份认证,这就非常有用。

处理器映射中的拦截器必须实现org.springframework.web.servlet包中的HandlerInterceptor接口。这个接口定义了三个方法,一个在处理器执行前被调用,一个在处理器执行后被调用,另一个在整个请求处理完后调用。这三个方法提供你足够的灵活度做任何处理前后的操作。

preHandle(..)方法有一个boolean返回值。使用这个值,你可以调整执行链的行为。当返回true时,处理器执行链将继续执行,当返回false时,DispatcherServlet认为该拦截器已经处理完了请求(比如显示正确的视图),而不继续执行执行链中的其它拦截器和处理器。

下面的例子提供了一个拦截器,它拦截所有请求,如果当前时间不是在上午9点到下午6点,它将用户重定向到某个页面。

<beans>
    <bean id="handlerMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
        <property name="mappings">
            <value>
                /*.form=editAccountFormController
                /*.view=editAccountFormController
            </value>
        </property>
    </bean>

    <bean id="officeHoursInterceptor"
          class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime" value="9"/>
        <property name="closingTime" value="18"/>
    </bean>
<beans>
package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;

    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }

    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }

    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler) throws Exception {

        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour < closingTime) {
            return true;
        } else {
            response.sendRedirect("http://host.com/outsideOfficeHours.html");
            return false;
        }
    }
}

所有的请求都将被TimeBasedAccessInterceptor截获,如果当前时间不在上班时间,用户会被重定向到一个静态html页面,提供诸如只有上班时间才能访问网站之类的告示。

Spring还提供了一个adapter类HandlerInterceptorAdapter让用户更方便的扩展HandlerInterceptor接口。

13.5. 视图与视图解析

所有web应用的MVC框架都有它们处理视图的方式。Spring提供了视图解析器供你在浏览器显示模型数据,而不必被束缚在特定的视图技术上。Spring内置了对JSP,Velocity模版和XSLT视图的支持。 Chapter 14, 集成视图技术这一章详细说明了Spring如何与不同的视图技术集成。

ViewResolverView是Spring的视图处理方式中特别重要的两个接口。 ViewResolver提供了从视图名称到实际视图的映射。View处理请求的准备工作, 并将该请求提交给某种具体的视图技术。

13.5.1. 视图解析器

正如前面(Section 13.3, “控制器”)所讨论的,SpringWeb框架的所有控制器都返回一个ModelAndView实例。Sprnig中的视图以名字为标识,视图解析器通过名字来解析视图。Spring提供了多种视图解析器。我们将举例加以说明。

Table 13.5. 视图解析器

ViewResolver 描述
AbstractCachingViewResolver 抽象视图解析器实现了对视图的缓存。在视图被投入使用之前,通常需要进行一些准备工作。从它继承的视图解析器将对要解析的视图进行缓存。
XmlViewResolver XmlViewResolver实现ViewResolver,支持XML格式的配置文件。该配置文件必须采用与Spring XML Bean Factory相同的DTD。默认的配置文件是 /WEB-INF/views.xml
ResourceBundleViewResolver ResourceBundleViewResolver实现ViewResolver,在一个ResourceBundle中寻找所需bean的定义。这个bundle通常定义在一个位于classpath中的属性文件中。默认的属性文件是views.properties
UrlBasedViewResolver UrlBasedViewResolver实现ViewResolver,将视图名直接解析成对应的URL,不需要显式的映射定义。如果你的视图名和视图资源的名字是一致的,就可使用该解析器,而无需进行映射。
InternalResourceViewResolver 作为UrlBasedViewResolver的子类,它支持InternalResourceView(对Servlet和JSP的包装),以及其子类JstlViewTilesView。通过setViewClass方法,可以指定用于该解析器生成视图使用的视图类。更多信息请参考UrlBasedViewResolver的Javadoc。
VelocityViewResolver / FreeMarkerViewResolver 作为UrlBasedViewResolver的子类,它能支持VelocityView(对Velocity模版的包装)和FreeMarkerView以及它们的子类。

举例来说,当使用JSP作为视图层技术时,就可以使用UrlBasedViewResolver。这个视图解析器会将视图名解析成URL,并将请求传递给RequestDispatcher来显示视图。

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

当返回的视图名为test时,这个视图解析器将请求传递给RequestDispatcherRequestDispatcher再将请求传递给/WEB-INF/jsp/test.jsp

当在一个web应用中混合使用不同的视图技术时,你可以使用ResourceBundleViewResolver:

<bean id="viewResolver"
      class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver通过basename所指定的ResourceBundle解析视图名。对每个待解析的视图,ResourceBundle里的[视图名].class所对应的值就是实现该视图的类。同样,[视图名].url所对应的值是该视图所对应的URL。从上面的例子里你也可以发现,你可以指定一个parent view,其它的视图都可以从parent view扩展。用这种方法,你可以声明一个默认的视图。

关于视图缓存的注意事项 --继承AbstractCachingViewResolver的解析器可以缓存它曾经解析过的视图。当使用某些视图技术时,这可以大幅度的提升性能。你也可以关掉缓存功能,只要把cache属性设成false就可以了。而且,如果你需要在系统运行时动态地更新某些视图(比如,当一个Velocity模板被修改了),你可以调用removeFromCache(String viewName, Locale loc)方法来达到目的。

<h3 class
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics