(八)Spring源码解析:Spring MVC

一、Servlet及上下文的初始化

1.1> DispatcherServlet的初始化

对于Spring MVC来说,最核心的一个类就是DispatcherServlet,它负责请求的行为流转。那么在Servlet的初始化阶段,会调用init()方法进行初始化操作,在DispatcherServlet中并没有去实现init()这个方法,而是由其父类HttpServletBean负责实现的。

public final void init() throws ServletException {
    /** 将Servelt初始化的参数init-param封装到PropertyValues实例对象中 */
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            // 将本身对象封装为BeanWrapper实例对象
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

            // 注册自定义属性编译器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));

            initBeanWrapper(bw); // 空方法
            bw.setPropertyValues(pvs, true); // 执行属性注入操作
        }
        catch (BeansException ex) {throw ex;}
    }

    /** 由子类负责实现具体的初始化行为 */
    initServletBean();
}

1.1.1> new ServletConfigPropertyValues(...) 创建PropertyValues实例对象

init()方法的第一行代码中,就是创建一个ServletConfigPropertyValues类型的实例对象,在该构造函数内部,会尝试获得配置的Servlet中的init-param标签参数,并将其维护到propertyValueList中。维护规则是:如果待插入的PropertyValue实例对象中的name值与propertyValueList中已存在的PropertyValue的name值相同,则可以执行替换或者合并value值必须是实现了Mergeable接口)操作。代码如下所示:

public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException {
    // 指定哪些参数是需要进行参数缺失校验的
    Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ? new HashSet<>(requiredProperties) : null);

    // 获得servlet中<init-param>标签下配置的参数
    Enumeration<String> paramNames = config.getInitParameterNames();
    while (paramNames.hasMoreElements()) {
        String property = paramNames.nextElement();
        Object value = config.getInitParameter(property);
        addPropertyValue(new PropertyValue(property, value));
        if (missingProps != null) missingProps.remove(property); // 每当匹配上参数,就将其从missingProps中移除
    }

    // 如果missingProps不为空,则说明存在缺失的待配置的参数,那么抛出异常
    if (!CollectionUtils.isEmpty(missingProps)) throw new ServletException(...);
}

1.1.2> initServletBean() 初始化servletBean

在类HttpServletBean中,initServletBean()方法是一个空方法,它的目的就是为了让子类去做具体的个性化逻辑实现。那么,在Spring框架中,真正实现这个方法的类其实是FrameworkServlet,在该类中我已经删除掉了无用的日志代码,仅仅剩下两个方法调用。而在其中,initFrameworkServlet()还是一个空方法,也是预留给子类做个性化定制的,在Spring框架中,还没有任何子类去实现它,因此我们就不用去关注这个方法了。而另一个方法initWebApplicationContext(),是用于初始化webApplicationContext的,我们将在1.2章节来详细分析一下它的实现逻辑。源码及注释如下所示:

protected final void initServletBean() throws ServletException {
    try {
        /** 初始化WebApplicationContext */
        this.webApplicationContext = initWebApplicationContext();

         // 空方法,由子类负责实现
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {throw ex;}
}

1.2> WebApplicationContext的初始化

在上面介绍的initWebApplicationContext()方法中,主要的作用就是用来创建或刷新WebApplicationContext实例对servlet功能所使用的变量进行初始化

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    /** 步骤1:如果web应用上下文不是活跃的,则刷新web应用上下文 */ 
    if (this.webApplicationContext != null) { 
        wac = this.webApplicationContext; // webApplicationContext=AnnotationConfigServletWebServerApplicationContext
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            // 如果cwac不是否活跃的,则在如下逻辑中进行激活操作
            if (!cwac.isActive()) {
                // context实例被注入时没有显式的父context -> 设置根应用上下文(如果有的话;可以为null)作为父节点
                if (cwac.getParent() == null) cwac.setParent(rootContext);

                /** 配置及刷新web应用上下文 */
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }

    /** 步骤2:根据contextAttribute属性查找WebApplicationContext实例对象 */
    if (wac == null) wac = findWebApplicationContext();

    /** 步骤3:如果wac还是找不到,则创建一个wac */    
    if (wac == null) wac = createWebApplicationContext(rootContext);

    /**
     * 步骤4:如果没有调用过onRefresh方法,则此处手动触发初始onRefresh操作
     */
    if (!this.refreshEventReceived) { // 判断onRefresh方法是否已经被调用
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }

    // 将上下文(context)发布为servlet上下文的属性
    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

1.2.1> 如果web应用上下文不是活跃的,则刷新web应用上下文

我们是通过调用Web应用上下文的isActive()方法来判断是否是活跃的,那么前提条件必然就是这个Web应用上下文是否就是null的。此处我们使用的是webApplicationContext,那么,webApplicationContext是在何时何处被赋值的呢?当Spring启动的时候,IOC会加载框架中自定义或默认注入的bean,那么当加载DispatcherServlet的时候,会执行到invokeAwareInterfaces(bean)方法中的最后一行if判断,如下所示:

那么当进入到setApplicationContext(...)方法的时候,由于此时webApplicationContext等于null,那么就会进入到if语句内被赋值了。此时的webApplicationContext其实是AnnotationConfigServletWebServerApplicationContext类型的实例对象。代码如下所示:

当我们通过isActive()判断出当前的Web应用上下文是不活跃的,那么我们就需要调用configureAndRefreshWebApplicationContext(cwac)方法来配置和刷新Web应用上下文

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    /** 步骤1:准备工作——初始化wac */
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        if (this.contextId != null) 
            wac.setId(this.contextId);
        else 
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().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);

    /** 步骤2:调用wac的refresh()方法进行IOC初始化 */
    wac.refresh(); 
}

1.2.2> 根据contextAttribute属性查找WebApplicationContext实例对象

在ServletContext中,以ConcurrentHashMap的类型保存着web应用上下文,其中:

【key】servlet中配置的参数contextAttribute
【value】WebApplicationContext实例对象wac

所以,在findWebApplicationContext()方法中,就是通过去ServletContext中查找wac,如果找到则返回,如果没找到,则抛出异常(因为配置了参数contextAttribute,但是却找不到wac,这个是有问题的);当然,如果本来就没有配置参数contextAttribute,直接返回null即可。代码如下所示:

protected WebApplicationContext findWebApplicationContext() {
    // 获得servlet中配置的参数contextAttribute,如果没有配置,直接返回null
    String attrName = getContextAttribute();
    if (attrName == null) return null;

    // 尝试去ServletContext中查找wac,如果找不到,则抛出异常
    WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(),                                                                                     attrName);
    if (wac == null) throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");

    return wac;
}

1.2.3> 如果wac还是找不到,那就创建一个wac

