Spring MVC - litter-fish/ReadSource GitHub Wiki

配置web.xml文件

web.xml是Spring用来初始化配置信息的文件,SpringMVC通过servlet拦截所有URL请求来达到控制

<!-- 配置上下文载入器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- ContextLoaderListener 从文件中载入配置-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:spring-all.xml</param-value>
</context-param>
<!-- 配置前端控制器 -->
<servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
        <!-- 从文件中载入配置应用上下文 -->
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

ContextLoaderListener

在启动web容器时,通过ContextLoaderListener自动装配ApplicationContext的配置信息。 ContextLoaderListener 实现了 ServletContextListener 接口,在服务器启动的时候会调用 contextInitialized 方法初始化根web服务器上下文 当服务器被关闭时会执行contextDestroyed方法关闭根web服务器上下文

ContextLoaderListener初始化时序图

初始化根web服务器上下文

// org/springframework/web/context/ContextLoaderListener.java
public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    if (this.contextLoader == null) {
        this.contextLoader = this;
    }
    // 初始化WebApplicationContext
    this.contextLoader.initWebApplicationContext(event.getServletContext());
}

初始化WebApplicationContext

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        // web.xml中存在多次ContextLoader定义
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
        logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // Store context in local instance variable, to guarantee that
        // it is available on ServletContext shutdown.
        if (this.context == null) {
            // 初始化context
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 将上下文context记录在servletContext中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            // 全局变量currentContextPerThread 用于保存类加载器和应用上下文的关系
            currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
        }
        if (logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
        }

        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
        throw err;
    }
}

首先进行校验web.xml文件中只能声明一次ContextLoaderListener, 接着开始创建WebApplicationContext, 接着将创建的context上下文记录在servletContext中,key为WebApplicationContext.class.getName() + ".ROOT", 最后使用全局变量currentContextPerThread 保存类加载器和应用上下文的关系

创建WebApplicationContext

// org/springframework/web/context/ContextLoader.java
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 查找WebApplicationContext的实现类
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 使用反射创建对象
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

获取WebApplicationContext实现类

