用了这么多年 Spring MVC,你真的了解它吗?


640?wx_fmt=png

吐血推荐

今天,正式介绍一下Java极客技术知识星球

Spring 源码分析:不得不重视的 Transaction 事务

Spring 源码学习(八) AOP 使用和实现原理

这么火的 OKR,你不了解下?

Java:控制反转(IoC)与依赖注入(DI)


经过前面的 AOP(面向切面编程) 和 Transaction(事务管理),这次来到了 MVC(Web 应用,进行请求分发和处理)

Spring MVC 定义:

分离了控制器(Controller)、模型(Model)、分配器(Adapter)、视图(View)和处理程序对象(Handler,实际上调用的是 Controller 中定义的逻辑)。

基于 Servlet 功能实现,通过实现了 Servlet 接口的 DispatcherServlet 来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上传文件支持。

同样老套路,本篇按照以下思路展开:

(1) 介绍如何使用

(2) 辅助工具类 ContextLoaderContext

(3) DispatcherServlet 初始化

(4) DispatcherServlet 处理请求


如何使用

代码结构如下:(详细代码可在文章末尾下载)

用了这么多年 Spring MVC,你真的了解它吗?_第1张图片

(1)配置 web.xml

在该文件中,主要配置了两个关键点:

1. contextConfigLocation :使 WebSpring 的配置文件相结合的关键配置

2. DispatcherServlet : 包含了 SpringMVC 的请求逻辑,使用该类拦截 Web 请求并进行相应的逻辑处理

用了这么多年 Spring MVC,你真的了解它吗?_第2张图片

使用 IDEA 时,尽量选择默认条件和自动扫描加载 Web 配置文件,然后添加 tomcat 进行启动,具体配置请查阅 idea 创建java web项目ssm-gradle


(2) 配置 applicationContext.xml

用了这么多年 Spring MVC,你真的了解它吗?_第3张图片

可以在这里自定义想要加载的 bean,或者设置数据库数据源、事务管理器等等 Spring 应用配置。


(3) 配置 spring-mvc.xml

用了这么多年 Spring MVC,你真的了解它吗?_第4张图片

使用了 InternalResourceViewResolver,它是一个辅助 Bean,这样配置的意图是:
ModelAndView 返回的视图名前加上 prefix 指定的前缀和 suffix 的后缀(我理解为用来解析和返回视图,以及将视图层进行统一管理,放到指定路径中)


(4) 创建 BookController

用了这么多年 Spring MVC,你真的了解它吗?_第5张图片

可以看出,与书中示例并不一样,使用的是更贴合我们实际开发中用到的 @RequestMapping 等注解作为例子。根据请求的 URL 路径,匹配到对应的方法进行处理。


(5) 创建 jsp 文件

用了这么多年 Spring MVC,你真的了解它吗?_第6张图片

按照现在前后端分离的大趋势,我其实并不想用 jsp 视图技术作为例子,但考虑到之前入门时也接触过,也为了跟我一样不会写前端的同学更好理解,所以还是记录一下如何使用 jsp


(6) 添加依赖 build.gradle

// 引入 spring-web 和 spring-webmvc,如果不是跟我一样使用源码进行编译,请到 mvn 仓库中寻找对应依赖optional(project(":spring-web"))optional(project(":spring-webmvc"))// 引入这个依赖,使用 jsp 语法 https://mvnrepository.com/artifact/javax.servlet/jstlcompile group: 'javax.servlet', name: 'jstl', version: '1.2'
optional(project(":spring-webmvc"))

// 引入这个依赖,使用 jsp 语法 https://mvnrepository.com/artifact/javax.servlet/jstl
compile group: 'javax.servlet', name: 'jstl', version: '1.2'

(7) 启动 Tomcat
如何配置和启动,网上也有很多例子,参考资料 3 是个不错的例子,下面是请求处理结果:

http://localhost:8080/bookView   (使用了 JSP 视图进行渲染)

用了这么多年 Spring MVC,你真的了解它吗?_第7张图片

http://localhost:8080/plain/value (前后端分离的话,常用的是这种,最后可以返回简单字符或者 json 格式的对象等)

用了这么多年 Spring MVC,你真的了解它吗?_第8张图片

在刚才的 web.xml 中有两个关键配置,所以现在学习下这两个配置具体是干啥的。


ContextLoaderContext

作用:在启动 web 容器时,自动装载 ApplicationContext 的配置信息。

下面是它的继承体系图:

用了这么多年 Spring MVC,你真的了解它吗?_第9张图片

这是一个辅助工具类,可以用来传递配置信息参数,在 web.xml 中,将路径以 context-param 的方式注册并使用 ContextLoaderListener 进行监听读取。

从图中能看出,它实现了 ServletContextListener 这个接口,只要在 web.xml 配置了这个监听器,容器在启动时,就会执行 contextInitialized(ServletContextEvent) 这个方法,进行应用上下文初始化。

public void contextInitialized(ServletContextEvent event) {    initWebApplicationContext(event.getServletContext());}
    initWebApplicationContext(event.getServletContext());
}

每个 Web 应用都会有一个 ServletContext 贯穿生命周期(在应用启动时创建,关闭时销毁),跟 SpringApplicationContext 类似,在全局范围内有效。

实际上初始化的工作,是由父类 ContextLoader 完成的:(简略版)

用了这么多年 Spring MVC,你真的了解它吗?_第10张图片

该函数主要是体现了创建 WebApplicationContext 实例的一个功能架构,实现的大致步骤如下:

1. WebApplicationContext 存在性的验证:
只能初始化一次,如果有多个声明,将会扰乱 Spring 的执行逻辑,所以有多个声明将会报错。
2. 创建 WebApplicationContext 实例:
createWebApplicationContext(servletContext);

