通过处理器映射,可以将web请求映射到正确的处理器(handler)上。
Spring内置了很多处理器映射策略,例如:SimpleUrlHandlerMapping
或者BeanNameUrlHandlerMapping
。
现在我们先来看一下HandlerMapping
的基本概念。
HandlerMapping
的基本功能是将请求传递到HandlerExecutionChain
上。
首先,这个HandlerExecutionChain
必须包含一个能处理该请求的处理器。
其次,这个链也可以包含一系列可以拦截请求的拦截器。
当收到请求时,DispatcherServlet
将请求交给处理器映射,让它检查请求并找到一个适当的HandlerExecutionChain
。
然后,DispatcherServlet
执行定义在链中的处理器和拦截器(interceptor)。
在处理器映射中通过配置拦截器(包括处理器执行前、执行后、或者执行前后运行拦截器)将使其功能更强大。
同时也可以通过自定义HandlerMapping
来支持更多的功能。
比如,一个自定义的处理器映射不仅可以根据请求的URL,而且还可以根据和请求相关的特定session状态来选择处理器。
下面我们将讲述Spring中最常用的两个处理器映射。
它们都是AbstractHandlerMapping
的子类,同时继承了下面这些属性:
interceptors
: 在映射中使用的拦截器列表。HandlerInterceptor
将在第 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
。urlDecode
:这个属性的默认值是true
,和2.5版本一样。 如果想比较编码后的路径,可以把这个属性设为false
。 不过,需要注意的是,HttpServletRequest
总是返回解码后的servlet路径, 与编码后的格式进行比较时可能不会匹配。lazyInitHandlers
:这个属性允许设置是否延迟singleton处理器的初始化工作(prototype处理器的初始化都是延迟的)。 这个属性的默认值是false
。
(注意:最后三个属性只有org.springframework.web.servlet.handler.AbstractUrlHandlerMapping
的子类才有。)
BeanNameUrlHandlerMapping
是一个简单但很强大的处理器映射,它将收到的HTTP请求映射到bean的名称(这些bean需要在web应用上下文中定义)。
例如,为了实现一个用户新建账号的功能,我们提供了FormController
(关于CommandController和FormController请参考第 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>
注意
要使用BeanNameUrlHandlerMapping
,无须(如上所示)在web应用上下文中定义它。
缺省情况下,如果在上下文中没有找到处理器映射,DispatcherServlet
会为你创建一个BeanNameUrlHandlerMapping
!
另一个更加强大的处理器映射是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>
上面的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
的请求传递给helpController
。
helpController
是一个UrlFilenameViewController
(要了解更多关于控制器的信息,请参阅第 13.3 节 “控制器”)。
对ex
目录中所有以view
开始,以.html
结尾的请求都会被传递给helpController
。
同样的,我们也为editAccountFormController
定义了两个映射。
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
接口。