如果通过上面的努力,我们依然没有获得Web应用上下文,那我们就只好通过createWebApplicationContext(...)方法来创建了,这里的创建过程比较简单,主要还是通过configureAndRefreshWebApplicationContext(wac)方法来刷新Web应用上下文,这个方法在1.2.1章节已经介绍过了,此处就不在赘述了,具体代码如下所示:

protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    // 步骤1:获得上下文类,其中:默认contextClass=XmlWebApplicationContext.class
    Class<?> contextClass = getContextClass(); 
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) throw new ApplicationContextException(...);

    // 步骤2:利用工具类创建wac实例对象
    ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) wac.setConfigLocation(configLocation);

    // 步骤3:刷新Web应用上下文(该方法在1.2.1章节中介绍过)
    configureAndRefreshWebApplicationContext(wac);
    return wac;
}

1.2.4> 手动触发初始onRefresh操作

通过上面的一套处理逻辑,我们已经获得了Web应用上下文(WebApplicationContext),那么,此时我们通过refreshEventReceived变量来判断是否onRefresh方法已经被调用过,如果没有被调用过,则此处手动触发调用onRefresh方法。

protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    // 初始化用于处理文件上传的MultipartResolver,即:从IOC中获取名称为“multipartResolver”的bean
    initMultipartResolver(context); 

    // 初始化用于国际化的LocaleResolver(从IOC中获取名称为“localeResolver”的bean)
    initLocaleResolver(context);

    // 初始化用于主题风格的ThemeResolver(从IOC中获取名称为“themeResolver”的bean)
    initThemeResolver(context);

    // 初始化用于处理Request请求及流转的HandlerMapping,根据变量”detectAllHandlerMappings“,有如下3种获取方式:
    //         方式1:获取IOC中所有类型为HandlerMapping的bean集合
    //         方式2:获取名称为“handlerMapping”的bean
    //         方式3:获取DispatcherServlet.properties文件中配置的HandlerMapping的bean列表
    initHandlerMappings(context);

    // 初始化用于进行请求处理的HandlerAdapter,根据变量”detectAllHandlerAdapters“,有如下3种获取方式:
    //         方式1:获取IOC中所有类型为HandlerAdapter的bean集合
    //         方式2:获取名称为“handlerAdapter”的bean
    //         方式3:获取DispatcherServlet.properties文件中配置的HandlerAdapter的bean列表
    initHandlerAdapters(context);

    // 初始化用于对异常的类型进行处理并生成ModelAndView的HandlerExceptionResolver,根据变量”detectAllHandlerExceptionResolvers“,有如下3种获取方式:
    //         方式1:获取IOC中所有类型为HandlerExceptionResolver的bean集合
    //         方式2:获取名称为“handlerExceptionResolver”的bean
    //         方式3:获取DispatcherServlet.properties文件中配置的HandlerExceptionResolver的bean列表
    initHandlerExceptionResolvers(context);

    // 初始化用于获取“逻辑视图名称”的RequestToViewNameTranslator(从IOC中获取名称为“viewNameTranslator”的bean)
    initRequestToViewNameTranslator(context);

    // 初始化用于创建View对象的ViewResolver,根据变量”viewResolver“,有如下3种获取方式:
    //         方式1:获取IOC中所有类型为ViewResolver的bean集合
    //         方式2:获取名称为“viewResolver”的bean
    //         方式3:获取DispatcherServlet.properties文件中配置的ViewResolver的bean列表
    initViewResolvers(context);

    // 初始化用于管理FlashMap的FlashMapManager(从IOC中获取名称为“flashMapManager”的bean)
    initFlashMapManager(context);
}

在上面的代码中,我们可以看到,有的初始化很简单,就是从IOC中获取指定的bean,而有些方法却比较复杂,下面我们以initHandlerMappings(context)为例,具体分析一下这种复杂初始化执行过程,从如下代码中可以看出这种复杂初始化过程一共执行了3个步骤:

步骤1】获取IOC中所有类型为HandlerMapping的bean集合
步骤2】获取名称为“handlerMapping”的bean
步骤3】获取DispatcherServlet.properties文件中配置的HandlerMapping的bean列表

其中,步骤1和步骤2比较容易理解,我们就不再做解析了(用白色框框住),对于步骤3的getDefaultStrategies(...)方法来说(用红色框框住),我们下面部分就深入分析一下具体的处理过程:

getDefaultStrategies(...)方法中,我们可以看到DEFAULT_STRATEGIES_PATH="DispatcherServlet.properties",即:我们需要读取DispatcherServlet.properties文件中配置的信息,在该文件中,以key和value的方式保存了针对key接口的value实现类列表,所以,获得了实现类列表后,自然需要通过反射将其转换为实例对象集合,然后返回出去。代码及注释如下所示:

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    if (defaultStrategies == null) {
        try {
            // DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {throw new IllegalStateException(...);}
    }

    String key = strategyInterface.getName(); 
    String value = defaultStrategies.getProperty(key); // eg:key=org.springframework.web.servlet.HandlerMapping

    // eg:value="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
    //               org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,
    //               org.springframework.web.servlet.function.support.RouterFunctionMapping"
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            try {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz); // 创建bean实例
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {throw new BeanInitializationException(...);}
            catch (LinkageError err) {throw new BeanInitializationException(...);}
        }
        return strategies;
    }
    else return Collections.emptyList(); // 否则,返回空集合
}

此处为了方便大家理解,我将DispatcherServlet.properties文件也展示出来了,其中红框就是我们解析HandlerMapping时需要获取的配置信息。

好了,解决了简单初始化和复杂初始化之后,我们在用如下的一系列篇幅来了解一下,以上的这些初始化类都是负责做什么的?

a> MultipartResolver

MultipartResolver主要是用来处理文件上传功能。如果我们需要这个功能,我们可以在Spring配置中添加multipart解析器,这样,每个请求都会被检查是否包含multipart,如果包含的话,我们在Spring上下文中定义的MultipartResolver就会对其进行解析操作,配置MultipartResolver如下所示:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="100000"/>
</bean>
b> LocaleResolver

LocaleResolver负责国际化配置。即:通过某种方式,我们可以让自己的系统以不同的语言文字进行转换。我们常用的有如下3种LocaleResolver:

AcceptHeaderLocaleResolver

说明】可以通过对URL参数进行解析,来控制国际化(在请求中附带locale=zh_CNlocale=en_US这种)
配置方式<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"/>

SessionLocaleResolver

说明】根据用户本次会话过程中的语言设定决定语言种类,如果该会话属性不存在,那么它会根据accept-language HTTP头部确定默认区域
配置方式<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>

CookieLocaleResolver

说明】于通过浏览器的cookie设置取得Locale对象。
配置方式<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