用了这么多年 Spring MVC,你真的了解它吗?_第11张图片

如果按照默认策略,它将会从配置文件 ContextLoader.properties 中读取需要创建的实现类:XmlWebApplicationContext

3. 将实例记录在 servletContext
4. 映射当前的类加载器与创建的实例到全局变量 currentContextPerThread

通过以上步骤,完成了创建 WebApplicationContext 实例,它继承自 ApplicaitonContext,在父类的基础上,追加了一些特定于 web 的操作和属性,可以把它当成我们之前初始化 Spring 容器时所用到的 ClassPathApplicaitonContext 那样使用。


DispatcherServlet 初始化

该类是 spring-mvc 的核心,该类进行真正逻辑实现,DisptacherServlet 实现了 Servlet 接口。

用了这么多年 Spring MVC,你真的了解它吗?_第12张图片

介绍:

servlet 是一个 Java 编写的程序,基于 Http 协议,例如我们常用的 Tomcat,也是按照 servlet 规范编写的一个 Java

servlet 的生命周期是由 servlet 的容器来控制,分为三个阶段:初始化、运行和销毁。

servlet 初始化阶段会调用其 init 方法:

HttpServletBean#init

public final void init() throws ServletException {    // 解析 init-param 并封装到 pvs 变量中    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);    // 将当前的这个 Servlet 类转换为一个 BeanWrapper,从而能够以 Spring 的方式对 init—param 的值注入    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);    ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());    // 注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));    // 空实现,留给子类覆盖    initBeanWrapper(bw);    bw.setPropertyValues(pvs, true);    // 初始化 servletBean (让子类实现,这里它的实现子类是 FrameworkServlet)    initServletBean();}
    // 解析 init-param 并封装到 pvs 变量中
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    // 将当前的这个 Servlet 类转换为一个 BeanWrapper,从而能够以 Spring 的方式对 init—param 的值注入
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    // 注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析
    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
    // 空实现,留给子类覆盖
    initBeanWrapper(bw);
    bw.setPropertyValues(pvs, true);
    // 初始化 servletBean (让子类实现,这里它的实现子类是 FrameworkServlet)
    initServletBean();
}

在这里初始化 DispatcherServlet,主要是通过将当前的 servlet 类型实例转换为 BeanWrapper 类型实例,以便使用 Spring 中提供的注入功能进行相应属性的注入。

从上面注释,可以看出初始化函数的逻辑比较清晰,封装参数、转换成 BeanWrapper 实例、注册自定义属性编辑器、属性注入,以及关键的初始化 servletBean


容器初始化

下面看下初始化关键逻辑:

FrameworkServlet#initServletBean

剥离了日志打印后,剩下的两行关键代码

protected final void initServletBean() throws ServletException {    // 仅剩的两行关键代码    this.webApplicationContext = initWebApplicationContext();    // 留给子类进行覆盖实现,但我们例子中用的 DispatcherServlet 并没有覆盖,所以先不用管它    initFrameworkServlet();}
    // 仅剩的两行关键代码
    this.webApplicationContext = initWebApplicationContext();
    // 留给子类进行覆盖实现,但我们例子中用的 DispatcherServlet 并没有覆盖,所以先不用管它
    initFrameworkServlet();
}

WebApplicationContext 的初始化

FrameworkServlet#initWebApplicationContext

该函数的主要工作就是创建或刷新 WebApplicationContext 实例并对 servlet 功能所使用的变量进行初始化。

protected WebApplicationContext initWebApplicationContext() {    // 从根容器开始查找    WebApplicationContext rootContext =            WebApplicationContextUtils.getWebApplicationContext(getServletContext());    WebApplicationContext wac = null;    if (this.webApplicationContext != null) {        // 有可能在 Spring 加载 bean 时,DispatcherServlet 作为 bean 加载进来了        // 直接使用在构造函数被注入的 context 实例        wac = this.webApplicationContext;        if (wac instanceof ConfigurableWebApplicationContext) {            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;            if (!cwac.isActive()) {                if (cwac.getParent() == null) {                    cwac.setParent(rootContext);                }                // 刷新上下文环境                configureAndRefreshWebApplicationContext(cwac);            }        }    }    if (wac == null) {        // 根据 contextAttribute 属性加载 WebApplicationContext        wac = findWebApplicationContext();    }    if (wac == null) {        // 经过上面步骤都没找到,那就来创建一个        wac = createWebApplicationContext(rootContext);    }    if (!this.refreshEventReceived) {        synchronized (this.onRefreshMonitor) {            // 刷新,初始化很多策略方法            onRefresh(wac);        }    }    if (this.publishContext) {        // Publish the context as a servlet context attribute.        String attrName = getServletContextAttributeName();        getServletContext().setAttribute(attrName, wac);    }    return wac;}
    // 从根容器开始查找
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        // 有可能在 Spring 加载 bean 时,DispatcherServlet 作为 bean 加载进来了
        // 直接使用在构造函数被注入的 context 实例
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                // 刷新上下文环境
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 根据 contextAttribute 属性加载 WebApplicationContext
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 经过上面步骤都没找到,那就来创建一个
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            // 刷新,初始化很多策略方法
            onRefresh(wac);
        }
    }
    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

根容器查找

我们最常用到的 spring-mvc,是 spring 容器和 web 容器共存,这时 rootContext 父容器就是 spring 容器。

在前面的 web.xml 配置的监听器 ContextLaoderListener,已经将 Spring 父容器进行了加载