// org/springframework/web/context/ContextLoader.java
protected Class<?> determineContextClass(ServletContext servletContext) {
    // 获取context-param参数中param-name 为contextClass的param-value
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        // 加载文件ContextLoader.properties获取WebApplicationContext.class.getName()的值
        // org.springframework.web.context.support.XmlWebApplicationContext
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            // 反射创建class
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

首先会从web.xml文件中获取context-param配置中参数param-name 为contextClass的param-value值,如果配置了使用此值通过反射创建class, 如果web.xml未配置则通过加载文件ContextLoader.properties获取WebApplicationContext.class.getName()的默认值

加载文件ContextLoader.properties获取 WebApplicationContext

// org/springframework/web/context/ContextLoader.java
static {
    try {
        // 当前目录下查找ContextLoader.properties文件
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    }
}

上面静态代码加载 ContextLoader.properties 文件,从文件中获取默认的 WebApplicationContext

ContextLoader.properties 文件内容

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

DispatcherServlet

ContextLoaderListener主要是用于创建WebApplicationContext,而DispatcherServlet是真正逻辑的实现地方。 从DispatcherServlet的继承关系中可知其实现了Servlet接口,

Servlet的生命周期

  1. 初始化阶段
  • servlet 容器加载 servlet类,把servlet类的.class文件读到内存中,并创建一个 Servlet 对象
  • servlet 容器创建一个ServletConfig对象,ServletConfig对象包含了servlet的初始化配置信息
  • Servlet 容器会调用init方法进行初始化
  1. 运行阶段 servlet 容器接收到一个请求后,会为这个请求创建 ServletRequest和ServletResponse对象,然后调用 service方法,并把这两个对象当作参数传入, service方法通过ServletRequest获取请求信息,并进行处理。然后通过ServletResponse对象封装请求结果,并返回给客户端。

  2. 销毁阶段 web应用在终止时,servlet容器会先调用servlet对象的destroy方法,然后在销毁servlet对象、同时也会销毁与servlet对象关联的ServletConfig对象

DispatcherServlet 类层次结构

DispatcherServlet

从上图可知DispatcherServlet间接实现了HttpServlet,因此通过init方法进行容器的初始化

init初始化

// org/springframework/web/servlet/HttpServletBean.java
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }

    // Set bean properties from init parameters.
    try {
        // 解析init-param参数
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        // 将当前的servlet类转换成BeanWrapper对象,以便spring 的注入
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
        // 注册自定义属性编辑器,
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
        initBeanWrapper(bw);
        // 属性注入
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

    // Let subclasses do whatever initialization they like.
    // 子类扩展
    initServletBean();

    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

首先会先进行init-param参数封装及验证,验证参数的加入时机???

// org/springframework/web/servlet/HttpServletBean.ServletConfigPropertyValues
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
    throws ServletException {
    // 必要参数
    Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
            new HashSet<String>(requiredProperties) : null;

    Enumeration en = config.getInitParameterNames();
    while (en.hasMoreElements()) {
        String property = (String) en.nextElement();
        Object value = config.getInitParameter(property);
        addPropertyValue(new PropertyValue(property, value));
        if (missingProps != null) {
            missingProps.remove(property);
        }
    }

    // Fail if we are still missing properties.
    if (missingProps != null && missingProps.size() > 0) {
        throw new ServletException(
            "Initialization from ServletConfig for servlet '" + config.getServletName() +
            "' failed; the following required properties were missing: " +
            StringUtils.collectionToDelimitedString(missingProps, ", "));
    }
}

接着将当前的servlet类转换成BeanWrapper对象,以便spring 的注入 然后注册Resource编辑器,当属性注入过程中遇到Resource属性时会使用ResourceEditor编辑器 最后进行ServletBean的初始化,在ContextLoaderListener初始化中只是创建了WebApplicationContext对象,而这里时对其进行初始化

// org/springframework/web/servlet/FrameworkServlet.java
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        // 创建或刷新WebApplicationContext实例
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                elapsedTime + " ms");
    }
}

首先创建或刷新WebApplicationContext实例

// org/springframework/web/servlet/FrameworkServlet.java
protected WebApplicationContext initWebApplicationContext() {
    // rootContext 在ContextLoaderListener的初始化时进行保存
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    // this.webApplicationContext != null 通过这个条件可以确定this.webApplicationContext是通过构造函数进行初始化
    // DispatcherServlet只可以进行一次声明
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        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) {
        // 前面两种还找不到WebApplicationContext则自己创建一个
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // 刷新Spring在web功能实现中所必须使用的全局变量
        onRefresh(wac);
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

上述方法处理逻辑: 首先寻找或创建对应的WebApplicationContext实例

  • 通过构造器的注入进行初始化 DispatcherServlet只可以进行一次声明,因此 可通过this.webApplicationContext != null 这个条件判断this.webApplicationContext是通过构造函数进行初始化的

  • 根据 contextAttribute属性加载WebApplicationContext

// org/springframework/web/servlet/FrameworkServlet.java
protected WebApplicationContext findWebApplicationContext() {
    String attrName = getContextAttribute();
    if (attrName == null) {
        return null;
    }
    WebApplicationContext wac =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    if (wac == null) {
        throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    return wac;
}

上述attrName值为,WebApplicationContext.class.getName() + ".ROOT",在ContextLoaderListener的初始化时进行保存

// org/springframework/web/context/ContextLoader.java
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    // 省略其他代码
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    // 省略其他代码
}
  • 重新创建WebApplicationContext实例
// org/springframework/web/servlet/FrameworkServlet.java
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}

// org/springframework/web/servlet/FrameworkServlet.java
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    // 获取servlet的初始化参数contextClass,如果没有配置则使用默认XmlWebApplicationContext
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName() +
                "' will try to create custom WebApplicationContext context of class '" +
                contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
    }
    // 通过反射实例化contextClass
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 设置父容器,在ContextLoaderListener中进行实例化
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());
    // 初始化Spring环境包括加载配置文件等
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