c> ThemeResolver

通过多套主题对应多套静态资源,来实现系统不同的主题界面,从而提升用户使用系统的视觉效果。

ThemeSource主题资源的接口,其默认实现为ResourceBundleThemeSource,我们可以通过如下方式进行自定义配置

<bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource">
    <property name="basenamePrefix" value="com.muse." />   <!-- 指定应用去com.muse路径下寻找资源文件 -->
</bean>

ThemeResolver 是主题解析器的接口,用于决定哪些用户使用哪些主题。常用如下3个实现类:

FixedThemeResolver用于选择一个固定的主题,配置方式如下所示:

<bean id="themeResolver" class="org.springframework.web.servlet.theme.FixedThemeResolver">
    <property name="defaultThemeName" value="muse" />
</bean>

CookieThemeResolver用于实现用户所选的主题, 以cookie的形式存放在客户端的机器上,配置方式如下所示:

<bean id="themeResolver" class="org.springframework.web.servlet.theme.CookieThemeResolver">
    <property name="defaultThemeName" value="muse" />
</bean>

SessionThemeResolver用于将主题保存在用户的Session中,配置方式如下所示:

<bean id="themeResolver" class="org.springframework.web.servlet.theme.SessionThemeResolver">
    <property name="defaultThemeName" value="muse" />
</bean>
d> HandlerMapping

当请求request发送到DispatcherServlet时,它会将其提交给HandlerMapping,然后HandlerMapping再将根据Web应用上下文的配置来请求到相应的Controller。我们可以通过设置detectAllHandlerMappings变量的值,来决定是否加载全部的HandlerMapping类型的bean,默认是true,即全都加载;但是我们也可以将其设置为false,即只加载bean名称为handlerMapping的bean,具体设置方式如下所示:

<servlet>
    ... ...
    <init-param>
        <param-name>detectAllHandlerMappings</param-name>
        <param-value>false</param-value>
    </init-param>
    ... ...
</servlet>
e> HandlerAdapter

当request请求发送到DispatcherServlet之后,会遍历所有HandlerMapping的集合,通过getHandler(request)方法来获取HandlerExecutionChain类型的实例对象handler,再通过调用HandlerExecutionChain的getHandler()方法来获得Object对象,然后将其传入到HandlerAdapter类的supports(...)方法中,来判断哪一个HandlerAdapter实现类可以处理这个Object对象,如果找到了对应的HandlerAdapter实现类,则通过该实现类的handle(request, response, handler)方法来进行请求的后续处理。

对于上段文字介绍的“会遍历所有HandlerMapping的集合”,关于这个HandlerMapping的集合,我们可以通过设置detectAllHandlerAdapters变量的值,来决定是否加载全部的HandlerMapping类型的bean,默认是true,即全都加载;但是我们也可以将其设置为false,即只加载bean名称为handlerAdapter的bean,具体设置方式如下所示:

<servlet>
    ... ...
    <init-param>
        <param-name>detectAllHandlerAdapters</param-name>
        <param-value>false</param-value>
    </init-param>
    ... ...
</servlet>
f> HandlerExceptionResolver

HandlerExceptionResolver接口只提供了一个方法,即:resolveException(...),任意实现了该接口的实现类,都可以在这个方法内部进行判断,如果符合条件,则生成相应的ModelAndView实例对象,如果不满足判断条件,则返回null即可。

public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request, 
                                  HttpServletResponse response, 
                                  Object handler, 
                                  Exception ex);
}

那么,在DispatcherServlet类的processHandlerException(...)方法中,如果我们配置了handlerExceptionResolvers变量,则轮询每一个HandlerExceptionResolver实例对象,调用其resolveException(...)方法,如果返回的ModelAndView不为空,则直接跳出循环即可。代码如下所示:

g> RequestToViewNameTranslator

当Controller没有返回一个View对象或者逻辑视图名称,并且在该方法中没有直接往response的OutputStream里面写数据,那么Spring就会采用约定好的方式提供一个逻辑视图名,那么这个逻辑视图名就是通过调用RequestToViewNameTranslator接口的getViewName(request)来获得的,下面我们来看一下这个接口的源码内容:

public interface RequestToViewNameTranslator {
    String getViewName(HttpServletRequest request) throws Exception;
}

那么,什么时候会调用获取默认视图名称呢?我们可以在DispatcherServletdoDispatch(...)方法中找到蛛丝马迹。我们可以发现,在applyDefaultViewName(...)方法中,试图去获取默认视图名称,那么条件就是mv不为null并且mv中没有视图(View),那么此时,我们就可以通过调用RequestToViewNameTranslator接口的getViewName(request)方法来获得默认视图名称了,代码如下所示:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ... ...
    try {
        ... ...
        try {
            ... ...
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            ... ...
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            ... ...
            /** 尝试获得默认的视图名称(ViewName) */
            applyDefaultViewName(processedRequest, mv);
            ... ...
        }
        catch (Exception ex) {...}
        catch (Throwable err) {...}
        ... ...
    }
    catch (Exception ex) {...}
    catch (Throwable err) {...}
    finally {...}
}

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    // 如果无法获得视图,则获取默认的视图名称
    if (mv != null && !mv.hasView()) {
        String defaultViewName = getDefaultViewName(request); /** 获得默认的视图名称 */
        if (defaultViewName != null) {
            mv.setViewName(defaultViewName);
        }
    }
}

private RequestToViewNameTranslator viewNameTranslator;
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
    // 调用RequestToViewNameTranslator的getViewName(request)方法获得默认视图名称
    return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}
h> ViewResolver

在SpringMVC中,当Controller将请求处理结果放入到Mode!AndView中以后,DispatcherServlet会根据Mode!AndView选择合适的视图View进行渲染,那么这个View的获取工作就是在ViewResolver接口中实现的,它提供了一个resolveViewName(viewName, locale)方法,通过该方法来返回对应的VIew视图,接口如下所示:

public interface ViewResolver {
    // 通过视图名称和国际化配置,返回试图View
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

我们也可以对ViewResolver的实现类进行自定义配置,配置方式如下所示:

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

当我们使用重定向的时候,可以将请求属性和路径存储到FlashMap中,然后重定向之后,再从FlashMap中进行获取,也可以对其进行清空删除操作。而FlashMapManager用于存储、检索、管理FlashMap实例。

二、DispatcherServlet的逻辑处理

为了便于理解后续debug过程中所展示的数据,我们执行Http GET请求——http://localhost:8888/hello

上面我们介绍完Web应用上下文的初始化操作后,本章我们将针对Http请求进行分析,那么我们常用的就是Post方式和Get方式,下面我们分别来看一下负责这两个请求方式处理的doPost(...)doGet(...)方法,代码如下所示:

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

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

DispatcherServlet没有实现这两个方法,而是由FrameworkServlet类负责实现具体处理逻辑,在doGet和doPost方法中都调用了processRequest(request, response)方法,那么,下面我们就来针对这个方法进行深度解析。代码如下所示:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    // 针对【国际化上下文】,获得当前的LocaleContext和最新request请求中的LocaleContext
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); 
    LocaleContext localeContext = buildLocaleContext(request);

    // 针对【请求参数】,获得当前的RequestAttributes和最新request请求中的RequestAttributes
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    // 初始化【异步请求管理器】
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    // 将request中最新的“国际化上下文”和“请求参数”设置到当前线程的上下文中
    initContextHolders(request, localeContext, requestAttributes);

    try {
        /** 处理请求 */
        doService(request, response); 
    }
    catch (ServletException | IOException ex) {...}
    catch (Throwable ex) {...}
    finally {
        // 还原以前的国际化上下文和请求参数设置到当前线程的上下文中
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) requestAttributes.requestCompleted();

        // 无论成功与否,都会发布ServletRequestHandledEvent事件
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

processRequest(request, response)方法中,主要执行了如下步骤:
步骤1】获得当前最新request中的国际化上下文LocaleContext
步骤2】获得当前最新request中的请求参数RequestAttributes
步骤3】初始化异步请求管理器WebAsyncManager
步骤4】将最新request中的“国际化上下文”和“请求参数”设置到当前线程的上下文中;
步骤5】处理Http请求;
步骤6】将当前线程上下文中的“国际化上下文”和“请求参数”还原为之前的值;
步骤7】无论成功与否,都会发布ServletRequestHandledEvent事件;

在上面的7个步骤中,最关键的就是“步骤5”了,那么下面我们来分析一下该步骤的执行方法doService(request, response)

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    Map<String, Object> attributesSnapshot = null;

    /** 【步骤1】在include请求时保存请求属性的快照,以便在include请求后恢复原始属性 */
    if (WebUtils.isIncludeRequest(request)) { // 确定给定的请求是否是一个include请求(判断方法:request中是否存在“javax.servlet.include.request_uri”的属性参数)
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            // DEFAULT_STRATEGIES_PREFIX="org.springframework.web.servlet"
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) 
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
    }

    /** 【步骤2】准备工作,为request附加更多属性 */
    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());
    if (this.flashMapManager != null) {
        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);
    }
    RequestPath previousRequestPath = null;
    if (this.parseRequestPath) {
        previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
        ServletRequestPathUtils.parseAndCache(request); // request.setAttribute(PATH_ATTRIBUTE, requestPath);
    }

    /** 【步骤3】处理Http请求 */
    try {
        doDispatch(request, response);
    }
    finally {
        /** 【步骤4】收尾工作 */
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            if (attributesSnapshot != null) 
                restoreAttributesAfterInclude(request, attributesSnapshot); // include请求后恢复原始属性
        }
        if (this.parseRequestPath) 
            ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); // 还原请求参数中PATH_ATTRIBUTE的值
    }
}

在如上的处理逻辑中,我们可以看到doDispatch(request, response)方法才是最核心的处理Http请求的方法,而其他的方法无外乎是处理请求之前的准备操作以及处理完毕后的收尾阶段,那么下面我们来分析一下这个方法:

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 {
            //【步骤1】如果是Multipart请求,则将request转换为MultipartHttpServletRequest类型 */
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            /**【步骤2】根据request,寻找匹配的Handler */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response); // 如果没找到,则抛出异常或者返回404
                return;
            }

            /**【步骤3】根据Handler,寻找匹配的HandlerAdapter */
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) return;
            }

            /**【步骤4】调用已注册的拦截器列表interceptors的preHandle方法 */
            if (!mappedHandler.applyPreHandle(processedRequest, response)) return;

            /**【步骤5】处理请求的逻辑并返回ModelAndView */
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) return;

            //【步骤6】如果没有视图View,则向mv中设置默认的视图名称
            applyDefaultViewName(processedRequest, mv);

            //【步骤7】调用已注册的拦截器列表interceptors的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {...}
        catch (Throwable err) {...}

        /**【步骤8】调用handler以及处理返回的结果,该结果要么是ModelAndView,要么就是一个需要解析到ModelAndView的异常 */
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {...}
    catch (Throwable err) {...}
    finally {...}
}

2.1> 根据request信息寻找对应的Handler

下面我们来看一下getHandler(request)方法,默认的handlerMappings包含下图中的5个实例对象,Spring会按照其先后顺序,依次调用mapping的getHander(request)方法,如果返回的handler不为null,则直接返回handler,不再进行后续的对比操作了。代码如下所述:

那么getHander(request)方法是如何通过request来生成HandlerExecutionChain的呢?

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    /** 2.1.1 试图从request请求中获取handler实例 **/
    Object handler = getHandlerInternal(request);
    if (handler == null) handler = getDefaultHandler(); // 如果获取不到,则获取默认handler,
    if (handler == null) return null; // 如果仍然获取不到,则直接返回null

    // 如果上面获取到的header是字符串类型的beanName,则从IOC中获取到对应的bean
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    // 确保拦截器等的缓存查找路径(lookupPath)的存在
    if (!ServletRequestPathUtils.hasCachedPath(request)) 
        initLookupPath(request);

    /** 2.1.2 将配置中对应的拦截器加入到执行链中,以确保拦截器生效 */
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    // 针对CORS(跨域)请求,进行特殊处理
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = getCorsConfiguration(handler, request);
        if (getCorsConfigurationSource() != null) {
            CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
            config = (globalConfig != null ? globalConfig.combine(config) : config);
        }
        if (config != null) {
            config.validateAllowCredentials();
        }
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

2.1.1> getHandlerInternal(request)

getHandlerInternal(request)方法中,试图从request请求中获取handler实例,它内部并没有做什么复杂的逻辑,只是又将处理的权限给了它的超类AbstractHandlerMethodMappinggetHandlerInternal(request)方法了,代码如下所示:

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    try {
        return super.getHandlerInternal(request); // 调用AbstractHandlerMethodMapping的getHandlerInternal方法
    }
    finally {
        ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}

AbstractHandlerMethodMapping类的getHandlerInternal(request)方法中,主要执行了两件事,其一:从request中寻找请求路径lookupPath;其二:通过lookupPath和request,获得请求待流转的方法handlerMethod

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    /**【步骤1】从request中寻找请求路径 */
    String lookupPath = initLookupPath(request); // eg: lookupPath="/hello"
    this.mappingRegistry.acquireReadLock();
    try {
        /**【步骤2】通过lookupPath和request,获得请求待流转的方法handlerMethod */
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        this.mappingRegistry.releaseReadLock();
    }
}

