Spring MVC 体系概述

Posted jwen1994

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC 体系概述相关的知识,希望对你有一定的参考价值。

Spring MVC 框架围绕 DispatcherServlet 这个核心展开,DispatcherServlet 是 Spring MVC 的总导演、总策划,它负责截获请求并将其分派给相应的处理器处理。Spring MVC 框架包括注解驱动控制器、请求及响应的信息处理、视图解析、本地化解析、上传文件解析、异常处理及表单标签绑定等内容。

 

1.体系结构

Spring MVC 是基于 Model 2 实现的技术框架,Model 2 是经典的MVC(Model、View、Control)模型在 Web 应用中的变体,这个改变主要源于 HTTP 协议的无状态性。Model 2 的目的和 MVC 一样,也是利用处理器分离模型、视图和控制,达到不同技术层级间松散层耦合的效果,提高系统灵活性、复用性和可维护性。在大多数情况下,可以将 Model 2 与 MVC 等同起来。

在利用 Model 2 之前,把所有的展现逻辑和业务逻辑集中在一起,有时也称这种应用模式为 Model 1。Model 1 的主要缺点就是紧耦合,复用性差,维护成本高。

由于 Spring MVC 是基于 Model 2 实现的框架,所以它底层的机制也是 MVC,通过下图描述 Spring MVC 的整体架构。

技术图片

从接收请求到返回响应,Spring MVC 框架的众多组件通力配合、各司其职,有条不紊地完成分内的工作。在整个框架中,DispatcherServlet 处于核心的位置,它负责协调和组织不同组件以完成请求处理并返回响应的工作。和大多数 Web 框架一样,Spring MVC 通过一个前端 Serviet 接收所有的请求,并将具体工作委托给其他组件进行处理,DispatcherServlet 就是 Spring MVC 的前端 Servlet。下面对 Spring MVC 处理请求的整体过程进行讲解。

(1)整个过程始于客户端发出一个 HTTP 请求,Web 应用服务器接收到这个请求。如果匹配 DispatcherServlet 的请求映射路径(在 web.xml 中指定),则 Web 容器将该请求转交给 DispatcherServlet 处理。

(2)DispatcherServlet 接收到这个请求后,将根据请求的信息(包括URL、HTTP方法、请求报文头、请求参数、Cookie等)及 HandlerMapping 的配置找到处理请求的处理器(Handler)。可将看作路由控制器,将 Handler 看作目标主机。值得注意的是,在 Spring MVC 中并没有定义一个 Handler 接口,实际上,任何一个 Object 都可以成为请求处理器。

(3)当 DispatcherServlet 根据 HandlerMapping 得到对应当前请求的 Handler 后,通过 HandlerAdapter 对 Handler 进行封装,再以统一的适配器接口调用 Handler。HandlerAdapter 是 Spring MVC 的框架级接口,顾名思义,HandlerAdapter 是一个适配器,它用统一的接口对各种 Handler 方法进行调用。

(4)处理器完成业务逻辑的处理后将返回一个 ModelAndView 给 DispatcherServlet,ModelAndView 包含了视图逻辑名和模型数据信息。

(5)ModelAndView 中包含的是“逻辑视图名”而非真正的视图对象,DispatcherServlet 借由 ViewResolver 完成逻辑视图名到真实视图对象的解析工作。

(6)当得到真实的视图对象 View 后,DispatcherServlet 就使用这个 View 对象对 ModelAndView 中的模型数据进行视图渲染。

(7)最终客户端得到的响应消息可能是一个普通的 html 页面,也可能是一个 XML 或 JSON 串,甚至是一张图片或一个 PDF 文档等不同的媒体形式。

以上每个步骤都包含丰富的知识点,本章将逐步揭示每个组件的“庐山真面目”。不过现在请收好所有的好奇心,我们第一步要做的是在 web.xml 中配置好 DispatcherServlet,让 Spring MVC 的“心脏”跳动起来。

 

2.配置DispatcherServIet

DispatcherServlet 是 Spring MVC 的“灵魂”和“心脏”,它负责接收 HTTP 请求并协调 Spring MVC 的各个组件完成请求处理的工作。和任何 Servlet 一样,用户必须在 web.xml 中配置好 DispatcherServlet。

要了解 Spring MVC 框架的工作机理,必须回答以下3个问题。