无论通过那种模式创建WebApplicationContext实例,最后都会通过configureAndRefreshWebApplicationContext进行初始化Spring环境包括加载配置文件等

// org/springframework/web/servlet/FrameworkServlet.java
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            // Generate default id...
            ServletContext sc = getServletContext();
            if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
                // Servlet <= 2.4: resort to name specified in web.xml, if any.
                String servletContextName = sc.getServletContextName();
                if (servletContextName != null) {
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
                            "." + getServletName());
                }
                else {
                    wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
                }
            }
            else {
                // Servlet 2.5's getContextPath available!
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
            }
        }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    // ?????
    applyInitializers(wac);
    // 加载配置文件并整合parent到wac中。
    wac.refresh();
}

加载完配置之后会刷新Spring在web功能实现中所必须使用的全局变量

// org/springframework/web/servlet/DispatcherServlet.java
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

// org/springframework/web/servlet/DispatcherServlet.java
protected void initStrategies(ApplicationContext context) {
    // 初始化MultipartResolver,用于提供文件上传的支持
    initMultipartResolver(context);
    // 初始化LocaleResolver
    initLocaleResolver(context);
    // 初始化ThemeResolver
    initThemeResolver(context);
    // 初始化HandlerMappings
    initHandlerMappings(context);
    // 初始化HandlerAdapters
    initHandlerAdapters(context);
    // 初始化HandlerExceptionResolvers
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    // 初始化FlashMapManager
    initFlashMapManager(context);
}

上面处理的逻辑:

  • 初始化MultipartResolver,用于提供文件上传的支持 在应用上下文中添加 multipart 解析器
<!-- 上传文件拦截,设置最大上传文件大小   10M=10*1024*1024(B)=10485760 bytes -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10485760" />
</bean>

初始化 multipart 解析器

// org/springframework/web/servlet/DispatcherServlet.java
private void initMultipartResolver(ApplicationContext context) {
    try {
        // 要想Spring支持上传功能,需要在配置文件中定义ID为multipartResolver的MultipartResolver类型bean
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
                    "': no multipart request handling provided");
        }
    }
}
  • 初始化LocaleResolver
private void initLocaleResolver(ApplicationContext context) {
    try {
        /**
         * AcceptHeaderLocaleResolver :基于URL的参数配置,通过在URL后面加上locale=zh_CN指定国际化参数
         * SessionLocaleResolver:基于session配置,通过检验用户会话中预置属性来解析
         * CookieLocaleResolver:基于cookie配置,
         *
         */
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
                    "': using default [" + this.localeResolver + "]");
        }
    }
}
  • 初始化ThemeResolver
// 
private void initThemeResolver(ApplicationContext context) {
    try {
        /**
         * FixedThemeResolver:选择一个固定的主题
         * CookieThemeResolver:用于实现用户所选的主题
         * SessionThemeResolver:
         *
         * ThemeChangeInterceptor提供拦截器,根据用户请求来改变主题
         */
        this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" +
                            this.themeResolver + "]");
        }
    }
}
  • 初始化HandlerMappings

初始化 handlerMappings

// org/springframework/web/servlet/DispatcherServlet.java
// 当客户端发起 Request 时 DispatcherServlet 会将Request交给 HandlerMappings ,
// 然后 HandlerMappings 根据 context 的配置来会传给 DispatcherServlet 相应的 Controller
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    // 通过设置web.xml文件中init-param参数决定是否加载容器中的所有HandlerMappings
    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            OrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            // 查找名为handlerMapping的bean,作为当前系统的唯一 HandlerMappings
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    // 如果没有发现 HandlerMappings 则去 DispatcherServlet.properties 文件中查找HandlerMapping作为默认的HandlerMappings
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}
  • 初始化HandlerAdapters

HttpRequestHandlerAdapter:HTTP请求处理器适配器,仅仅只支持对HTTP请求处理器的适配 SimpleControllerHandlerAdapter:简单控制器处理器适配器,将HTTP请求适配到一个控制器的实现进行处理