我们以http://localhost:8888/hello为例,执行debug操作,解析出的lookupPath为"/hello",如下图所示:

在步骤1中,我们获得了请求路径lookupPath,那么在通过request我们就可以获得需要将请求流转到的方法上面了。具体步骤是,首先:我们通过lookupPath来寻找所有匹配的方法,并将其通过一系列封装为Match实例对象,保存到matches列表中。其次:如果这个列表中不仅仅有一个匹配方法,则进行特殊处理或者抛出异常。如果仅仅有一个匹配的方法,就通过Match实例对象的getHandlerMethod()方法,来获得匹配这次Http请求待处理的类的方法。代码如下所示:

// eg: lookupPath="/hello"
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    /**【步骤1】根据请求路径lookupPath来获得可以匹配处理的方法列表  */
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);

    /**【步骤2】从directPathMatches获取匹配的Match实例对象,并保存到matches集合中 */
    if (directPathMatches != null) 
         // 根据request对象,解析出http请求method、参数params,请求headers等信息来创建RequestMappingInfo对象,
         // 然后封装到Match对象中,并保存到matches中 
        addMatchingMappings(directPathMatches, matches, request);

    if (matches.isEmpty()) 
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);

    /**【步骤3】针对已匹配的Match集合对象进行处理 */
    if (!matches.isEmpty()) {
        Match bestMatch = matches.get(0);
        // 发现匹配上了两个相同的method,则进行特殊处理或者抛出异常
        if (matches.size() > 1) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            bestMatch = matches.get(0);

            if (CorsUtils.isPreFlightRequest(request)) 
                for (Match match : matches) 
                    if (match.hasCorsConfig()) return PREFLIGHT_AMBIGUOUS_MATCH;                
            else {
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.getHandlerMethod().getMethod();
                    Method m2 = secondBestMatch.getHandlerMethod().getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
        }
        // eg: bestMatch.getHandlerMethod()=com.muse.springbootdemo.controller.DemoController#hello()
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
        handleMatch(bestMatch.mapping, lookupPath, request); // 对request进行附加属性赋值
        return bestMatch.getHandlerMethod(); // 返回com.muse.springbootdemo.controller.DemoController#hello()
    }

    /**【步骤4】没有找到已匹配的Match集合对象,则进行异常抛出等操作 */
    else return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}

getMappingsByDirectPath(...)方法中,就是以上面解析出来的请求路径urlPath作为key,去pathLookup中获取value值;那么以本例来说,pathLookup变量中一共保存了7个值,分别是:"/hello", "/mapTest", "/getOrder", "/teacher", "/success", "/student", "/error",获取到value值,返回即可,代码如下所示:

// eg:urlPath="/hello"
public List<T> getMappingsByDirectPath(String urlPath) {
    return this.pathLookup.get(urlPath); // eg:return {GET [/hello]}
}

2.1.2> getHandlerExecutionChain(handler, request)

将配置中对应的拦截器加入到执行链中,以确保拦截器生效。首先,如果handler是HandlerExecutionChain类型,则强转为HandlerExecutionChain类型对象chain;否则,创建一个HandlerExecutionChain实例对象chain,并将handler包装其中;其次,遍历adaptedInterceptors,将符合条件的拦截器加入到chain中,以请求http://localhost:8888/hello为例,adaptedInterceptors={ConversionServiceExposingInterceptor@7409ResourceUrlProviderExposingInterceptor@7410} ,代码如下所示:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    //【步骤1】要么强转HandlerExecutionChain类型,要么新建HandlerExecutionChain实例对象
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    //【步骤2】遍历adaptedInterceptors,将符合条件的拦截器加入到chain中
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(request)) 
                chain.addInterceptor(mappedInterceptor.getInterceptor());
        }
        else chain.addInterceptor(interceptor);
    }
    return chain;
}

其实通过上面的逻辑,我们就可以看得出来,HandlerExecutionChain类就是包含着两部分内容:

Object handler】真正处理请求的headler;
ListinterceptorList】interceptor拦截器列表;

当我们想要实现一个自定义的请求拦截器时,可以通过实现HandlerInterceptor接口的方式,这个接口一共有3个方法,分别是针对处理请求之前进行拦截操作的preHandle方法,以及针对处理请求之后进行拦截操作的postHandle方法,和在所有请求处理完毕之后进行额外操作afterCompletion方法,代码如下所示:

public interface HandlerInterceptor {
    // 在处理请求之前进行额外操作
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        return true;
    }

    // 在处理请求之后进行额外操作
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {}

    // 在所有请求处理完毕之后进行额外操作
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {}

}

2.2> 根据当前Handler寻找对应的HandlerAdapter

getHandlerAdapter(...)方法中,我们会通过逐一遍历根据handlerAdapters集合,通过调用HandlerAdapter的supports(handler)方法,来获取与入参handler相匹配的HandlerAdapter,只要找到了匹配的HandlerAdapter,直接返回即可,不再继续遍历对比。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) 
        for (HandlerAdapter adapter : this.handlerAdapters) 
            if (adapter.supports(handler)) // 寻找到匹配的adapter
                return adapter; // eg: RequestMappingHandlerAdapter@7511

    // 如果没有找到HandlerAdapter,则抛出异常
    throw new ServletException(...);
}

handlerAdapters默认包含4个HandlerAdapter,分别是:

  • RequestMappingHandlerAdapter

  • HandlerFunctionAdapter

  • HttpRequestHandlerAdapter

  • SimpleControllerHandlerAdapter

HandlerAdapter接口中,主要有两个方法需要被实现,其一是supports方法,用于判断是否匹配该HandlerAdapter实现类;其二,handle方法,用于真正处理Http请求的方法。代码如下所示:

public interface HandlerAdapter {
    // 是否匹配该HandlerAdapter
    boolean supports(Object handler);

    // 处理请求,返回结果ModelAndView
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    // 用于请求缓存,已作废
    @Deprecated
    long getLastModified(HttpServletRequest request, Object handler);
}

2.3> 逻辑处理

上面其实大多还都是请求处理前的准备操作,那么真正处理请求的就是handle(request, response, handler)这个方法了,下面我们就针对这个方法来进行深度解析,代码如下所示:

public final ModelAndView handle(HttpServletRequest request, 
                                 HttpServletResponse response, 
                                 Object handler) throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
}

在handler方法中,只是将处理权利移交给了handleInternal(...)方法,代码如下所示:

在该方法中,主要是执行了两个步骤:
步骤1】调用invokeHandlerMethod(request, response, handlerMethod)方法真正的执行请求处理;
步骤2】对于Header不包含 “Cache-Control”的情况进行特殊处理。