WebApplicationContextUtils#getWebApplicationContext(ServletContext)

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {    // key 值 :WebApplicationContext.class.getName() + ".ROOT"    // (ServletContext) sc.getAttribute(attrName) ,    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);}
    // key 值 :WebApplicationContext.class.getName() + ".ROOT"
    // (ServletContext) sc.getAttribute(attrName) ,
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

同时,根据上面代码,了解到 Spring 父容器,是以 key 值为 : WebApplicationContext.class.getName() + ".ROOT" 保存到 ServletContext 上下文中。


根据 contextAttribute 寻找

虽然有默认 key,但用户可以重写初始化逻辑(在 web.xml 文件中设定 servlet 参数 contextAttribute),使用自己创建的 WebApplicaitonContext,并在 servlet 的配置中通过初始化参数 contextAttribute 指定 key

protected WebApplicationContext findWebApplicationContext() {    String attrName = getContextAttribute();    if (attrName == null) {        return null;    }    // attrName 就是用户在`web.xml` 文件中设定的 `servlet` 参数 `contextAttribute`    WebApplicationContext wac =            WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);    if (wac == null) {        throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");    }    return wac;}
    String attrName = getContextAttribute();
    if (attrName == null) {
        return null;
    }
    // attrName 就是用户在`web.xml` 文件中设定的 `servlet` 参数 `contextAttribute`
    WebApplicationContext wac =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    if (wac == null) {
        throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    return wac;
}

重新创建实例

通过前面的方法都没找到,那就来重新创建一个新的实例:

FrameworkServlet#createWebApplicationContext(WebApplicationContext)

protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {    return createWebApplicationContext((ApplicationContext) parent);}protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {    // 允许我们自定义容器的类型,通过 contextClass 属性进行配置    // 但是类型必须要继承 ConfigurableWebApplicationContext,不然将会报错    Class contextClass = getContextClass();    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {        throw new ApplicationContextException();    }    // 通过反射来创建 contextClass    ConfigurableWebApplicationContext wac =            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);    wac.setEnvironment(getEnvironment());    wac.setParent(parent);    // 获取 contextConfigLocation 属性,配置在 servlet 初始化函数中    String configLocation = getContextConfigLocation();    wac.setConfigLocation(configLocation);    // 初始化 Spring 环境包括加载配置环境    configureAndRefreshWebApplicationContext(wac);    return wac;}
    return createWebApplicationContext((ApplicationContext) parent);
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // 允许我们自定义容器的类型,通过 contextClass 属性进行配置
    // 但是类型必须要继承 ConfigurableWebApplicationContext,不然将会报错
    Class contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException();
    }
    // 通过反射来创建 contextClass
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    // 获取 contextConfigLocation 属性,配置在 servlet 初始化函数中
    String configLocation = getContextConfigLocation();
    wac.setConfigLocation(configLocation);
    // 初始化 Spring 环境包括加载配置环境
    configureAndRefreshWebApplicationContext(wac);
    return wac;
}

获取上下文类 contextClass

默认使用的是 XmlWebApplicationContext,但如果需要配置自定义上下文,可以在 web.xml 中的 标签中修改 contextClass 属性对应的 value,但需要注意图中提示:

用了这么多年 Spring MVC,你真的了解它吗?_第13张图片

configureAndRefreshWebApplicationContext

使用该方法,用来对已经创建的 WebApplicaitonContext 进行配置以及刷新

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {    // 遍历 ApplicationContextInitializer,执行 initialize 方法    applyInitializers(wac);    // 关键的刷新,加载配置文件及整合 parent 到 wac    wac.refresh();}
    // 遍历 ApplicationContextInitializer,执行 initialize 方法
    applyInitializers(wac);
    // 关键的刷新,加载配置文件及整合 parent 到 wac
    wac.refresh();
}

ApplicationContextInitializer

该类可以通过 contextInitializerClasses 进行自定义配置:

    contextInitializerClasses    自定义类,需继承于 `ApplicationContextInitializer`
    <param-name>contextInitializerClassesparam-name>
    <param-value>自定义类,需继承于 `ApplicationContextInitializer`param-value>
init-param>

正如代码中的顺序一样,是在 mvc 容器创建前,执行它的 void initialize(C applicationContext) 方法:

protected void applyInitializers(ConfigurableApplicationContext wac) {    AnnotationAwareOrderComparator.sort(this.contextInitializers);    for (ApplicationContextInitializer initializer : this.contextInitializers) {        initializer.initialize(wac);    }}
    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer initializer : this.contextInitializers) {
        initializer.initialize(wac);
    }
}

所有如果没有配置的话,默认情况下 contextInitializers 列表为空,表示没有 ApplicationContextInitializer 需要执行。


加载 Spring 配置

wac.refresh(),实际调用的是我们之前就很熟悉的刷新方法:

org.springframework.context.support.AbstractApplicationContext#refresh

用了这么多年 Spring MVC,你真的了解它吗?_第14张图片

从图中能够看出,刷新方法的代码逻辑与之前一样,通过父类 AbstractApplicationContextrefresh 方法,进行了配置文件的加载。

在例子中的 web.xml 配置中,指定了加载 spring-mvc.xml 配置文件

    dispatcherServlet    org.springframework.web.servlet.DispatcherServlet            contextConfigLocation        /WEB-INF/spring-mvc.xml    
<servlet>
    <servlet-name>dispatcherServletservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>/WEB-INF/spring-mvc.xmlparam-value>
    init-param>
servlet>

注册 mvc 解析器

由于我们配置了 contextConfigLocation,指定了加载资源的路径,所以在 XmlWebApplicationContext 初始化的时候,加载的 Spring 配置文件路径是我们指定 spring-mvc.xml

用了这么多年 Spring MVC,你真的了解它吗?_第15张图片

spring-mvc.xml 配置中,主要配置了三项

        
<context:component-scan base-package="web.controller"/>

<mvc:annotation-driven/>


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

同样老套路,使用了 自定义注解的话,要注册相应的解析器后,Spring 容器才能解析元素:

org.springframework.web.servlet.config.MvcNamespaceHandler