初始化 HandlerAdapters 解析器

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;
    // 是否加载所有实现 HandlerAdapter 的解析器
    if (this.detectAllHandlerAdapters) {
        // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            OrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }

    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
        }
    }
}
  • 初始化HandlerExceptionResolvers 在应用上下文中添加exception解析器
<bean id="exceptionResolver"
      class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="defaultErrorView" value="error/500"></property>
    <property name="exceptionMappings">
        <props>
            <!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到/WEB-INF/jsp/error_fileupload.jsp页面 -->
            <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">error_fileupload</prop>
            <prop key="java.lang.Throwable">error/500</prop>
        </props>
    </property>
</bean>

初始化exception解析器

// org/springframework/web/servlet/DispatcherServlet.java
private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;
    // 是否加载所有实现HandlerExceptionResolver的解析器
    if (this.detectAllHandlerExceptionResolvers) {
        // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
            // We keep HandlerExceptionResolvers in sorted order.
            OrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
        try {
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

上述代码中通过配置init-param参数detectAllHandlerExceptionResolvers的值为false加载指定的异常解析器 而不是加载所有符合条件的

  • 初始化RequestToViewNameTranslator
  • 初始化ViewResolvers 在应用上下文中添加 View 解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean>

初始化 View 解析器

private void initViewResolvers(ApplicationContext context) {
    this.viewResolvers = null;

    if (this.detectAllViewResolvers) {
        // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
            // We keep ViewResolvers in sorted order.
            OrderComparator.sort(this.viewResolvers);
        }
    }
    else {
        try {
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default ViewResolver later.
        }
    }

    // Ensure we have at least one ViewResolver, by registering
    // a default ViewResolver if no other resolvers are found.
    if (this.viewResolvers == null) {
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
        }
    }
}
  • 初始化FlashMapManager

请求处理的时序图

DispatcherServlet 类层次结构

从图中我们知道DispatcherServlet继承HttpServlet类,并在父父类FrameworkServlet中重写了相应的请求方法

doPost具体逻辑分析

// org/springframework/web/servlet/FrameworkServlet.java
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    // doPost、doGet等请求方法都是委托其处理
    processRequest(request, response);
}

委托请求处理方法

// org/springframework/web/servlet/FrameworkServlet.java
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    // 记录当前时间,用户统计请求处理的时间
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    // 保存当前线程的LocaleContext和RequestAttributes
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // 绑定LocaleContext和RequestAttributes到当前线程
    initContextHolders(request, localeContext, requestAttributes);

    try {
        doService(request, response);
    }
    // 省略异常

    finally {
        // 请求处理后恢复线程到原始状态
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        // 省略日志

        // 请求结束后不论成功或失败都会发布事件通知
        publishRequestHandledEvent(request, startTime, failureCause);
    }
}

上面方法中首先会获取当前时间、从request中获取LocaleContext和RequestAttributes然后将其绑定到当前处理线程中,接着进行请求处理,最后不论请求是否成功处理 都需要将恢复线程到原始状态及发布事件通知。

请求处理前一些必要属性的设置

// org/springframework/web/servlet/DispatcherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
        String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
        logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<String, Object>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    // 给请求设置一些必要的属性
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

    try {
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

首先对request设置一些必要属性,如Spring上下文、localeResolver、themeResolver等 然后进入真正的逻辑处理流程 先看下doDispatch时序图

// org/springframework/web/servlet/DispatcherServlet.java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 如果是 Multipart 则将request转换为 MultipartHttpServletRequest 类型的request
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            // 根据request信息寻找对应的Handler
            mappedHandler = getHandler(processedRequest, false);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                // 反馈没有找到Handler
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // 根据handler找到对应的 HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            // 如果当前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 (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 拦截器preHandle方法的调用
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 真正激活handler并返回视图
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            // 视图名称的转换 - 添加前缀和后缀
            applyDefaultViewName(request, mv);
            // 应用拦截器的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}
  1. 首先进行判断请求是否是文件上传请求,
  2. 然后根据request信息寻找对应的Handler,如果没有找到这边就直接return了,
  3. 根据handler找到对应的 HandlerAdapter,
  4. last-modified支持的检查,
  5. 拦截器preHandle方法的调用,
  6. 真正激活handler并返回视图
  7. 视图名称的转换 - 添加前缀和后缀
  8. 应用拦截器的postHandle方法
  9. 最后处理返回结果

获取HandlerExecutionChain时序图

根据request获取HandlerExecutionChain

// org/springframework/web/servlet/DispatcherServlet.java
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
    return getHandler(request);
}

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // spring 启动时 会将所有映射类型的bean保存到handlerMappings中,
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

