Spring提供了一个相当灵活和可扩展的MVC实现——Spring MVC。Spring MVC框架主要由DispatcherServlet、处理器映射(handler)、处理器(controller)、视图解析器(ViewResolver)、视图(view)组成。

Spring MVC的处理过程从一个HTTP请求开始:

  1)DispatcherServlet接收到请求后,根据对应配置文件中配置的处理器映射,找到对应的处理器映射(HandlerMapping),根据配置的映射规则,找到对应的处理器(Handler)。

  2)调用相应处理器中的处理方法,处理该请求,处理器处理结束后会将一个ModelAndView类型的数据传给DispatcherServlet,这其中包含了处理结果的视图和视图中要使用的数据。

  3)DispatcherServlet根据得到的ModelAndView中的视图对象,找到一个合适的ViewResolver(视图解析器),根据视图解析器的配置,DispatcherServlet将视图要显示的数据传给对应的视图,最后给浏览器构造一个HTTP响应。

DispatcherServlet是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。其主要工作有以下三项:

   1)截获符合特定格式的URL请求。

   2)初始化DispatcherServlet上下文对应的WebApplicationContext,并将其与业务层、持久化层的WebApplicationContext建立关联。

   3)初始化Spring MVC的各个组成组件,并装配到DispatcherServlet中。

我们从一个多视图、多控制器的配置入手来理解Spring MVC.

使用多视图、多控制器时,我们常采用的处理器映射是SimpleUrlHandlerMapping,并采用路径匹配算法将web请求映射到正确的处理器(handler)上。视图解析器根据使用的模板、JSP选择合适的解析器,当多个视图解析器一起使用,Spring可以组成一个视图解析链顺序查找,直到找到对应的 “视图解析器”。

1)首先在web.xml中配置多个Dispatcher,不同的视图转发不同,如下:

<servlet-name>sample</servlet-name>
    <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class>
</servlet>
<servlet-mapping>    <servlet-name>sample</servlet-name>
    <url-pattern>/**/*.jstl</url-pattern>
</servlet-mapping>
<servlet-mapping>    <servlet-name>sample</servlet-name>
    <url-pattern>/**/*.form</url-pattern>
</servlet-mapping>

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

2)配置处理器映射