public void init() {    // MVC 标签解析需要注册的解析器    registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());    registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());    registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());    registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());    registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());    registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());    registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());    registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());    registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());    registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());    registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());    registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());    registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());}
    // MVC 标签解析需要注册的解析器
    registerBeanDefinitionParser("annotation-driven"new AnnotationDrivenBeanDefinitionParser());
    registerBeanDefinitionParser("default-servlet-handler"new DefaultServletHandlerBeanDefinitionParser());
    registerBeanDefinitionParser("interceptors"new InterceptorsBeanDefinitionParser());
    registerBeanDefinitionParser("resources"new ResourcesBeanDefinitionParser());
    registerBeanDefinitionParser("view-controller"new ViewControllerBeanDefinitionParser());
    registerBeanDefinitionParser("redirect-view-controller"new ViewControllerBeanDefinitionParser());
    registerBeanDefinitionParser("status-controller"new ViewControllerBeanDefinitionParser());
    registerBeanDefinitionParser("view-resolvers"new ViewResolversBeanDefinitionParser());
    registerBeanDefinitionParser("tiles-configurer"new TilesConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("freemarker-configurer"new FreeMarkerConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("groovy-configurer"new GroovyMarkupConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("script-template-configurer"new ScriptTemplateConfigurerBeanDefinitionParser());
    registerBeanDefinitionParser("cors"new CorsBeanDefinitionParser());
}

可以看到,mvc 提供了很多便利的注解,有拦截器、资源、视图等解析器,但我们常用的到的是 anntation-driven 注解驱动,这个注解通过 AnnotationDrivenBeanDefinitionParser 类进行解析,其中会注册两个重要的 bean :

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {    public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();    public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();    ...}

    public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();

    public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();
    ...
}

跳过其他熟悉的 Spring 初始化配置,通过上面的步骤,完成了 Spring 配置文件的解析,将扫描到的 bean 加载到了 Spring 容器中。

那么下面就正式进入 mvc 的初始化。


mvc 初始化

onRefresh 方法是 FrameworkServlet 类中提供的模板方法,在子类 DispatcherServlet 进行了重写,主要用来刷新 SpringWeb 功能实现中所必须用到的全局变量:

protected void onRefresh(ApplicationContext context) {    initStrategies(context);}protected void initStrategies(ApplicationContext context) {    // 初始化 multipartResolver 文件上传相关    initMultipartResolver(context);    // 初始化 LocalResolver 与国际化相关    initLocaleResolver(context);    // 初始化 ThemeResolver 与主题更换相关    initThemeResolver(context);    // 初始化 HandlerMapping 与匹配处理器相关    initHandlerMappings(context);    // 初始化 HandlerAdapter 处理当前 Http 请求的处理器适配器实现,根据处理器映射返回相应的处理器类型    initHandlerAdapters(context);    // 初始化 HandlerExceptionResolvers,处理器异常解决器    initHandlerExceptionResolvers(context);    // 初始化 RequestToViewNameTranslator,处理逻辑视图名称    initRequestToViewNameTranslator(context);    // 初始化 ViewResolver 选择合适的视图进行渲染    initViewResolvers(context);    // 初始化 FlashMapManager 使用 flash attributes 提供了一个请求存储属性,可供其他请求使用(重定向时常用)    initFlashMapManager(context);}
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    // 初始化 multipartResolver 文件上传相关
    initMultipartResolver(context);
    // 初始化 LocalResolver 与国际化相关
    initLocaleResolver(context);
    // 初始化 ThemeResolver 与主题更换相关
    initThemeResolver(context);
    // 初始化 HandlerMapping 与匹配处理器相关
    initHandlerMappings(context);
    // 初始化 HandlerAdapter 处理当前 Http 请求的处理器适配器实现,根据处理器映射返回相应的处理器类型
    initHandlerAdapters(context);
    // 初始化 HandlerExceptionResolvers,处理器异常解决器
    initHandlerExceptionResolvers(context);
    // 初始化 RequestToViewNameTranslator,处理逻辑视图名称
    initRequestToViewNameTranslator(context);
    // 初始化 ViewResolver 选择合适的视图进行渲染
    initViewResolvers(context);
    // 初始化 FlashMapManager 使用 flash attributes 提供了一个请求存储属性,可供其他请求使用(重定向时常用)
    initFlashMapManager(context);
}

该函数是实现 mvc 的关键所在,先来大致介绍一下初始化的套路:

  1. 寻找用户自定义配置

  2. 没有找到,使用默认配置

显然,Spring 给我们提供了高度的自定义,可以手动设置想要的解析器,以便于扩展功能。

如果没有找到用户配置的 bean,那么它将会使用默认的初始化策略: getDefaultStrategies 方法


默认策略

DispatcherServlet#getDefaultStrategies(缩减版)

protected  List getDefaultStrategies(ApplicationContext context, Class strategyInterface) {    // 策略接口名称    String key = strategyInterface.getName();    // 默认策略列表    String value = defaultStrategies.getProperty(key);    String[] classNames = StringUtils.commaDelimitedListToStringArray(value);    List strategies = new ArrayList<>(classNames.length);    for (String className : classNames) {        // 实例化        Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());        Object strategy = createDefaultStrategy(context, clazz);        strategies.add((T) strategy);    }    return strategies;}// 默认策略列表private static final Properties defaultStrategies;static {    // 路径名称是:DispatcherServlet.properties    try {        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);    }}List getDefaultStrategies(ApplicationContext context, Class strategyInterface) {
    // 策略接口名称
    String key = strategyInterface.getName();
    // 默认策略列表
    String value = defaultStrategies.getProperty(key);
    String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
    List strategies = new ArrayList<>(classNames.length);
    for (String className : classNames) {
        // 实例化
        Class clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
        Object strategy = createDefaultStrategy(context, clazz);
        strategies.add((T) strategy);
    }
    return strategies;
}

// 默认策略列表
private static final Properties defaultStrategies;

static {
    // 路径名称是:DispatcherServlet.properties
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
}

从静态默认策略属性 defaultStrategies 的加载过程中,读取的是 DispatcherServlet.properties 文件内容,看完下面列出来的信息,相信你跟我一样恍然大悟,了解 Spring 配置了哪些默认策略:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\    org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\    org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
    org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    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 文件上传相关

private void initMultipartResolver(ApplicationContext context) {    try {        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);    catch (NoSuchBeanDefinitionException ex) {        // Default is no multipart resolver.        this.multipartResolver = null;    }}
    try {
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
    }
}

默认情况下,Spring 是没有 mulitpart 处理,需要自己设定


<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

注册的 idmultipartResolver


LocalResolver 与国际化相关

LocalResolver 接口定义了如何获取客户端的地区

private void initLocaleResolver(ApplicationContext context) {    try {        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);    }    catch (NoSuchBeanDefinitionException ex) {        // We need to use the default.        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);    }}
    try {
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
    }
}

通过寻找 idlocaleResolverbean,如果没有的话,将会使用默认的策略进行加载 AcceptHeaderLocaleResolver,它是基于 URL 参数来控制国际化,例如使用  来设定简体中文,默认参数名为 locale

当然还有其他两种,基于 session 和基于 cookie 的配置,想要深入了解的可以去细看~


ThemeResolver 主题更换相关

主题是一组静态资源(例如样式表 css 和图片 image),也可以理解为应用皮肤,使用 Theme 更改主题风格,改善用户体验。

默认注册的 idthemeResolver,类型是 FixedThemeResolver,表示使用的是一个固定的主题,以下是它的继承体系图:

用了这么多年 Spring MVC,你真的了解它吗?_第16张图片

工作原理是通过拦截器拦截,配置对应的主题解析器,然后返回主题名称,还是使用上面的解析器作为例子:

FixedThemeResolver#resolveThemeName

public String resolveThemeName(HttpServletRequest request) {    return getDefaultThemeName();}public String getDefaultThemeName() {    return this.defaultThemeName;}
    return getDefaultThemeName();
}

public String getDefaultThemeName() {
    return this.defaultThemeName;
}

HandlerMapping 与匹配处理器相关

首先判断 detectAllHandlerMappings 变量是否为 true,表示是否需要加载容器中所有的 HandlerMappingfalse 将会加载用户配置的。

如注释所说,至少得保证有一个 HandlerMapping,如果前面两个分支都没寻找到,那么就进行默认策略加载。

private void initHandlerMappings(ApplicationContext context) {    this.handlerMappings = null;    if (this.detectAllHandlerMappings) {        // 默认情况下,寻找应用中所有的 HandlerMapping ,包括祖先容器(其实就是 Spring 容器啦)        Map matchingBeans =                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);        if (!matchingBeans.isEmpty()) {            this.handlerMappings = new ArrayList<>(matchingBeans.values());            // handlerMapping 有优先级,需要排序            AnnotationAwareOrderComparator.sort(this.handlerMappings);        }    }    else {        // 从上下文中,获取名称为 handlerMapping 的 bean        HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);        this.handlerMappings = Collections.singletonList(hm);    }    // 需要保证,至少有一个 HandlerMapping    // 如果前面两步都没找到 mapping,将会由这里加载默认策略    if (this.handlerMappings == null) {        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);    }}
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        // 默认情况下,寻找应用中所有的 HandlerMapping ,包括祖先容器(其实就是 Spring 容器啦)
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, truefalse);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // handlerMapping 有优先级,需要排序
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        // 从上下文中,获取名称为 handlerMapping 的 bean
        HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
        this.handlerMappings = Collections.singletonList(hm);

    }
    // 需要保证,至少有一个 HandlerMapping
    // 如果前面两步都没找到 mapping,将会由这里加载默认策略
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }
}

