springMVC 文档手册
Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传。处理器是你的应用中注解了@Controller和@RequestMapping的类和方法,Spring为处理器方法提供了极其多样灵活的配置。Spring 3.0以后提供了@Controller注解机制、@PathVariable注解以及一些其他的特性,你可以使用它们来进行RESTful web站点和应用的开发。
DispatcherServlet
Spring MVC框架,与其他很多web的MVC框架一样:请求驱动;所有设计都围绕着一个中央Servlet来展开,它负责把所有请求分发到控制器;同时提供其他web应用开发所需要的功能。不过Spring的中央处理器,DispatcherServlet,能做的比这更多。它与Spring IoC容器做到了无缝集成,这意味着,Spring提供的任何特性,在Spring MVC中你都可以使用。
下图展示了Spring Web MVC的DispatcherServlet处理请求的工作流。熟悉设计模式的朋友会发现,DispatcherServlet应用的其实就是一个“前端控制器”的设计模式(其他很多优秀的web框架也都使用了这个设计模式)。
DispatcherServlet其实就是个Servlet(它继承自HttpServlet基类),同样也需要在你web应用的web.xml配置文件下声明。你需要在web.xml文件中把你希望DispatcherServlet处理的请求映射到对应的URL上去。这就是标准的Java EE Servlet配置;下面的代码就展示了对DispatcherServlet和路径映射的声明:
1 | <web-app> |
在上面的例子中,所有路径以/example开头的请求都会被名字为example的DispatcherServlet处理。在Servlet 3.0+的环境下,你还可以用编程的方式配置Servlet容器。下面是一段这种基于代码配置的例子,它与上面定义的web.xml配置文件是等效的。
1 | public class MyWebApplicationInitializer implements WebApplicationInitializer { |
WebApplicationInitializer是Spring MVC提供的一个接口,它会查找你所有基于代码的配置,并应用它们来初始化Servlet 3版本以上的web容器。它有一个抽象的实现AbstractDispatcherServletInitializer,用以简化DispatcherServlet的注册工作:你只需要指定其servlet映射(mapping)即可。若想了解更多细节,可以参考基于代码的Servlet容器初始化一节。
上面只是配置Spring Web MVC的第一步,接下来你需要配置其他的一些bean(除了DispatcherServlet以外的其他bean),它们也会被Spring Web MVC框架使用到。
在6.15 应用上下文ApplicationContext的其他作用)一节中我们聊到,Spring中的ApplicationContext实例是可以有范围(scope)的。在Spring MVC中,每个DispatcherServlet都持有一个自己的上下文对象WebApplicationContext,它又继承了根(root)WebApplicationContext对象中已经定义的所有bean。这些继承的bean可以在具体的Servlet实例中被重载,在每个Servlet实例中你也可以定义其scope下的新bean。
DispatcherServlet的初始化过程中,Spring MVC会在你web应用的WEB-INF目录下查找一个名为[servlet-name]-servlet.xml的配置文件,并创建其中所定义的bean。如果在全局上下文中存在相同名字的bean,则它们将被新定义的同名bean覆盖。
看看下面这个DispatcherServlet的Servlet配置(定义于web.xml文件中):
1 | <web-app> |
有了以上的Servlet配置文件,你还需要在应用中的/WEB-INF/路径下创建一个golfing-servlet.xml文件,在该文件中定义所有Spring MVC相关的组件(比如bean等)。你可以通过servlet初始化参数为这个配置文件指定其他的路径(更多细节请参考下文)。
Spring的DispatcherServlet使用了特殊的bean来处理请求、渲染视图等,这些特定的bean是Spring MVC框架的一部分。如果你想指定使用哪个特定的bean,你可以在web应用上下文WebApplicationContext中简单地配置它们。当然这只是可选的,Spring MVC维护了一个默认的bean列表,如果你没有进行特别的配置,框架将会使用默认的bean。下一小节会介绍更多的细节,这里,我们将先快速地看一下,DispatcherServlet都依赖于哪些特殊的bean来进行它的初始化。
| bean的类型 | 作用 |
|---|---|
HandlerMapping |
处理器映射。它会根据某些规则将进入容器的请求映射到具体的处理器以及一系列前处理器和后处理器(即处理器拦截器)上。具体的规则视HandlerMapping类的实现不同而有所不同。其最常用的一个实现支持你在控制器上添加注解,配置请求路径。当然,也存在其他的实现。 |
HandlerAdapter |
处理器适配器。拿到请求所对应的处理器后,适配器将负责去调用该处理器,这使得DispatcherServlet无需关心具体的调用细节。比方说,要调用的是一个基于注解配置的控制器,那么调用前还需要从许多注解中解析出一些相应的信息。因此,HandlerAdapter的主要任务就是对DispatcherServlet屏蔽这些具体的细节。 |
HandlerExceptionResolver |
处理器异常解析器。它负责将捕获的异常映射到不同的视图上去,此外还支持更复杂的异常处理代码。 |
ViewResolver |
视图解析器。它负责将一个代表逻辑视图名的字符串(String)映射到实际的视图类型View上。 |
LocaleResolver & LocaleContextResolver |
地区解析器 和 地区上下文解析器。它们负责解析客户端所在的地区信息甚至时区信息,为国际化的视图定制提供了支持。 |
ThemeResolver |
主题解析器。它负责解析你web应用中可用的主题,比如,提供一些个性化定制的布局等。 |
MultipartResolver |
解析multi-part的传输请求,比如支持通过HTML表单进行的文件上传等。 |
FlashMapManager |
FlashMap管理器。它能够存储并取回两次请求之间的FlashMap对象。后者可用于在请求之间传递数据,通常是在请求重定向的情境下使用。 |
DispatcherServlet维护了一个列表,其中保存了其所依赖的所有bean的默认实现。这个列表保存在包org.springframework.web.servlet下的DispatcherServlet.properties文件中。
DispatcherServlet的处理流程
配置好DispatcherServlet以后,开始有请求会经过这个DispatcherServlet。
此时,DispatcherServlet会依照以下的次序对请求进行处理:
- 首先,搜索应用的上下文对象WebApplicationContext并把它作为一个属性(attribute)绑定到该请求上,以便控制器和其他组件能够使用它。属性的键名默认为DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
- 将地区(locale)解析器绑定到请求上,以便其他组件在处理请求(渲染视图、准备数据等)时可以获取区域相关的信息。如果你的应用不需要解析区域相关的信息,忽略它即可
- 将主题(theme)解析器绑定到请求上,以便其他组件(比如视图等)能够了解要渲染哪个主题文件。同样,如果你不需要使用主题相关的特性,忽略它即可
- 如果你配置了multipart文件处理器,那么框架将查找该文件是不是multipart(分为多个部分连续上传)的。若是,则将该请求包装成一个MultipartHttpServletRequest对象,以便处理链中的其他组件对它做进一步的处理。关于Spring对multipart文件传输处理的支持,读者可以参考21.10 Spring的multipart(文件上传)支持一小节
- 为该请求查找一个合适的处理器。如果可以找到对应的处理器,则与该处理器关联的整条执行链(前处理器、后处理器、控制器等)都会被执行,以完成相应模型的准备或视图的渲染
- 如果处理器返回的是一个模型(model),那么框架将渲染相应的视图。若没有返回任何模型(可能是因为前后的处理器出于某些原因拦截了请求等,比如,安全问题),则框架不会渲染任何视图,此时认为对请求的处理可能已经由处理链完成了
如果在处理请求的过程中抛出了异常,那么上下文WebApplicationContext对象中所定义的异常处理器将会负责捕获这些异常。通过配置你自己的异常处理器,你可以定制自己处理异常的方式。
Spring的DispatcherServlet也允许处理器返回一个Servlet API规范中定义的 最后修改时间戳(last-modification-date) 值。决定请求最后修改时间的方式很直接:DispatcherServlet会先查找合适的处理器映射来找到请求对应的处理器,然后检测它是否实现了 LastModified 接口。若是,则调用接口的long getLastModified(request)方法,并将该返回值返回给客户端。
你可以定制DispatcherServlet的配置,具体的做法,是在web.xml文件中,Servlet的声明元素上添加一些Servlet的初始化参数(通过init-param元素)。该元素可选的参数列表如下:
| 可选参数 | 解释 |
|---|---|
contextClass |
任意实现了WebApplicationContext接口的类。这个类会初始化该servlet所需要用到的上下文对象。默认情况下,框架会使用一个XmlWebApplicationContext对象。 |
contextConfigLocation |
一个指定了上下文配置文件路径的字符串,该值会被传入给contextClass所指定的上下文实例对象。该字符串内可以包含多个字符串,字符串之间以逗号分隔,以此支持你进行多个上下文的配置。在多个上下文中重复定义的bean,以最后加载的bean定义为准 |
namespace |
WebApplicationContext的命名空间。默认是[servlet-name]-servlet |
Controller( 控制器)
Resolver (viewResolver 视图解析)
Spring MVC中所有控制器的处理器方法都必须返回一个逻辑视图的名字,无论是显式返回(比如返回一个String、View或者ModelAndView)还是隐式返回(比如基于约定的返回)。Spring中的视图由一个视图名标识,并由视图解析器来渲染。Spring有非常多内置的视图解析器。下表列出了大部分,表后也给出了一些例子。
视图解析器
| 视图解析器 | 描述 |
|---|---|
AbstractCachingViewResolver |
一个抽象的视图解析器类,提供了缓存视图的功能。通常视图在能够被使用之前需要经过准备。继承这个基类的视图解析器即可以获得缓存视图的能力。 |
XmlViewResolver |
视图解析器接口ViewResolver的一个实现,该类接受一个XML格式的配置文件。该XML文件必须与Spring XML的bean工厂有相同的DTD。默认的配置文件名是/WEB-INF/views.xml。 |
ResourceBundleViewResolver |
视图解析器接口ViewResolver的一个实现,采用bundle根路径所指定的ResourceBundle中的bean定义作为配置。一般bundle都定义在classpath路径下的一个配置文件中。默认的配置文件名为views.properties。 |
UrlBasedViewResolver |
ViewResolver接口的一个简单实现。它直接使用URL来解析到逻辑视图名,除此之外不需要其他任何显式的映射声明。如果你的逻辑视图名与你真正的视图资源名是直接对应的,那么这种直接解析的方式就很方便,不需要你再指定额外的映射。 |
InternalResourceViewResolver |
UrlBasedViewResolver的一个好用的子类。它支持内部资源视图(具体来说,Servlet和JSP)、以及诸如JstlView和TilesView等类的子类。You can specify the view class for all views generated by this resolver by using setViewClass(..)。更多的细节,请见UrlBasedViewResolver类的java文档。 |
VelocityViewResolver / FreeMarkerViewResolver |
UrlBasedViewResolver下的实用子类,支持Velocity视图VelocityView(Velocity模板)和FreeMarker视图FreeMarkerView以及它们对应子类。 |
ContentNegotiatingViewResolver |
视图解析器接口ViewResolver的一个实现,它会根据所请求的文件名或请求的Accept头来解析一个视图。更多细节请见21.5.4 内容协商视图解析器”ContentNegotiatingViewResolver”一小节。 |
我们可以举个例子,假设这里使用的是JSP视图技术,那么我们可以使用一个基于URL的视图解析器UrlBasedViewResolver。这个视图解析器会将URL解析成一个视图名,并将请求转交给请求分发器来进行视图渲染。
1 | <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> |
Spring支持同时使用多个视图解析器。因此,你可以配置一个解析器链,并做更多的事比如,在特定条件下覆写一个视图等。你可以通过把多个视图解析器设置到应用上下文(application context)中的方式来串联它们。如果需要指定它们的次序,那么设置order属性即可。请记住,order属性的值越大,该视图解析器在链中的位置就越靠后。
在下面的代码例子中,视图解析器链中包含了两个解析器:一个是InternalResourceViewResolver,它总是自动被放置在解析器链的最后;另一个是XmlViewResolver,它用来指定Excel视图。InternalResourceViewResolver不支持Excel视图。
1 | <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> |
如果一个视图解析器不能返回一个视图,那么Spring会继续检查上下文中其他的视图解析器。此时如果存在其他的解析器,Spring会继续调用它们,直到产生一个视图返回为止。如果最后所有视图解析器都不能返回一个视图,Spring就抛出一个ServletException。
重定向前缀——redirect:
重定向前缀——redirect:
异常处理
Spring的处理器异常解析器HandlerExceptionResolver接口的实现负责处理各类控制器执行过程中出现的异常。某种程度上讲,HandlerExceptionResolver与你在web应用描述符web.xml文件中能定义的异常映射(exception mapping)很相像,不过它比后者提供了更灵活的方式。比如它能提供异常被抛出时正在执行的是哪个处理器这样的信息。并且,一个更灵活(programmatic)的异常处理方式可以为你提供更多选择,使你在请求被直接转向到另一个URL之前(与你使用Servlet规范的异常映射是一样的)有更多的方式来处理异常。
实现HandlerExceptionResolver接口并非实现异常处理的唯一方式,它只是提供了resolveException(Exception, Hanlder)方法的一个实现而已,方法会返回一个ModelAndView。除此之外,你还可以框架提供的SimpleMappingExceptionResolver或在异常处理方法上注解@ExceptionHandler。SimpleMappingExceptionResolver允许你获取可能抛出的异常类的名字,并把它映射到一个视图名上去。这与Servlet API提供的异常映射特性是功能等价的,但你也可以基于此实现粒度更精细的异常映射。而@ExceptionHandler注解的方法则会在异常抛出时被调用以处理该异常。这样的方法可以定义在@Controller注解的控制器类里,也可以定义在@ControllerAdvice类中,后者可以使该异常处理方法被应用到更多的@Controller控制器中。
常见的配置项
启用springMvc
要启用MVC Java编程配置,你需要在其中一个注解了@Configuration的类上添加@EnableWebMvc注解:
1 |
|
要启用XML命名空间,请在你的DispatcherServlet上下文中(如果没有定义任何DispatcherServlet上下文,那么就在根上下文中)添加一个mvc:annotation-driven元素:
1 |
|
上面的简单的声明代码,就已经默认注册了一个RequestMappingHandlerMapping、一个RequestMappingHandlerAdapter,以及一个ExceptionHandlerExceptionResolver,以支持对使用了@RequestMapping、@ExceptionHandler及其他注解的控制器方法的请求处理。
同时,上面的代码还启用了以下的特性:
1、 Spring3风格的类型转换支持这是使用一个配置的转换服务ConversionService实例,以及theJavaBeansPropertyEditorsusedforDataBinding.;
2、 使用@NumberFormat对数字字段进行格式化,类型转换由ConversionService实现;
3、 使用@DateTimeFormat注解对Date、Calendar、Long及JodaTime类型的字段进行格式化;
4、 使用@Valid注解对@Controller输入进行验证——前提是classpath路径下比如提供符合JSR-303规范的验证器;
5、 HTTP消息转换HttpMessageConverter的支持,对注解了@RequestMapping或@ExceptionHandler方法的@RequestBody方法参数或@ResponseBody返回值生效;
下面给出了一份由mvc:annotation-driven注册可用的HTTP消息转换器的完整列表:
1、 转换字节数组的ByteArrayHttpMessageConverter;
2、 转换字符串的StringHttpMessageConverter;
3、 ResourceHttpMessageConverter:org.springframework.core.io.Resource与所有媒体类型之间的互相转换;
4、 SourceHttpMessageConverter:从(到)javax.xml.transform.Source的转换;
5、 FormHttpMessageConverter:数据与MultiValueMap<String,String>之间的互相转换;
6、 Jaxb2RootElementHttpMessageConverter:Java对象与XML之间的互相转换——该转换器在classpath路径下有JAXB2依赖并且没有Jackson2XML扩展时被注册;
7、 MappingJackson2HttpMessageConverter:从(到)JSON的转换——该转换器在classpath下有Jackson2依赖时被注册;
8、 MappingJackson2XmlHttpMessageConverter:从(到)XML的转换——该转换器在classpath下有[Jackson2XML扩展][Jackson2XML]时被注册;
9、 AtomFeedHttpMessageConverter:Atom源的转换——该转换器在classpath路径下有Rome时被注册;
10、 RssChannelHttpMessageConverter:RSS源的转换——该转换器在classpath路径下有Rome时被注册;
你可以参考21.16.12 消息转换器一小节,了解如何进一步定制这些默认的转换器。
Jackson JSON和XML转换器是通过Jackson2ObjectMapperBuilder创建的ObjectMapper实例创建的,目的在于提供更好的默认配置
该builder会使用以下的默认属性对Jackson进行配置:
同时,如果检测到在classpath路径下存在这些模块,该builder也会自动地注册它们:
- jackson-datatype-jdk7: 支持Java 7的一些类型,例如java.nio.file.Path
- jackson-datatype-joda: 支持Joda-Time类型
- jackson-datatype-jsr310: 支持Java 8的Date & Time API类型
- jackson-datatype-jdk8: 支持Java 8其他的一些类型,比如Optional等
默认配置的覆盖
在MVC Java编程配置方式下,如果你想对默认配置进行定制,你可以自己实现WebMvcConfigurer接口,要么继承WebMvcConfigurerAdapter类并覆写你需要定制的方法:
java 8 后 WebMvcConfigurer 接口已经有默认实现的支持,WebMvcConfigurerAdapter已被弃用.
1 |
|
在MVC XML命名空间下,如果你想对默认配置进行定制,请查看<mvc:annotation-driven/>元素支持的属性和子元素。你可以查看Spring MVC XML schema,或使用IDE的自动补全功能来查看有哪些属性和子元素是可以配置的。
转换与格式化
数字的Number类型和日期Date类型的格式化是默认安装了的,包括@NumberFormat注解和@DateTimeFormat注解。如果classpath路径下存在Joda Time依赖,那么完美支持Joda Time的时间格式化库也会被安装好。如果要注册定制的格式化器或转换器,请覆写addFormatters方法:
1 |
|
使用MVC命名空间时,<mvc:annotation-driven>也会进行同样的默认配置。要注册定制的格式化器和转换器,只需要提供一个转换服务ConversionService:
1 |
|
关于如何使用格式化管理器FormatterRegistrar,请参考 8.6.4 FormatterRegistrar SPI一节,以及FormattingConversionServiceFactoryBean的文档
拦截器
你可以配置处理器拦截器HandlerInterceptors或web请求拦截器WebRequestInterceptors等拦截器,并配置它们拦截所有进入容器的请求,或限定到符合特定模式的URL路径。
在MVC Java编程配置下注册拦截器的方法:
1 |
|
在MVC XML命名空间下,则使用<mvc:interceptors>元素:
1 | <mvc:interceptors> |
内容协商
我们进行web开发时,现在一般都是设计成RESTful风格的url。如果此时我们希望在请求同一个RESTful的URL时,得到不同的PDF视图、JSON视图、Html视图,也就是说我们需要对同一个url返回多种不同的结果,这该如何实现?
要想实现上面的需求,这就可以用到我今天给大家讲解的内容协商ContentNegotiation机制了!
一个URL资源服务端可以以多种形式进行响应:即MIME(MediaType)媒体类型。但对于某一个客户端(浏览器、APP、Excel导出…)来说它只需要一种。此时在客户端和服务端之间就得有一种机制来沟通这个事情,这就是我们要说的内容协商机制。
内容协商机制是指客户端和服务器端就响应的资源内容进行协商交涉,然后提供给客户端最为合适的资源。内容协商是以响应资源的语言、字符集、编码方式等作为判断的基准。
http内容协商的方式大致有两种:
- ①.服务端将可用的MIME类型列表发给客户端,客户端选择某个MIME类型后再告诉服务端。这样服务端就按照客户端告诉它的MIME类型来返回给它具体的内容。(缺点:多一次网络交互,而且使用对使用者要求高,所以此方式一般不用)
- ②.客户端发请求时就指明需要的MIME类型(比如Http头部的Accept),服务端根据客户端指定的要求返回合适的内容类型,并且在响应头中做出说明(如Content-Type)。如果客户端要求的MIME类型数据服务端提供不了,那就会产生406异常。
pring MVC在实现了HTTP内容协商的同时,又进行了扩展,它支持4种协商方式:
1 | HTTP的Accept头; |
配置文件
1 | <!--1、检查扩展名(如my.pdf);2、检查Parameter(如my?format=pdf);3、检查Accept Header--> |