委托父类方法获取HandlerExecutionChain

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 根据request获取对应的handler
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        // 如果没有对应request的handler则使用默认的handler
        handler = getDefaultHandler();
    }
    // 走到这说明没有对应的handler,直接return
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    return getHandlerExecutionChain(handler, request);
}

根据request和handler构建handler拦截处理链

// org/springframework/web/servlet/handler/AbstractHandlerMapping.java
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    chain.addInterceptors(getAdaptedInterceptors());

    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
        if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
            chain.addInterceptor(mappedInterceptor.getInterceptor());
        }
    }

    return chain;
}

如果根据request信息没有寻找对应的Handler,此时会向客户端反馈一个handler未找到错误

// org/springframework/web/servlet/DispatcherServlet.java
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
        pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
                "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    response.sendError(HttpServletResponse.SC_NOT_FOUND);
}

如果上一步中找到了合适的handler则根据handler找到对应的 HandlerAdapter

// org/springframework/web/servlet/DispatcherServlet.java
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    // handlerAdapters Spring启动时进行初始化
    for (HandlerAdapter ha : this.handlerAdapters) {
        if (logger.isTraceEnabled()) {
            logger.trace("Testing handler adapter [" + ha + "]");
        }
        if (ha.supports(handler)) {
            return ha;
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

返回HandlerAdapter后,会判断当前request的方法是否是GET、HEAD,如果是则判断last-modified

// org/springframework/web/context/request/ServletWebRequest.java
public boolean checkNotModified(long lastModifiedTimestamp) {
    if (lastModifiedTimestamp >= 0 && !this.notModified &&
            (this.response == null || !this.response.containsHeader(HEADER_LAST_MODIFIED))) {
        long ifModifiedSince = -1;
        try {
            ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
        }
        catch (IllegalArgumentException ex) {
            String headerValue = getRequest().getHeader(HEADER_IF_MODIFIED_SINCE);
            // Possibly an IE 10 style value: "Wed, 09 Apr 2014 09:57:42 GMT; length=13774"
            int separatorIndex = headerValue.indexOf(';');
            if (separatorIndex != -1) {
                String datePart = headerValue.substring(0, separatorIndex);
                try {
                    ifModifiedSince = Date.parse(datePart);
                }
                catch (IllegalArgumentException ex2) {
                    // Giving up
                }
            }
        }
        // ifModifiedSince 为上一次请求返回的服务器最后修改时间
        // lastModifiedTimestamp 服务其现在最后修改时间
        // 如果现在服务器最后修改时间小于上一次
        this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
        if (this.response != null) {
            if (this.notModified && supportsNotModifiedStatus()) {
                this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
            else {
                // 返回304
                this.response.setDateHeader(HEADER_LAST_MODIFIED, lastModifiedTimestamp);
            }
        }
    }
    return this.notModified;
}

如果支持last-modified,服务器会通过将上一次请求返回的最后修改时间再次携带到当前请求头中,并和当前服务器的最后修改时间做比较, 如果当前时间比上一次时间小,说明请求内容未做更改,直接返回304标识,后面逻辑将不再做处理。

如果不支持last-modified或请求内容已经变更,此时会继续请求处理

// org/springframework/web/servlet/HandlerExecutionChain.java
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            // 前处理器如果返回false,则会终止请求的处理
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

首先或取所有拦截器,然后遍历拦截器,调用前置处理逻辑,如果有一个返回了false,则会终止请求的处理流程。 经过前置处理流程后,会调用正在的处理逻辑,以SimpleControllerHandlerAdapter为例进行分析

// org/springframework/web/servlet/mvc/SimpleControllerHandlerAdapter.java
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

    return ((Controller) handler).handleRequest(request, response);
}

委托AbstractController进行处理

// org/springframework/web/servlet/mvc/AbstractController.java
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws Exception {

    // Delegate to WebContentGenerator for checking and preparing.
    // 检测方法是否支持、是否支持session等,及给response设置一些缓存属性
    checkAndPrepare(request, response, this instanceof LastModified);

    // Execute handleRequestInternal in synchronized block if required.
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                return handleRequestInternal(request, response);
            }
        }
    }

    // 调用我们自己实现的业务逻辑
    return handleRequestInternal(request, response);
}