通过 Debug 得知,之前在加载 Spring 配置时,就已经注入了 RequestMappingHandlerMappingBeanNameUrlHandlerMapping

用了这么多年 Spring MVC,你真的了解它吗?_第17张图片


HandlerAdapter 适配器

套路与前面的一样,使用的默认策略是:HttpRequestHandlerAdapterSimpleControllerHandlerAdapterRequestMappingHandlerAdapterHandlerFunctionAdapter

说到适配器,可以将它理解为,将一个类的接口适配成用户所期待的,将两个接口不兼容的工作类,通过适配器连接起来。


HandlerExceptionResolver 处理器异常解决器

套路也与前面一样,使用的默认策略是:ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver

实现了 HandlerExceptionResolver 接口的 resolveException 方法,在方法内部对异常进行判断,然后尝试生成 ModelAndView 返回。

public ModelAndView resolveException(        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {    if (shouldApplyTo(request, handler)) {        prepareResponse(ex, response);        ModelAndView result = doResolveException(request, response, handler, ex);        return result;    }    else {        return null;    }}
    if (shouldApplyTo(request, handler)) {
        prepareResponse(ex, response);
        ModelAndView result = doResolveException(request, response, handler, ex);
        return result;
    }
    else {
        return null;
    }
}

RequestToViewNameTranslator 处理逻辑视图名称

初始化代码逻辑与前面一样,使用的默认策略是:DefaultRequestToViewNameTranslator

使用场景:当 Controller 处理器方法没有返回逻辑视图名称时,Spring 通过该类的约定,提供一个逻辑视图名称。

由于本地测试不出来,所以引用参考资料 7 的例子:

DefaultRequestToViewNameTranslator的转换例子:

http://localhost:8080/gamecast/display.html -> display(视图)


ViewResolver 视图渲染