(1)DispatcherServlet 框架如何截获特定的 HTTP 请求并交由 Spring MVC 框架处理?

(2)位于 Web 层的 Spring 容器(WebApplicationContext)如何与位于业务层的 Spring 容器(ApplicationContext)建立关联,以使 Web 层的 Bean 可以调用业务层的 Bean?

(3)如何初始化 Spring MVC 的各个组件,并将它们装配到 DispatcherServlet 中?

1)配置 DispatcherServlet,截获特定的URL请求

大家知道,我们可以在 web.xml 中配置一个 Servlet,并通过 <servlet-mapprng> 指定其处理的URL。这是传统的 DispatcherServlet 配置方式。而 Spring4.0 已经全面支持 Servlet 3.0,因此也可以采用编程式的配置方式。这里先采用传统的 web.xml 的方式进行讲解,然后介绍基于 Servlet 3.0 的新方式。假设我们希望 Spring MVC 的 DispatcherServlet 能截获并处理所有以 .html 结束的 URL 请求,那么可以在 web.xml 中按如下方式进行配置,如下面代码所示。

<!-- ①业务层和持久层的Spring配置文件,这些配置文件被父Spring容器所使用 -->
<
context-param>   <param-name>contextConfigLocation</param-name>   <param-value>classpath:/applicationContext.xml</param-value> </context-param> <listener>   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
<!-- ②声明DispatcherServlet --> <servlet>   <servlet-name>smart</servlet-name>   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>   <load-on-startup>1</load-on-startup> </servlet> <!-- ③名为DispatcherServlet匹配的URL模式 --> <servlet-mapping>   <servlet-name>smart</servlet-name>   <url-pattern>.html</url-pattern> </servlet-mapping>

在①处,通过 contextConfigLocation 参数指定业务层 Spring 容器的配置文件(多个配置文件使用逗号分隔)。ContextLoaderListener 是一个ServletContextListener,它通过 contextConfigLocation 参数所指定的 Spring 配置文件启动“业务层”的 Spring 容器。

在②处配置了名为 smart 的 DispatcherServlet,它默认自动加载 /WEB-INF/smart-servlet.xml(<servlet-Name>-servlet.xml)的 Spring 配置文件,启动 Web 层的 Spring 容器。

在③处,通过 <servlet-mappmg> 指定 DispatcherServlet 处理所有以 .html 为后缀的 HTTP 请求,即所有带 .html 后缀的 HTTP 请求都会被 DispatcherServlet 截获并处理。

我们知道,多个 Spring 容器之间可设置为父子级的关系,以实现良好的解耦。在这里,"web层” Spring容器将作为“业务层” Spring 容器的子容器,即“web层”容器可以引用“业务层”容器的 Bean,而“业务层”容器却访问不到"web层”容器的Bean。

需要提醒的是,一个 web.xml 可以配置多个 DispatcherServlet,通过其 <servlet-mapping> 配置,让每个 DispatcherServlet 处理不同的请求。

DispatcherServlet 遵循“契约优于配置”的原则,在大多数情况下,用户无须进行额外的配置,只需按契约行事即可。

如果确实要对 DispatcherServlet 的默认规则进行调整,则 Dispatcherservlet 是“敞开胸怀”的。下面是常用的一些配置参数,可通过 <servlet> 的 <init-param>  指定。

(1)namespace:DispatcherServlet 对应的命名空间,默认为 <servlet-name>-servlet,用于构造 Spring 配置文件的路径。在显式指定该属性后,配置文件对应的路径为 WEB-INF/<namespace>.xml,而非 WEB-INF/<servlet-name>-servlet.xml。如果将 namespace 设置为 sample,则对应的 Spring 配置文件为 WEB-INF/sample.xml

(2)contextConfigLocation:如果 DispatcherServlet 上下文对应的 Sprlng 配置文件有多个,则可以使用该属性按照 Spring 资源路径的方式指定。如“classpath:samplel.xml,classpath:sample2.xml”,DispatcherServIet 将使用类路径下的 samplel.xml 和 sample2.xml 这两个配置文件初始化 WebApplicationContext。

(3)publishContext:布尔类型的属性,默认值为 ture。DispatcherServlet 根据该属性决定是否将 WebApplicationContext 发布到 ServletContext 的属性列表中,以便调用者可借由 ServletContext 找到 WebApplicationContext 实例,对应的属性名为 DispatcherServlet#getServletContextAttributeName() 方法的返回值。