首先进行一些验证和预准备

// org/springframework/web/servlet/support/WebContentGenerator.java
protected final void checkAndPrepare(
        HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified)
        throws ServletException {

    // Check whether we should support the request method.
    // 支持方法的验证
    String method = request.getMethod();
    if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
        throw new HttpRequestMethodNotSupportedException(
                method, StringUtils.toStringArray(this.supportedMethods));
    }

    // Check whether a session is required.
    // session的验证
    if (this.requireSession) {
        if (request.getSession(false) == null) {
            throw new HttpSessionRequiredException("Pre-existing session required but none found");
        }
    }

    // Do declarative cache control.
    // Revalidate if the controller supports last-modified.
    // 缓存控制
    applyCacheSeconds(response, cacheSeconds, lastModified);
}

缓存控制

// org/springframework/web/servlet/support/WebContentGenerator.java
protected final void applyCacheSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
    // 如果设置了缓存过期时间
    if (seconds > 0) {
        cacheForSeconds(response, seconds, mustRevalidate);
    }
    else if (seconds == 0) {
        // 禁止缓存
        preventCaching(response);
    }
    // Leave caching to the client otherwise.
}

// org/springframework/web/servlet/support/WebContentGenerator.java
protected final void cacheForSeconds(HttpServletResponse response, int seconds, boolean mustRevalidate) {
    if (this.useExpiresHeader) {
        // HTTP 1.0 header
        response.setDateHeader(HEADER_EXPIRES, System.currentTimeMillis() + seconds * 1000L);
    }
    if (this.useCacheControlHeader) {
        // HTTP 1.1 header
        String headerValue = "max-age=" + seconds;
        if (mustRevalidate || this.alwaysMustRevalidate) {
            headerValue += ", must-revalidate";
        }
        response.setHeader(HEADER_CACHE_CONTROL, headerValue);
    }
}

// org/springframework/web/servlet/support/WebContentGenerator.java
protected final void preventCaching(HttpServletResponse response) {
    response.setHeader(HEADER_PRAGMA, "no-cache");
    if (this.useExpiresHeader) {
        // HTTP 1.0 header
        response.setDateHeader(HEADER_EXPIRES, 1L);
    }
    if (this.useCacheControlHeader) {
        // HTTP 1.1 header: "no-cache" is the standard value,
        // "no-store" is necessary to prevent caching on FireFox.
        response.setHeader(HEADER_CACHE_CONTROL, "no-cache");
        if (this.useCacheControlNoStore) {
            response.addHeader(HEADER_CACHE_CONTROL, "no-store");
        }
    }
}

根据请求是否设置了过期时间,未找到通过哪里设置????

经过上面处理,请求已经经过了我们自己的逻辑,接着会进行:视图名称的转换,如添加前缀和后缀

// org/springframework/web/servlet/DispatcherServlet.java
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
    if (mv != null && !mv.hasView()) {
        mv.setViewName(getDefaultViewName(request));
    }
}

// org/springframework/web/servlet/DispatcherServlet.java
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
    return this.viewNameTranslator.getViewName(request);
}