套路还是跟前面一样,默认策略使用的是:InternalResourceViewResolver

同时,这也是 demo 中,我们手动配置的视图解析器


FlashMapManager 存储属性

默认使用的是:SessionFlashMapManager,通过与 FlashMap 配合使用,用于在重定向时保存/传递参数

例如 Post/Redirect/Get 模式,Flash attribute 在重定向之前暂存(根据类名,可以知道范围是 session 级别有效),以便重定向之后还能使用。


RequestMappingHandler

该类作用:配合 @Controller@RequestMapping 注解使用,通过 URL 来找到对应的处理器。

前面在 spring-mvc.xml 文件加载时,初始化了两个重要配置,其中一个就是下面要说的 RequestMappingHandler,先来看它的继承体系图:

用了这么多年 Spring MVC,你真的了解它吗?_第18张图片 request_mapping_handler_mapping_diagram

从继承图中看到,它实现了 InitializingBean 接口,所以在初始化时,将会执行 afterPropertiesSet 方法(图片中注释写错方法,请以下面为准),核心调用的初始化方法是父类 AbstractHandlerMethodMapping#initHandlerMethods 方法

AbstractHandlerMethodMapping#initHandlerMethods

protected void initHandlerMethods() {    // 获取容器中所有 bean 名字    for (String beanName : this.detectHandlerMethodsInAncestorContexts ?                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :                obtainApplicationContext().getBeanNamesForType(Object.class)) {        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {            // 如果前缀不是 scopedTarget.            // 执行 detectHandlerMethods() 方法            Class beanType = obtainApplicationContext().getType(beanName);            if (beanType != null && isHandler(beanType)) {                detectHandlerMethods(beanName);            }        }    }    // 打印数量,可以当成空实现    handlerMethodsInitialized(getHandlerMethods());}protected void detectHandlerMethods(Object handler) {    Class handlerType = (handler instanceof String ?            obtainApplicationContext().getType((String) handler) : handler.getClass());    if (handlerType != null) {        Class userType = ClassUtils.getUserClass(handlerType);        // 通过反射,获取类中所有方法        // 筛选出 public 类型,并且带有 @RequestMapping 注解的方法        Map methods = MethodIntrospector.selectMethods(userType,                (MethodIntrospector.MetadataLookup) method -> {                    // 通过 RequestMappingHandlerMapping.getMappingForMethod 方法组装成 RequestMappingInfo(映射关系)                    return getMappingForMethod(method, userType);            });        methods.forEach((method, mapping) -> {            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);            // 通过 mappingRegistry 进行注册上面获取到的映射关系            registerHandlerMethod(handler, invocableMethod, mapping);        });    }}
    // 获取容器中所有 bean 名字
    for (String beanName : this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
                obtainApplicationContext().getBeanNamesForType(Object.class)) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            // 如果前缀不是 scopedTarget.
            // 执行 detectHandlerMethods() 方法
            Class beanType = obtainApplicationContext().getType(beanName);
            if (beanType != null && isHandler(beanType)) {
                detectHandlerMethods(beanName);
            }
        }
    }
    // 打印数量,可以当成空实现
    handlerMethodsInitialized(getHandlerMethods());
}

protected void detectHandlerMethods(Object handler) {
    Class handlerType = (handler instanceof String ?
            obtainApplicationContext().getType((String) handler) : handler.getClass());
    if (handlerType != null) {
        Class userType = ClassUtils.getUserClass(handlerType);
        // 通过反射,获取类中所有方法
        // 筛选出 public 类型,并且带有 @RequestMapping 注解的方法
        Map methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup) method -> {
                    // 通过 RequestMappingHandlerMapping.getMappingForMethod 方法组装成 RequestMappingInfo(映射关系)
                    return getMappingForMethod(method, userType);

            });
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            // 通过 mappingRegistry 进行注册上面获取到的映射关系
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

梳理一下代码逻辑,initHandlerMethods 方法将会扫描注册 bean 下所有公共 public 方法,如果带有 @RequestMapping 注解的,将会组装成 RequestMappingInfo 映射关系,然后将它注册到 mappingRegistry 变量中。之后可以通过映射关系,输入 URL 就能够找到对应的处理器 Controller


MappingRegistry

该类是 AbstractHandlerMethodMapping 的内部类,是个工具类,用来保存所有 Mappinghandler method,通过暴露加锁的公共方法,避免了多线程对该类的内部变量的覆盖修改。

下面是注册的逻辑:

public void register(T mapping, Object handler, Method method) {    this.readWriteLock.writeLock().lock();    try {        // 包装 bean 和方法        HandlerMethod handlerMethod = createHandlerMethod(handler, method);        // 校验        validateMethodMapping(handlerMethod, mapping);        this.mappingLookup.put(mapping, handlerMethod);        List directUrls = getDirectUrls(mapping);        for (String url : directUrls) {            this.urlLookup.add(url, mapping);        }        String name = null;        if (getNamingStrategy() != null) {            name = getNamingStrategy().getName(handlerMethod, mapping);            addMappingName(name, handlerMethod);        }        // 跨域参数        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);        if (corsConfig != null) {            this.corsLookup.put(handlerMethod, corsConfig);        }        // 将映射关系放入  Map> registry        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));    }    finally {        this.readWriteLock.writeLock().unlock();    }}
    this.readWriteLock.writeLock().lock();
    try {
        // 包装 bean 和方法
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        // 校验
        validateMethodMapping(handlerMethod, mapping);
        this.mappingLookup.put(mapping, handlerMethod);
        List directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }
        // 跨域参数
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
        // 将映射关系放入  Map> registry
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

用了这么多年 Spring MVC,你真的了解它吗?_第19张图片

通过前面的包装和校验方法,最后映射关系将会放入这里 Map> registry。它是一个泛型的 Mapkey 类型是 RequestMappingInfo,保存了 @RequestMapping 各种属性的集合,value 类型是 AbstractHandlerMethodMapping,保存的是我们的映射关系。

从图中可以看出,如果输入的 URL/plain/{name},将会找到对应的处理方法 web.controller.BookController#plain{String}


RequestMappingHandlerAdapter

而另一个重要的配置就是处理器适配器 RequestMappingHandlerAdapter,由于它的继承体系与 RequestMappingHandler 类似,所以我们直接来看它在加载时执行的方法

RequestMappingHandlerAdapter#afterPropertiesSet

public void afterPropertiesSet() {    // 首先执行这个方法,可以添加 responseBody 切面 bean    initControllerAdviceCache();    // 参数处理器    if (this.argumentResolvers == null) {        List resolvers = getDefaultArgumentResolvers();        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);    }    // 处理 initBinder 注解    if (this.initBinderArgumentResolvers == null) {        List resolvers = getDefaultInitBinderArgumentResolvers();        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);    }    // 初始化结果处理器    if (this.returnValueHandlers == null) {        List handlers = getDefaultReturnValueHandlers();        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);    }}
    // 首先执行这个方法,可以添加 responseBody 切面 bean
    initControllerAdviceCache();
    // 参数处理器
    if (this.argumentResolvers == null) {
        List resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 处理 initBinder 注解
    if (this.initBinderArgumentResolvers == null) {
        List resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 初始化结果处理器
    if (this.returnValueHandlers == null) {
        List handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

所以看到这个适配器中,初始化了很多工具变量,用来处理 @ControllerAdviceInitBinder 等注解和参数。不过核心还是待会要讲到的 handleInternal() 方法,它将适配处理器调用,然后返回 ModelView 视图。


DispatcherServlet 的逻辑处理

请求处理的入口定义在 HttpServlet,主要有以下几个方法:

用了这么多年 Spring MVC,你真的了解它吗?_第20张图片

当然,父类 HttpServlet 只是给出了定义,直接调用父类这些方法将会报错,所以 FrameworkServlet 将它们覆盖重写了处理逻辑:

protected final void doGet(HttpServletRequest request, HttpServletResponse response) {    // 注解 10. 具体调用的是 processRequest 方法    processRequest(request, response);}protected final void doPost(HttpServletRequest request, HttpServletResponse response) {    processRequest(request, response);}
    // 注解 10. 具体调用的是 processRequest 方法
    processRequest(request, response);
}

protected final void doPost(HttpServletRequest request, HttpServletResponse response) {
    processRequest(request, response);
}

可以看到 doGetdoPost 这些方法,底层调用的都是 processRequest 方法进行处理,关键方法是委托给子类 DispatcherServletdoServie() 方法

DispatcherServlet#doService

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {    logRequest(request);    // 暂存请求参数    Map attributesSnapshot = null;    ...    // 经过前面的准备(属性、辅助变量),进入请求处理过程    doDispatch(request, response);}
    logRequest(request);
    // 暂存请求参数
    Map attributesSnapshot = null;
    ...
    // 经过前面的准备(属性、辅助变量),进入请求处理过程
    doDispatch(request, response);
}

请求分发和处理逻辑的核心是在 doDispatch(request, response) 方法中,在进入这个方法前,还有些准备工作需要执行。


请求上下文

processRequestdoServie() 方法执行前,主要做了这以下准备工作:

(1) 为了保证当前线程的 LocaleContext 以及 RequestAttributes 可以在当前请求后还能恢复,提取当前线程的两个属性。
(2) 根据当前 request 创建对应的 LocaleContext 以及 RequestAttributes,绑定到当前线程
(3) 往 request 对象中设置之前加载过的 localeResolverflashMapManager 等辅助工具变量


请求分发 doDispatch

经过前面的配置设置,doDispatch 函数展示了请求的完成处理过程:

DispatcherServlet#doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {    HttpServletRequest processedRequest = request;    HandlerExecutionChain mappedHandler = null;     // 注释 10. 检查是否 MultipartContent 类型    processedRequest = checkMultipart(request);    // 根据 request 信息寻找对应的 Handler    mappedHandler = getHandler(processedRequest);    if (mappedHandler == null) {        // 没有找到 handler,通过 response 向用户返回错误信息        noHandlerFound(processedRequest, response);        return;    }    // 根据当前的 handler 找到对应的 HandlerAdapter 适配器    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());    // 如果当前 handler 支持 last-modified 头处理    String method = request.getMethod();    boolean isGet = "GET".equals(method);    if (isGet || "HEAD".equals(method)) {        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {            return;        }    }    // 拦截器的 preHandler 方法的调用    if (!mappedHandler.applyPreHandle(processedRequest, response)) {        return;    }    // 真正激活 handler 进行处理,并返回视图    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());    if (asyncManager.isConcurrentHandlingStarted()) {        return;    }    // 视图名称转换(有可能需要加上前后缀)    applyDefaultViewName(processedRequest, mv);    // 应用所有拦截器的 postHandle 方法    mappedHandler.applyPostHandle(processedRequest, response, mv);    // 处理分发的结果(如果有 mv,进行视图渲染和跳转)    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);    }
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null
    // 注释 10. 检查是否 MultipartContent 类型
    processedRequest = checkMultipart(request);
    // 根据 request 信息寻找对应的 Handler
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
        // 没有找到 handler,通过 response 向用户返回错误信息
        noHandlerFound(processedRequest, response);
        return;
    }
    // 根据当前的 handler 找到对应的 HandlerAdapter 适配器
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // 如果当前 handler 支持 last-modified 头处理
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
        }
    }
    // 拦截器的 preHandler 方法的调用
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    // 真正激活 handler 进行处理,并返回视图
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    if (asyncManager.isConcurrentHandlingStarted()) {
        return;
    }
    // 视图名称转换(有可能需要加上前后缀)
    applyDefaultViewName(processedRequest, mv);
    // 应用所有拦截器的 postHandle 方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    // 处理分发的结果(如果有 mv,进行视图渲染和跳转)
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);    
}

上面贴出来的代码略有缩减,不过从上面示例中能看出,整体的逻辑都挺清晰的,主要步骤如下:

1. 寻找处理器 mappedandler
2. 根据处理器,寻找对应的适配器 HandlerAdapter
3. 激活 handler,调用处理方法
4. 返回结果(如果有 mv,进行视图渲染和跳转)


寻找处理器 mappedHandler

demo 说明,寻找处理器,就是根据 URL 找到对应的 Controller 方法

DispatcherServlet#getHandler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {    if (this.handlerMappings != null) {        // 遍历注册的全部 handlerMapping        for (HandlerMapping mapping : this.handlerMappings) {            HandlerExecutionChain handler = mapping.getHandler(request);            if (handler != null) {                return handler;            }        }    }    return null;}
    if (this.handlerMappings != null) {
        // 遍历注册的全部 handlerMapping
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

实际上,在这一步遍历了所有注册的 HandlerMapping,然后委派它们去寻找处理器,如果找到了合适的,就不再往下寻找,直接返回。

同时,HandlerMapping 之间有优先级的概念,根据 mvc 包下 AnnotationDrivenBeanDefinitionParser 的注释:

This class registers the following {@link HandlerMapping HandlerMappings}
@link RequestMappingHandlerMapping
ordered at 0 for mapping requests to annotated controller methods.

说明了 RequestMappingHandlerMapping 的优先级是最高的,优先使用它来寻找适配器。

具体寻找调用的方法:

AbstractHandlerMapping#getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {    // 根据 Request 获取对应的 handler    Object handler = getHandlerInternal(request);    // 将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);    if (hasCorsConfigurationSource(handler)) {        CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);        config = (config != null ? config.combine(handlerConfig) : handlerConfig);        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);    }    return executionChain;}
    // 根据 Request 获取对应的 handler
    Object handler = getHandlerInternal(request);
    // 将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (hasCorsConfigurationSource(handler)) {
        CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        config = (config != null ? config.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }
    return executionChain;
}

(1) getHandlerInternal(request) 函数作用:

根据 request 信息获取对应的 Handler,也就是我们例子中的,通过 URL 找到匹配的 Controller 并返回。

(2) getHandlerExcetionChain 函数作用:

将适应该 URL 对应拦截器 MappedInterceptor 加入 addInterceptor() 到执行链 HandlerExecutionChain 中。

(3) CorsConfiguration

这个参数涉及到跨域设置,具体看下这篇文章:SpringBoot下如何配置实现跨域请求?


寻找适配器 HandlerAdapter

前面已经找到了对应的处理器了,下一步就得找到它对应的适配器

DispatcherServlet#getHandlerAdapter

protected  getHandlerAdapter(Object handler) throws ServletException {    if (this.handlerAdapters != null) {        for (HandlerAdapter adapter : this.handlerAdapters) {            if (adapter.supports(handler)) {                return adapter;            }        }    }}
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
}