(4)publishEvents:布尔类型的属性。当 DispatcherServlet 处理完一个请求后,是否需要向容器发布一个 ServletRequestHandledEvent 事件,默认值为 true。如果容器中没有任何事件监听器,则可以将该属性设置为 false,以便提高运行性能。

下面的代码显式指定 web 层的 Spring 配置文件。

<servlet>
  <servlet-name>smart</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/webApplicationContext.xml</param-value>
    </init-param>
</servlet>

之前提到 Spring4.0 已全面支持 Servlet3.0,因此,在3.0环境中,也可以使用编程的方式来配置 Servlet 容器。下面的代码可达到和上面代码同样的效果。

public class SmartApplicationInitializer implements WebApplicationInitializer《
    @Override
    public void onStartup(ServletContext container)
        ServietRegistration.Dynamic registration = container.addServlet("dispatcher",new
            DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping(".html");
    

接下来看看 Servlet3.0 的实现原理。在 Servlet3.0 环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer 的类,如果发现已有实现类,就会调用它来配置 Servlet 容器。在 Spring 中,org.springframework.web.SpringServletContainerInitializer 类实现了该接口,同时这个类又会查找实现org.springframework.web.WebApplicationInitializer 接口的类,并将配置任务交给这些实现类去完成。另外, Spring 提供了一个便利的抽象类 AbstractAnnotationConfigDispatcherServletlnitializer 来实现这个接口,使得它在注册 DispatcherServlet 时只需简单地指定它的 Serviet 映射即可。在上述示例中,当应用部署到 3.0 容器中时,容器启动时会自动发现它,并使用它来配置 Servlet 上下文。

2)探究 DispatcherServIet 的内部逻辑

现在剩下的最后一个问题是:Spring 如何将上下文中的 Spring MVC 组件装配到 DispatcherServIet 中?通过查看DispatcherServlet 的 initStrategies() 方法的代码,一切真相就大白于天下了。

protected void initStrategies(ApplicationContext context)
    initMultipartResolver();//①初始化上传文件解析器(直译为多部分请求解析器)
    initLocaleResolver();//②初始化本地化解析器
    initThemeResolver();//③初始化主题解析器
    initHandlerMappings();//④初始化处理器映射器
    initHandlerAdapters();//⑤初始化处理器适配器
    initHandIerExceptionResolver();//⑥初始化处理器异常解析器
    initRequestToViewNameTranslator();//⑦初始化请求到视图名翻译器
    initViewResolvers();//⑧初始化视图解析器

initStrategies() 方法将在 WebApplicationContext 初始化后自动执行,此时 Spring 上下文中的 Bean 已经初始化完毕。该方法的工作原理是:通过反射机制查找并装配 Spring 容器中用户显式自定义的组件 Bean,如果找不到,则装配默认的组件实例。

Spring MVC 定义了一套默认的组件实现类,也就是说,即使在 Spring 容器中没有显式定义组件 Bean,DispatcherServlet 也会装配好一套可用的默认组件。在 spring-webmvc-4.x.jar 包的 org/springframework/web/servlet 类路径下拥有一个 DispatcherServlet.properties 配置文件,该文件指定了 DispatcherServlet 所使用的默认组件。

##本地化解析器
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

##主题解析器
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

##处理器映射(共2个)
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

##处理器适配器(共3个)
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

##异常处理器(共3个)
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

##视图名称翻译器
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

##视图解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

技术图片

技术图片

有些组件最多允许存在一个实例,如 MultipartResolver、LocaleResolver 等,而另一些组件允许存在多个实例,如 HandlerMappmg、HandlerAdapter 等。同一类型的组件如果存在多个,那么它们之间的优先级顺序如何确定呢?这些组件都实现了 org.springframework.core.Ordered接口,可通过 order 属性确定优先级顺序,值越小优先级越高。

简言之,当 DispatcherServlet 初始化后,就会自动扫描上下文的 Bean,根据名称或类型匹配的机制查找自定义的组件,找不到时则使用 DispatcherServlet.properties 定义的默认组件

以上是关于Spring MVC 体系概述的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC体系结构和处理请求控制器

Spring MVC 设计概述

spring MVC概述

1.SpringMVC概述

1.SpringMVC概述

1.SpringMVC概述