viewNameTranslator在容器启动是进行了初始化,如果未提供则使用默认的DefaultRequestToViewNameTranslator进行处理

// org/springframework/web/servlet/view/DefaultRequestToViewNameTranslator.java
public String getViewName(HttpServletRequest request) {
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    return (this.prefix + transformPath(lookupPath) + this.suffix);
}

接着会执行拦截器的postHandle方法,可以对视图做一些修正等操作

// org/springframework/web/servlet/HandlerExecutionChain.java
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

程序走到此时,说明流程处理正常或其中一步抛了异常,不管如何都会进行结果处理 先看下结果流程的时序图 13b89db8c70af4215b51886149f94df2.png

// org/springframework/web/servlet/DispatcherServlet.java
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

    boolean errorView = false;

    // 异常处理
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    // 如果在handler实例的处理中返回来view,那么需要进行页面处理
    if (mv != null && !mv.wasCleared()) {
        // 处理页面跳转
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                    "': assuming HandlerAdapter completed request handling");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // 完成处理激活触发器
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

上面先进行异常处理,然后在处理页面的跳转逻辑

// org/springframework/web/servlet/DispatcherServlet.java
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale = this.localeResolver.resolveLocale(request);
    response.setLocale(locale);

    View view;
    if (mv.isReference()) {
        // We need to resolve the view name.
        // DispatcherServlet根据ModelAndView选择合适的视图来渲染
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isDebugEnabled()) {
        logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    try {
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                    getServletName() + "'", ex);
        }
        throw ex;
    }
}

根据ModelAndView选择合适的视图来渲染

// org/springframework/web/servlet/DispatcherServlet.java
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
        HttpServletRequest request) throws Exception {

    for (ViewResolver viewResolver : this.viewResolvers) {
        // 根据视图名称找视图
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
    return null;
}

遍历视图处理器,然后根据视图名称找视图

// org/springframework/web/servlet/view/AbstractCachingViewResolver.java
public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
        // 不存在缓存直接创建视图
        return createView(viewName, locale);
    }
    else {
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // Ask the subclass to create the View object.
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Cached view [" + cacheKey + "]");
                        }
                    }
                }
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

首先会先判断是否存在缓存,如果不存在则进行创建

// org/springframework/web/servlet/view/UrlBasedViewResolver.java
protected View createView(String viewName, Locale locale) throws Exception {
    // If this resolver is not supposed to handle the given view,
    // return null to pass on to the next resolver in the chain.
    // 当前解析器不支持直接return
    if (!canHandle(viewName, locale)) {
        return null;
    }
    // Check for special "redirect:" prefix.
    // 处理前缀为redirect:XX的情况
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
        RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
        return applyLifecycleMethods(viewName, view);
    }
    // Check for special "forward:" prefix.
    // 处理前缀为forward:XX的情况
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
        return new InternalResourceView(forwardUrl);
    }
    // Else fall back to superclass implementation: calling loadView.
    return super.createView(viewName, locale);
}

上面以UrlBasedViewResolver为例,先后处理redirect:XX的情况、forward:XX的情况

找到View后会进行跳转

// org/springframework/web/servlet/view/AbstractView.java
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
            " and static attributes " + this.staticAttributes);
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    // 准备一些响应头
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, request, response);
}

进行跳转逻辑

// org/springframework/web/servlet/view/InternalResourceView.java
protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // Determine which request handle to expose to the RequestDispatcher.
    HttpServletRequest requestToExpose = getRequestToExpose(request);

    // Expose the model object as request attributes.
    // 将model中的数据以属性的方式设置到request中
    exposeModelAsRequestAttributes(model, requestToExpose);

    // Expose helpers as request attributes, if any.
    exposeHelpers(requestToExpose);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(requestToExpose, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(requestToExpose, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.include(requestToExpose, response);
    }

    else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        exposeForwardRequestAttributes(requestToExpose);
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.forward(requestToExpose, response);
    }
}
⚠️ **GitHub.com Fallback** ⚠️