同样,HandlerAdapter 之间也有优先级概念,由于第 0 位是 RequestMappingHandlerAdapter,而它的 supports 方法总是返回 true,所以毫无疑问返回了它

......

End


如有收获,请帮忙转发,您的鼓励是作者最大的动力!

640?wx_fmt=png


640?wx_fmt=jpeg

我是本周的小编「沉默王二」,首先要告诉大家一个沉痛的消息:本篇文字超过了 5 万字,微信要二哥删减后再发,我已经把代码截图了很多,但仍然审核不通过,没办法,最后只能忍痛割爱。觉得遗憾的同学可以加入「Java极客技术」知识星球,找作者惊奇索要完整的文章。


另外,「Java极客技术」知识星球还提供以下足够多的福利:


文章有疑问的地方可以提问,其他工作问题都可以提问出来,作者免费作答。

 https://t.zsxq.com/Y3fYny7


每周都有大牛分享一些面试题,和面试注意的知识点!

 https://t.zsxq.com/2bufE2v


每周由Java极客技术独家编制的设计模式与大家分享!

 https://t.zsxq.com/3bUNbEI


每两周还会分享一个话题,和大家一起成长!

 https://t.zsxq.com/BI6Unm2


还有Java极客技术团队亲自录制了一套 Spring Boot 视频,这套视频加密,加密后放到云盘上,下载链接加密之后,一机一码,每个星球的用户一个播放授权码。

 

我们做知识星球的目的和其他星主一样,就是为了帮助大家一起更好的成长,与高手拉近距离,减少差距,其实你也是高手!

用了这么多年 Spring MVC,你真的了解它吗?_第21张图片 用了这么多年 Spring MVC,你真的了解它吗?_第22张图片 用了这么多年 Spring MVC,你真的了解它吗?_第23张图片 用了这么多年 Spring MVC,你真的了解它吗?_第24张图片 用了这么多年 Spring MVC,你真的了解它吗?_第25张图片

1000人,50元/每年,现在大约还剩387名额。

长按二维码


用了这么多年 Spring MVC,你真的了解它吗?_第26张图片


640?wx_fmt=png



欢迎长按下图关注公众号Java极客技术,后台回复“资料”,获取作者独家秘制精品资料

用了这么多年 Spring MVC,你真的了解它吗?_第27张图片

Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专注分享原创、高质量的 Java 文章。如果您觉得我们的文章还不错,请帮忙赞赏、在看、转发支持,鼓励我们分享出更好的文章。



640?

你可能感兴趣的:(用了这么多年 Spring MVC,你真的了解它吗?)