2.3.1> invokeHandlerMethod(...)方法

从上面的代码中,我们可以看到无论是不是采用Session会话,最终都会调用一个方法,即:invokeHandlerMethod(request, response, handlerMethod),通过该方法,我们可以获得请求解析后的ModelAndView实例对象,那么,我们就把视野关注到这个方法上。在该方法中,代码量还是蛮大的,但是其实可以总体的将其分为3大部分:

第1部分】进行实例对象的创建及赋值操作,包含:webRequestbinderFactorymodelFactoryinvocableMethod、……
第2部分】通过invocableMethod.invokeAndHandle(webRequest, mavContainer)方法,进行请求处理;
第3部分】通过getModelAndView(mavContainer, modelFactory, webRequest)方法,将执行结果封装为ModelAndView实例对象;

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, 
                                           HttpServletResponse response,
                                           HandlerMethod handlerMethod) throws Exception {
    // 创建webRequest
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 创建binderFactory和modelFactory
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        //  创建及设置invocableMethod
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) 
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        if (this.returnValueHandlers != null) 
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        // 创建及设置mavContainer
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 创建及设置asyncWebRequest
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // 创建及设置asyncManager
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }

        /** 处理请求,实际执行逻辑的地方 */
        invocableMethod.invokeAndHandle(webRequest, mavContainer);

        if (asyncManager.isConcurrentHandlingStarted()) return null;

        // 处理请求返回结果,获得ModelAndView
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

invokeAndHandle(...)方法中,是真正要进行请求处理的地方了,这个方法主要是通过调用invokeForRequest(webRequest, mavContainer, providedArgs)方法来进行反射调用。代码如下所示:

public void invokeAndHandle(ServletWebRequest webRequest, 
                            ModelAndViewContainer mavContainer,
                            Object... providedArgs) throws Exception {
    /** 通过反射请求到具体的Controller上,并获得返回值 */
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

    // 根据ResponseStatus注释设置响应状态
    setResponseStatus(webRequest);

    // 如果没有返回值
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    // 如果存在返回相关的响应状态原因
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
    mavContainer.setRequestHandled(false);

    /** 针对利用HandlerMethodReturnValueHandler的handleReturnValue方法,对返回值进行处理 */
    try {
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer,  webRequest);
    } catch (Exception ex) {throw ex;}
}

/**
 * 根据ResponseStatus注释设置响应状态
 */
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
    // 获得请求响应状态,如果为null,则直接返回
    HttpStatus status = getResponseStatus();
    if (status == null) return;

    // 获得请求响应response,尝试为response设置失败信息(response.sendError)或者状态码(response.setStatus)
    HttpServletResponse response = webRequest.getResponse();
    if (response != null) {
        String reason = getResponseStatusReason();
        if (StringUtils.hasText(reason)) response.sendError(status.value(), reason);
        else response.setStatus(status.value());
    }

    // 被RedirectView获取
    webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
a> invokeForRequest(...)

invokeForRequest(...)方法中,我们会首先对request请求中的参数进行解析,转换为方法的入参args,然后再采用反射的方式调用Controller类的所对应的处理方法,获得最终处理后的结果,代码如下所示:

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    /** 解析出请求的入参 **/
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

    /** 利用反射,调用Controller类的所对应的处理方法 */
    return doInvoke(args);
}

/**
 * 请求参数解析
 */
protected Object[] getMethodArgumentValues(NativeWebRequest request, 
                                           ModelAndViewContainer mavContainer,
                                           Object... providedArgs) throws Exception {
    // 获得http请求中的参数列表,如果没有入参,则直接返回空的入参数组
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) return EMPTY_ARGS;

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);

        // 如果参数属于providedArgs类型,则跳过,不进行解析
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) continue;

        /** 如果所有resolver解析器都不能解析的话,则直接抛出异常 */
        if (!this.resolvers.supportsParameter(parameter)) throw new IllegalStateException(...);

        /** 进行参数解析操作 */
        try {
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        } catch (Exception ex) {throw ex;}
    }
    return args;
}

/**
 * 通过反射,执行逻辑调用
 */
protected Object doInvoke(Object... args) throws Exception {
    // 获得被桥接的方法,即:用户自定义的方法
    Method method = getBridgedMethod();
    try {
        if (KotlinDetector.isSuspendingFunction(method)) 
            return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);

        // 利用反射,调用Controller类中相应的method方法
        return method.invoke(getBean(), args);
    }
    catch (IllegalArgumentException ex) {...}
    catch (InvocationTargetException ex) {...}
}

通过supportsParameter(parameter)方法我们可以确定这个入参我们的解析器是否支持解析,那么我们先不着急解析这个方法,下来看一下具体实现:

public boolean supportsParameter(MethodParameter parameter) {
    // 获取方法参数解析器
    return getArgumentResolver(parameter) != null;
}

那么resolveArgument(...)方法用于对参数进行解析的,大家可以在如下的源码中发现,它内部其实一上来就执行了getArgumentResolver(parameter)操作,这个与supportsParameter(parameter)方法是一样的,即:它内部其实做了两件事,其一:是判断parameter参数是否有解析器可以对其进行解析;其二:如果有,则通过该解析器的resolveArgument(...)方法进行参数解析操作:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    //【步骤1】获取方法参数解析器
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) 
        throw new IllegalArgumentException("Unsupported parameter type ...");

    //【步骤2】执行解析操作
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

/**
 * 获得可以解析parameter参数的方法参数解析器(HandlerMethodArgumentResolver)
 */
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 如果之前解析过,则直接从缓存中获取
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);

    if (result == null) {
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            if (resolver.supportsParameter(parameter)) { // supportsParameter方法是由子类实现的
                result = resolver;
                this.argumentResolverCache.put(parameter, result); // 保存到缓存中
                break;
            }
        }
    }
    return result; 
}

默认来说,resolvers中是包含 27 个方法参数解析器的,Spring会从头依次遍历每个解析器的 supportsParameter(parameter) 方法来寻找可以解析入参parameter的具体解析器实现类resolver,如果找到了,再调用该resolver的 resolveArgument(...) 方法,来对parameter参数进行解析操作。如下是默认的27个方法参数解析器:

b> handleReturnValue(...)

handleReturnValue(...)方法是用来对请求返回的结果进行额为处理,它的处理方式与我们上面寻找HandlerMethodArgumentResolver也非常类似的,主要执行两个步骤,【步骤1】寻找可以对结果进行处理的handler实例对象;【步骤2】调用handler的handleReturnValue(...)方法来进行结果的处理。代码如下所示:

public void handleReturnValue(Object returnValue, 
                              MethodParameter returnType,
                              ModelAndViewContainer mavContainer, 
                              NativeWebRequest webRequest) throws Exception {
    /** 选择可以对结果进行解析的解析器 */
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) throw new IllegalArgumentException("Unknown return value type: " + ...);

    /** 具体解析操作,由子类负责 */
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

/** 
 * 选择可以对结果进行解析的解析器
 */
private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) 
            continue;
        if (handler.supportsReturnType(returnType)) // 由子类负责实现supportsReturnType方法
            return handler;
    }
    return null;
}

那么,默认的HandlerMethodReturnValueHandler解析器一共有15个,Spring会从头开始遍历这些handler,如果找到匹配了,则直接返回,不用再遍历对比其他handler了。具体如下所示:

2.4> 渲染给定的ModelAndView

processDispatchResult(...)方法是用来针对上述调用结果进行处理的,要么是一个ModelAndView,要么是一个需要解析到ModelAndView的异常。代码如下所示:

private void processDispatchResult(HttpServletRequest request,
                                   HttpServletResponse response,
                                   HandlerExecutionChain mappedHandler, 
                                   ModelAndView mv,
                                   Exception exception) throws Exception {
    boolean errorView = false;

    //【步骤1】如果出现了异常,则进行异常处理
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) 
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    //【步骤2】如果存在mv,则对mv进行渲染操作
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response); /** 执行页面渲染操作 */
        if (errorView) WebUtils.clearErrorRequestAttributes(request);
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) return;

    if (mappedHandler != null) 
        mappedHandler.triggerAfterCompletion(request, response, null);
}

当我们在第一步中获得了ModelAndView的实例对象mv之后,就可以对其进行渲染操作了,具体操作是在render(mv, request, response)这部分的代码中,在这段代码中,主要执行了两件事情,其一:尝试获得View实例对象;其二:针对view对象,执行页面渲染,代码如下所示:

2.4.1> resolveViewName(...)

Spring通过resolveViewName(...)方法来创建view视图对象,并将其加入到IOC中,其具体实现方式还是遍历每一个视图解析器(ViewResolver),调用其resolverViewName(viewName, locale)方法,尝试获得View视图实例对象,如果获得到了,则直接返回,不需要继续遍历其他的视图解析器了,代码如下所示:

每个视图解析器(ViewResolver)的实现子类都需要自定义实现resolveViewName(viewName,locale)方法,通过该方法来创建view视图对象,并将其加入到IOC中,代码如下所示:

Spring通过createView(viewName, locale)方法来创建视图,在该方法中没有做什么额外的事情,只是将创建视图的工作交付给了loadView(viewName, locale)方法去做了,代码如下所示:

protected View createView(String viewName, Locale locale) throws Exception {
    /** 创建及加载view视图 */
    return loadView(viewName, locale); // UrlBasedViewResolver.loadView(viewName, locale);
}

loadView(viewName, locale)方法中,主要执行两个步骤,首先,通过buildView(viewName)方法来创建和初始化一个视图view;然后,通过applyLifecycleMethods(viewName, view)方法,将viewName作为beanName,将view实例对象加入到IOC中:

protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName); /** 创建及初始化View视图对象 */
    View result = applyLifecycleMethods(viewName, view); // 将viewName作为beanName,将view实例对象加入到IOC中
    return (view.checkResource(locale) ? result : null);
}

/**
 * 创建及初始化View试图对象
 */
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    //【步骤1】创建view对象
    AbstractUrlBasedView view = instantiateView();

    //【步骤2】针对view对象进行初始化赋值操作
    view.setUrl(getPrefix() + viewName + getSuffix());
    view.setAttributesMap(getAttributesMap());
    String contentType = getContentType();
    if (contentType != null) view.setContentType(contentType);
    String requestContextAttribute = getRequestContextAttribute();
    if (requestContextAttribute != null) view.setRequestContextAttribute(requestContextAttribute);
    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) view.setExposePathVariables(exposePathVariables);
    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    String[] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) view.setExposedContextBeanNames(exposedContextBeanNames);

    return view;
}

2.4.2> view.render(...)

render(...)方法中,执行了对视图的渲染操作,其主要的渲染操作,可以由子类去自定义实现:

public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    /** 渲染前的准备操作(可由子类自定义实现)*/
    prepareResponse(request, response);

    /** 将渲染后的视图合并到输出流中(可由子类自定义实现)*/
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

下面,我们就以AbstractJackson2View为例,看一下prepareResponse(...)renderMergedOutputModel(...)的具体实现方式:

/**
 * 试图View被渲染前的准备操作
 */
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
    setResponseContentType(request, response); // 设置response响应的ContentType
    response.setCharacterEncoding(this.encoding.getJavaName()); // 设置response响应的CharacterEncoding
    if (this.disableCaching) response.addHeader("Cache-Control", "no-store"); // 设置response响应的Cache-Control
}

/**
 * 将渲染后的视图合并到输出流中
 */
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
    ByteArrayOutputStream temporaryStream = null;
    OutputStream stream;
    //【步骤1】获得相应的输出流
    if (this.updateContentLength) {
        temporaryStream = createTemporaryOutputStream();
        stream = temporaryStream;
    }
    else stream = response.getOutputStream();

    //【步骤2】试图serializationView和filters包装在MappingJacksonValue实例对象中
    Object value = filterAndWrapModel(model, request);

    //【步骤3】将渲染的视图value保存到输出流stream中
    writeContent(stream, value);
    if (temporaryStream != null) 
        writeToResponse(response, temporaryStream);
}

今天的文章内容就这些了:

写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/144158.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

arcgis--二维点、线转三维

利用【3D Analyst工具】-【3D要素】-【依据属性实现要素转3D】将点要素转换成三维点。二维点属性中需含有其点高程信息。原始输入数据点属性表如下&#xff1a; 利用【依据属性实现要素转3D】工具&#xff0c;将其转成三维点。参数设置如下&#xff1a; 点转三维后&#xff0c;…

一个非常基础的python函数语法:lambda表达式

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 lambda表达式本身是一个非常基础的python函数语法&#xff0c;其基本功能跟使用def所定义的python函数是一样的&#xff0c; 只是lambda表达式基本在一行以内就完…

使用后端代码生成器,提高开发效率

如果你是一名后端开发者&#xff0c;那么大多数的工作一定是重复编写各种 CRUD&#xff08;增删改查&#xff09;代码。时间长了你会发现&#xff0c;这些工作不仅无趣&#xff0c;还会浪费你的很多时间&#xff0c;没有机会去做更有创造力和挑战的工作。 作为一名程序员&…

基于C++设计与实现的算符优先分析器