<beans>
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>
                /jstl/*.form=myFormController
                /ex/*.jstl=myJstlController
            </value>
        </property>
    </bean>
    <bean id="myJstlController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController">
        <property name="validator"> <!-- 增加校验器 -->
          <bean class="sample.myJstlValidator" />
        </property>    </bean>
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 
        <!-- property name="prefix"><value>/WEB-INF/sample/jsp/</value></property -->  
        <property name="suffix"><value>.jstl</value></property>  
        <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>  
   <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">  
      <property name="suffix"><value>.form</value></property>  
      <property name="viewClass">  
            <value>org.springframework.web.servlet.view.freemarker.FreeMarkerView</value>  
      </property>  
      <property name="contentType"><value>text/html; charset=utf-8</value></property>  
</bean>     
</beans>  如果使用视图链,那么freemarker和jsp/jstl多视图配置文件如下:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views" />
    <property name="order" value="0" />
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/sample/jsp/" />
    <property name="suffix" value=".jstl" />
</bean>
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath">
        <value>/WEB-INF/sample/freemarker/</value>
    </property>
</bean>

上面配置中,ResourceBundleViewResolver设置了order属性,用于定义顺序,注意,一般将jsp处理放在视图处理链的未尾。

ResourceBundleViewResolver还需要有它的配置文件(这里配置的是views.properties文件),并把它放置在classpath的下。views.properties文件内容如下:

sample.(class)=org.springframework.web.servlet.view.freemarker.FreeMarkerView
    sample.url=sample.ftl

文件内容中的sample是spring mvc返回字符串的名称,url对应freemarkerConfig中设置的templateLoaderPath下的文件路径。

 对多视图、多控制器一个更好的选择是采用注解方式配置。注解方式配置中有两个主要的类:

  DefaultAnnotationHandlerMapping,支持通过直接扫描Controller类中的Annotation来确定请求映射关系。

  AnnotationMethodHandlerAdapter,支持对某个 Controller 注册属性编辑器,包括基本数据类型及其包裹类的属性编辑器、String 属性编辑器、JavaBean 的属性编辑器等,以及注册一些自定义的属性编辑器,如时间格式编辑器等,该类在实际调用handlermethod前使用属性编辑器对其参数进行处理。 

  注解方式还有一种简写方式:<mvc:annotation-driven />。使用<mvc:annotation-driven />,Spring会自动注册DefaultAnnotationHandlerMapping 与AnnotationMethodHandlerAdapter 这两个bean。

注:在spring mvc 3.1中,这两个Bean对应变如下: 

DefaultAnnotationHandlerMapping -> RequestMappingHandlerMapping 
    AnnotationMethodHandlerAdapter -> RequestMappingHandlerAdapter 
    AnnotationMethodHandlerExceptionResolver -> ExceptionHandlerExceptionResolver

具体配置如下:

    <!-- 对web包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 -->

   

<context:component-scan base-package="sample.contoller"/>
    <!--Spring3.1开始的注解 HandlerMapping -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
      <property name="interceptors">        
        <list>        
          <bean class="sample.MyInteceptor"></bean>  <!-- 进行安全校验 -->
        </list>        
      </property>
    </bean>
    <!--Spring3.1开始的注解 HandlerAdapter -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
      <property name="webBindingInitializer">
        <bean class="sample.MyBindingInitializer"/><!-- 对参数进行格式化处理 -->
      </property>
    </bean>

或者使用简写方式:

<!-- 拦截器 -->  
<mvc:interceptors>  
  <bean class="sample.MyInteceptor" />  <!-- 进行安全校验 -->
</mvc:interceptors>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">  
  <property name="converters">  
    <list>  
      <bean class="sample.MyDataConverter" /><!-- 对参数进行格式化处理 -->  
    </list>  
  </property>  
</bean>
<mvc:annotation-driven conversion-service="conversionService" />

 

配置完成以上的文件后,我们来看看Controller的书写,Spring提供了一些关于合法的请求处理方法(见参考手册):

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

         public ModelAndView displayCatalog(HttpServletRequest, HttpServletResponse)

2)  下面这个方法接收Login参数,该参数中包含从请求中抽取出来的信息。

         public ModelAndView login(HttpServletRequest, HttpServletResponse, Login)

3)   下面这个方法要求请求中已经存在合法的session对象。

public ModelAndView viewCart(HttpServletRequest, HttpServletResponse, HttpSession)

4)   下面这个方法接受一个Product参数,这个参数包含从请求中抽取出来的信息,并且要求请求中已经存在一个 合法的session对象。注意参数的顺序很重要:session必须是第三个参数,而绑定参数必须是final的,并位于session之后。

         public ModelAndView updateCart(HttpServletRequest, HttpServletResponse, HttpSession, Product)

5)  下面这个方法声明返回void类型,这说明它会直接写response。

            public void home(HttpServletRequest, HttpServletResponse)

6)  下面这个方法返回Map,表明视图解析器应该从请求中抽取视图名,而返回数据将被放入model。

          public Map list(HttpServletRequest, HttpServletResponse)

7)可以声明自己的方法来处理请求处理过程中产生的Exceptions。该方法的签名与请求处理方法的签名类似:第一个参数必须是 HttpServletRequest,第二个参数必须是HttpServletResponse。不过与请求处理 方法不同的是,该方法的名字可以任意,具体匹配策略由该方法的第三个参数(参数类型必须是一种Exception)决定。Spring根据最接近的 异常类型进行匹配。下面是一个这种异常处理方法签名的例子:

         public ModelAndView processException(HttpServletRequest, HttpServletResponse, IllegalArgumentException)

Spring引入Annotation来完成请求-响应的映射关系,可以让一个Controller来处理多个请求。并且SpringMVC在响应方法上,可以支持多种多样不同的参数类型和返回值类型。例如,当参数类型为Model时,SpringMVC将会自动将请求参数封装于Model内部而传入请求方法;当返回值类型是String时,直接表示SpringMVC需要返回的视图类型和视图内容。

请求处理方法为标注了 @RequestMapping 注解的 Controller 方法,Spring MVC 允许极其灵活的请求处理方法签名方式。对于方法入参来说,它允许多种类型的入参,通过下表进行说明(参考:使用 Spring 2.5 基于注解驱动的 Spring MVC http://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/):

请求处理方法入参的可选类型

说明

Java 基本数据类型和 String

默认情况下将按名称匹配的方式绑定到 URL 参数上,可以通过 @RequestParam 注解改变默认的绑定规则

request/response/session

既可以是 Servlet API 的也可以是 Portlet API 对应的对象,Spring 会将它们绑定到 Servlet 和 Portlet 容器的相应对象上

org.springframework.web.context.request.WebRequest

内部包含了 request 对象

java.util.Locale

绑定到 request 对应的 Locale 对象上

java.io.InputStream/java.io.Reader

可以借此访问 request 的内容

java.io.OutputStream / java.io.Writer

可以借此操作 response 的内容

任何标注了 @RequestParam 注解的入参

被标注 @RequestParam 注解的入参将绑定到特定的 request 参数上

java.util.Map / org.springframework.ui.ModelMap

它绑定 Spring MVC 框架中每个请求所创建的潜在的模型对象,它们可以被 Web 视图对象访问(如 JSP)

命令/表单对象(注:一般称绑定使用 HTTP GET 发送的 URL 参数的对象为命令对象,而称绑定使用 HTTP POST 发送的 URL 参数的对象为表单对象)

它们的属性将以名称匹配的规则绑定到 URL 参数上,同时完成类型的转换。而类型转换的规则可以通过 @InitBinder 注解或通过 HandlerAdapter 的配置进行调整

org.springframework.validation.Errors / org.springframework.validation.BindingResult

为属性列表中的命令/表单对象的校验结果,注意检验结果参数必须紧跟在命令/表单对象的后面

org.springframework.web.bind.support.SessionStatus

可以通过该类型 status 对象显式结束表单的处理,这相当于触发 session 清除其中的通过 @SessionAttributes 定义的属性

 方法返回参数,通过下表进行说明:(参考:使用 Spring 2.5 基于注解驱动的 Spring MVC http://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/)

请求处理方法入参的可选类型

说明

void

此时逻辑视图名由请求处理方法对应的 URL 确定,如以下的方法:


@RequestMapping("/welcome.do")


public void welcomeHandler() {


}

对应的逻辑视图名为“welcome”

String

此时逻辑视图名为返回的字符,如以下的方法:


@RequestMapping(method = RequestMethod.GET)


public String setupForm(@RequestParam("ownerId") int ownerId, ModelMap model) {


  Owner owner = this.clinic.loadOwner(ownerId);


  model.addAttribute(owner);


  return "ownerForm";


}

对应的逻辑视图名为“ownerForm”

org.springframework.ui.ModelMap

和返回类型为 void 一样,逻辑视图名取决于对应请求的 URL,如下面的例子:


@RequestMapping("/vets.do")


public ModelMap vetsHandler() {


  return new ModelMap(this.clinic.getVets());


}

对应的逻辑视图名为“vets”,返回的 ModelMap 将被作为请求对应的模型对象,可以在 JSP 视图页面中访问到。

ModelAndView

当然还可以是传统的 ModelAndView

 我们常用的书写方式:

@RequestMapping({"/sample/enableshop/{id}"})
public String enableShop(HttpServletRequest request, HttpServletResponse response, @PathVariable("id")String shopId) ; //返回url地址 
@RequestMapping({"/sample/addproduct"})
public String addProduct(HttpServletRequest request, HttpServletResponse response, Product product) ;  //返回url地址  
 @RequestMapping(value = {"/sample/savestorestatus"}, method = {org.springframework.web.bind.annotation.RequestMethod.GET})
public @ResponseBody String saveShopStatus(ModelMap modelMap, HttpServletRequest request, HttpServletResponse response);   
@RequestMapping(value = {"/sample/getcurrentorders"}, method = {
            org.springframework.web.bind.annotation.RequestMethod.GET})
public void getCurrentOrders(HttpServletRequest request, HttpServletResponse response);//可以使用Ajax方式来调用,数据采用JSON返回; @RequestMapping(value = {"/sample/saveshopparams"}, method = {
            org.springframework.web.bind.annotation.RequestMethod.POST})
 public void saveShopParams(HttpServletRequest request, HttpServletResponse response) ; //可用使用Ajax方式来调用

访问静态资源

 

我们可以用Web服务器的defaultServlet来处理静态文件,也可用Spring框架来处理静态文件。使用Spring来处理,可以在配置中加入以下代码:

           <mvc:default-servlet-handler/>  

这样spring会用默认的Servlet来响应静态文件,(DefaultServletHttpRequestHandler在容器启动是会使用主流web容器默认servlet的名称列表自动查找容器的默认servlet,包括Tomcat, Jetty, Glassfish, JBoss, Resin, WebLogic, and WebSphere。),如果为默认servlet配置了新的名称,或者这个容器servlet名字不在spring列表中是,必须显式配置默认servlet的名字,如下:

<mvc:default-servlet-handler default-servlet-name="customServlet"/>

或者使用mvc:resources方式来处理,如下:

      

<mvc:resources mapping="/images/**" location="/images/" />

 

使用<mvc:resources/>元素把images/**映射到ResourceHttpRequestHandler进行处理,location指定静态资源的位置.可以是web application根目录下、jar包里面,这样可以把静态资源压缩到jar包中。cache-period 可以使得静态资源进行web cache 

 

文件上传

Spring是通过MultipartResolver来处理文件上传,它支持 Commons FileUpload 和 COS FileUpload。 想要使用Spring的multipart支持文件上传,首先需要在web应用的上下文中添加multipart解析器。这样,对每个请求就会检查是否包含multipart,当请求中包含multipart,上下文中定义的MultipartResolver就会解析它。

<!-- 添加上传拦截 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="1048576"/>
        <property name="defaultEncoding" value="utf-8"/>
    </bean>在controller中增加处理逻辑,如下
@RequestMapping(value = "/uploadfile", method = RequestMethod.POST)   
public String handleFormUpload(@RequestParam("file") MultipartFile file) {   
   if (!file.isEmpty()) {   
   byte[] bytes = file.getBytes();   
   // ….
}

方法验证

 如前面在controller中增加校验类sample.MyJstlValidator,该类实现了Validate接口。如果使用注解方式,则首先实现一个Validator接口的类,如下:

@Component("myJstlValidator")
public class MyJstlValidator implements Validator {
    @SuppressWarnings("unchecked")
    @Override
    public boolean supports(Class clazz) {
        return …;
    }
    @Override
    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "name","field.required");
    }
}
在Controller中通过注解获取:
     @Resource(name = " myJstlValidator ")
     private Validator validator;
        ...
    @RequestMapping("/jstl/save.form")
    public ModelAndView save(Product product, BindingResult result) {
        this.validator.validate(product, result);
 
        if (result.hasErrors()) {
            return new ModelAndView("input");
        }
         ….
     }

 

国际化

首先配置资源文件绑定

<

!-- 资源文件绑定器 -->
<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource">
 <property name="basename" value="i18n/Messages" />
 <property name="useCodeAsDefaultMessage" value="true" />
</bean>

配置好这个bean后,在JSP中设置JSTL支持的渲染器,那么在JSP文件中使用fmt标记就可以实现客户浏览器语言国际化了。如:

 <fmt:message key="sample.title" />

其中的sample.title和你的资源文件对应.

另外一种方式是使用spring自带的标签显示国际化信息,如:

 <spring:message code="sample.title" />

实际操作中,针对不同语言要求进行切换的方式大多有以下几种:
1. 根据语言种类,部署时手动替换*.properties文件
2. 根据当前系统Locale设置,自动匹配(如上例)
3. 根据客户浏览器语言设定,自动切换界面语种。
前两种部署方式依赖于部署时的设定和系统环境。第三种最为灵活,系统一旦部署后,仍可自动根据客户浏览器的语言设定来自动切换语言种类。Spring中提供了以下三种语言自动切换机制的实现(均实现了LocaleResolver接口):

1) AcceptHeaderLocaleResolver:根据浏览器Http Header中的accept-language域判定。

2)SessionLocaleResolver:根据用户本次会话过程中的语言设定决定语言种类(一般用户在登录时选择语言种类,此后本次登录周期内统一使用该语言设定)。

3) CookieLocaleResolver:根据Cookie判定用户的语言设定(Cookie中保存着用户前一次的语言设定参数)。

如果改变locale设置时,需要增加LocaleChangeInterceptor拦截器,这个时候,但凡有了符合UrlMapping的请求,就会被拦截,并且开始配置国际化参数。