(八)Spring源码解析:Spring MVC

一、Servlet及上下文的初始化

1.1> DispatcherServlet的初始化

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

java 复制代码
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接口)操作。代码如下所示:

java 复制代码
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章节来详细分析一下它的实现逻辑。源码及注释如下所示:

java 复制代码
protected final void initServletBean() throws ServletException {
	try {
        /** 初始化WebApplicationContext */
		this.webApplicationContext = initWebApplicationContext();
    
     	// 空方法,由子类负责实现
		initFrameworkServlet();
	}
	catch (ServletException | RuntimeException ex) {throw ex;}
}

1.2> WebApplicationContext的初始化

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

java 复制代码
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应用上下文

java 复制代码
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即可。代码如下所示:

java 复制代码
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章节已经介绍过了,此处就不在赘述了,具体代码如下所示:

java 复制代码
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方法。

java 复制代码
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实现类列表,所以,获得了实现类列表后,自然需要通过反射将其转换为实例对象集合,然后返回出去。代码及注释如下所示:

java 复制代码
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如下所示:

java 复制代码
<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,我们可以通过如下方式进行自定义配置

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

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

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

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

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

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

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

java 复制代码
<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,具体设置方式如下所示:

java 复制代码
<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,具体设置方式如下所示:

java 复制代码
<servlet>
	... ...
    <init-param>
        <param-name>detectAllHandlerAdapters</param-name>
        <param-value>false</param-value>
    </init-param>
	... ...
</servlet>

f> HandlerExceptionResolver

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

java 复制代码
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)来获得的,下面我们来看一下这个接口的源码内容:

java 复制代码
public interface RequestToViewNameTranslator {
	String getViewName(HttpServletRequest request) throws Exception;
}

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

java 复制代码
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视图,接口如下所示:

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

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

java 复制代码
<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(...)方法,代码如下所示:

java 复制代码
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)方法,那么,下面我们就来针对这个方法进行深度解析。代码如下所示:

java 复制代码
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)

java 复制代码
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请求的方法,而其他的方法无外乎是处理请求之前的准备操作以及处理完毕后的收尾阶段,那么下面我们来分析一下这个方法:

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 {
            //【步骤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的呢?

java 复制代码
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)方法了,代码如下所示:

java 复制代码
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

java 复制代码
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请求待处理的类的方法。代码如下所示:

java 复制代码
// 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值,返回即可,代码如下所示:

java 复制代码
// 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@7409, ResourceUrlProviderExposingInterceptor@7410} ,代码如下所示:

java 复制代码
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;

List interceptorList】interceptor拦截器列表;

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

java 复制代码
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,直接返回即可,不再继续遍历对比。

java 复制代码
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请求的方法。代码如下所示:

java 复制代码
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)这个方法了,下面我们就针对这个方法来进行深度解析,代码如下所示:

java 复制代码
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实例对象;

java 复制代码
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)方法来进行反射调用。代码如下所示:

java 复制代码
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类的所对应的处理方法,获得最终处理后的结果,代码如下所示:

java 复制代码
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)方法我们可以确定这个入参我们的解析器是否支持解析,那么我们先不着急解析这个方法,下来看一下具体实现:

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

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

java 复制代码
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(...)方法来进行结果的处理。代码如下所示:

java 复制代码
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的异常。代码如下所示:

java 复制代码
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)方法去做了,代码如下所示:

java 复制代码
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中:

java 复制代码
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(...)方法中,执行了对视图的渲染操作,其主要的渲染操作,可以由子类去自定义实现:

java 复制代码
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(...)的具体实现方式:

java 复制代码
/**
 * 试图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);
}

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

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

更多技术干货,欢迎大家关注公众号"爪哇缪斯" ~ \(^o^)/ ~ 「干货分享,每天更新」

相关推荐
2401_857610036 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
LuckyLay21 分钟前
Spring学习笔记_34——@Controller
spring·controller
ApiHug2 小时前
ApiSmart x Qwen2.5-Coder 开源旗舰编程模型媲美 GPT-4o, ApiSmart 实测!
人工智能·spring boot·spring·ai编程·apihug
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
背水2 小时前
初识Spring
java·后端·spring
晴天飛 雪3 小时前
Spring Boot MySQL 分库分表
spring boot·后端·mysql
weixin_537590453 小时前
《Spring boot从入门到实战》第七章习题答案
数据库·spring boot·后端
AskHarries3 小时前
Spring Cloud Gateway快速入门Demo
java·后端·spring cloud