一、实验目的及要求 1.1 目的 加深对语法分析器工作过程的理解&#xff1b;加强对算符优先分析法实现语法分析程序的掌握&#xff1b;能够采用一种编程语言实现简单的语法分析程序&#xff1b;能够使用自己编写的分析程序对简单的程序段进行语法翻译。 1.2 要求 对语法规则…

单链表OJ题目——C语言

本篇博客并非提供完整解题思路代码&#xff0c;而是重点阐述在OJ的链表题目中容易被忽视的点&#xff0c;从而让部分读者在出错百思不得解的情况下能快速发现自己的漏洞&#xff0c;高效查缺补漏&#xff0c;本博客支持读者按题搜索&#xff0c;同时也支持读者根据博客内容自行…

安装pr缺少wmvcore.dll的解决方法,分享4个有效的方法

在当今数字化时代&#xff0c;计算机软件在我们的生活中扮演着重要的角色。然而&#xff0c;有时候我们可能会遇到一些软件问题&#xff0c;其中之一就是缺少某个关键的动态链接库文件。在这篇文章中&#xff0c;我将分享关于如何解决Adobe Premiere Pro&#xff08;PR&#xf…

【官宣】守护四年,“惠医保”暖心回归!

11月13日&#xff0c;惠州参保人专属的城市定制型商业健康险——“惠医保2024”正式升级上线&#xff0c;面向全市基本医保参保人开放参保&#xff0c;超200万保障&#xff0c;150元/人/年起&#xff01; 惠州市医疗保障局、惠州市卫生健康局、惠州市金融工作局及国家金融监督…

c#桥接模式详解

基础介绍&#xff1a; 将抽象部分与它的实现部分分离&#xff0c;使它们都可以独立地变化。适用于不希望在抽象和实现部分之间有固定的绑定关系的情况&#xff0c;或者类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充的情况。 将抽象部分与实现部分分离&#xff0c;…

C语言中自己实现了一个排序为什么会比 qsort 的速度慢几十倍不止

C语言中自己实现了一个排序&#xff0c;为什么会比 qsort 的速度慢几十倍不止? 讲到算法&#xff0c;有一个非常重要的前置知识叫时间复杂度&#xff0c;脱离了这个讲算法的优劣是没什么意义的。这个概念主要是指&#xff0c;你数据量的增加&#xff0c;会让算法的处理时间增加…

mini型光学3D表面轮廓仪,上车即走,上桌即用!

“小身材&#xff0c;大作用”——一个简单的比喻&#xff0c;恰当地总结了SuperView WM100光学3D表面轮廓仪的特点。mini型光学3D表面轮廓仪SuperView WM100&#xff0c;回应了市场对小型化、便携式光学3D表面轮廓仪的需求。 轻便的机身&#xff0c;简约的设计——没有控制箱…

element el-upload上传功能

2023.11.14今天我学习了如何使用el-upload: <!--drag设置可拖动--><!--accept".xlsx"设置上传的文件类型--><!--:limit1上传文件的最大个数--><!--:auto-upload"false"是否在选取后直接上传--><!--:before-upload"beforeU…

cout的输出整数格式

cout默认以十进制格式显示整数 cout << oct和cout << hex不会显示出来&#xff0c;而只是修改cout显示整数的方式 使用std::hex和std::oct时&#xff0c;可以将hex和oct当做变量名&#xff0c;否则不可以 #include<iostream>int main() {int a;std::cout &l…

高并发场景下,如何设计订单库存架构,一共9个关键性问题

库存系统可以分为实物库存和虚拟库存两种类型。实物库存的管理涉及到采购、存储、销售和库存轮换等复杂的活动&#xff0c;需要进行供应链管理和仓库管理等工作。相比之下&#xff0c;虚拟库存的管理相对简单&#xff0c;主要适用于线上资源的数量管理&#xff0c;包括各类虚拟…

安装部署Esxi7.0并创建虚拟机

1. Esxi介绍 ESXI虚拟平台是VMware出品的一个强大平台&#xff0c;它可以直接安装在物理机上&#xff0c;从而充分利用物理奖性能&#xff0c;虚拟多个系统出来。ESXI是一个带WEB管理后台的软件&#xff0c;非常适合安装在服务器上&#xff0c;然后直接通过网页进行远程管理。…

发布自研大模型 夸克App将迎来全面升级

国产大模型阵营再添新锐选手。11月14日&#xff0c;阿里巴巴智能信息事业群发布全栈自研、千亿级参数的夸克大模型&#xff0c;将应用于通用搜索、医疗健康、教育学习、职场办公等众多场景。夸克App将借助自研大模型全面升级&#xff0c;加速迈向年轻人工作、学习、生活的AI助手…

分享一下微信会员充值管理系统怎么做

在当今数字化时代&#xff0c;微信作为中国最流行的社交平台&#xff0c;其功能已经从简单的沟通交流扩展到了生活的方方面面。微信会员充值管理系统就是其中之一&#xff0c;它为商家提供了一个高效、便捷的充值体验&#xff0c;同时也为用户提供了更加个性化的服务。本文将详…

操作系统——死锁(一文详解死锁,死锁产生的原因和死锁的解决方案)

1、什么是死锁&#xff1f;死锁产生的条件&#xff1f; 1.1、什么是死锁&#xff1f; 答&#xff1a; 在两个或者多个并发进程中&#xff0c;如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源&#xff0c;在未改变这种状态之前都不能向前推进&#xff…

22款奔驰C260L升级小柏林音响 全新15个扬声器 提升音乐效果

奔驰新款C级号称奔驰轿车的小“S”&#xff0c;在配置方面上肯定也不能低的&#xff0c;提了一台低配的车型&#xff0c;通过后期升级加装件配置提升更高档次&#xff0c;打造独一无二的奔驰C级&#xff0c;此次来安排一套小柏林之声音响&#xff0c;效果怎么样&#xff0c;我们…

bclinux aarch64 ceph 14.2.10 对象存储 http网关 CEPH OBJECT GATEWAY Civetweb

相关内容 bclinux aarch64 ceph 14.2.10 文件存储 Ceph File System, 需要部署mds&#xff1a; ceph-deploy mds-CSDN博客 ceph-deploy bclinux aarch64 ceph 14.2.10【3】vdbench fsd 文件系统测试-CSDN博客 ceph-deploy bclinux aarch64 ceph 14.2.10【2】vdbench rbd 块设…

独立站邮件营销大佬,手把手教你如何做好!

做独立站邮件营销的方式&#xff1f;独立站怎么做邮件营销&#xff1f; 邮件营销&#xff0c;作为独立站营销的重要手段之一&#xff0c;越来越受到卖家的重视。如何才能做好邮件营销呢&#xff1f;蜂邮EDM将手把手教你如何做好独立站邮件营销&#xff0c;让你在电商领域中更上…
最新文章