(八)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=AnnotationConfigServletWebServerApplicationContextif (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,如果没有配置,直接返回nullString 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.classClass<?> 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”的beaninitMultipartResolver(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 {// 通过视图名称和国际化配置,返回试图ViewView 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请求中的LocaleContextLocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request);// 针对【请求参数】,获得当前的RequestAttributes和最新request请求中的RequestAttributesRequestAttributes 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); // 如果没找到,则抛出异常或者返回404return;}/**【步骤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中获取到对应的beanif (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)) // 寻找到匹配的adapterreturn adapter; // eg: RequestMappingHandlerAdapter@7511// 如果没有找到HandlerAdapter,则抛出异常throw new ServletException(...);
}

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

  • RequestMappingHandlerAdapter

  • HandlerFunctionAdapter

  • HttpRequestHandlerAdapter

  • SimpleControllerHandlerAdapter

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

public interface HandlerAdapter {// 是否匹配该HandlerAdapterboolean supports(Object handler);// 处理请求,返回结果ModelAndViewModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;// 用于请求缓存,已作废@Deprecatedlong 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 {// 创建webRequestServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 创建binderFactory和modelFactoryWebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);//  创建及设置invocableMethodServletInvocableHandlerMethod 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);// 创建及设置mavContainerModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);// 创建及设置asyncWebRequestAsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);// 创建及设置asyncManagerWebAsyncManager 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;// 处理请求返回结果,获得ModelAndViewreturn 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响应的ContentTyperesponse.setCharacterEncoding(this.encoding.getJavaName()); // 设置response响应的CharacterEncodingif (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.rhkb.cn/news/193176.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第三天课程 RabbitMQ

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…

数据库事务相关问题

1. 什么是数据库事务&#xff1f; 事务&#xff0c;由一个有限的数据库操作序列构成&#xff0c;这些操作要么全部执行,要么全部不执行&#xff0c;是一个不可分割的工作单位。 假如A转账给B 100 元&#xff0c;先从A的账户里扣除 100 元&#xff0c;再在 B 的账户上加上 100 …

python自动化第一篇—— 带图文的execl的自动化合并

简述 最近接到一个需求&#xff0c;需要为公司里的一个部门提供一个文件上传自动化合并的系统&#xff0c;以供用户稽核&#xff0c;谈到自动化&#xff0c;肯定是选择python&#xff0c;毕竟python的轮子多。比较了市面上几个用得多的python库&#xff0c;我最终选择了xlwings…

SOME/IP学习笔记3

目录 1.SOMEIP Transformer 1.1 SOME/IP on-wire format 1.2 协议指定 2. SOMEIP TP 2.1 SOME/IP TP Header 3.小结 1.SOMEIP Transformer 根据autosar CP 相关规范&#xff0c;SOME/IP Transformer主要用于将SOME/IP格式的数据序列化&#xff0c;相当于一个转换器。总体…

uniapp+vite+vue3开发跨平台app,运行到安卓模拟器调试方法

因为没有使用hbuilder开发uniapp&#xff0c;而是使用了vscode和vite来开发的&#xff0c;所以怎么将这个程序运行到安卓模拟器调试开发呢&#xff1f;其实方法很简单&#xff0c;使用android studio创建一个模拟器或者其他mumu模拟器&#xff0c;然后将项目使用hbuilder打开&a…

macos死机后IDEA打不开,Cannot connect to already running IDE instance.

Cannot connect to already running IDE instance. Exception: Process 573 is still running 解决办法 进入&#xff1a;/Users/lzq/Library/Application Support/JetBrains 找到IDEA的目录删除隐藏文件夹 .lock rm -rf .lock

黑马程序员微服务 第五天课程 分布式搜索引擎2

分布式搜索引擎02 在昨天的学习中&#xff0c;我们已经导入了大量数据到elasticsearch中&#xff0c;实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。 所以今天&#xff0c;我们研究下elasticsearch的数据搜索功能。我们会分别使用DSL和Res…

入选《人工智能领域内容榜》

入选《人工智能领域内容榜》第 23名 C# OpenCvSharp DNN HybridNets 同时处理车辆检测、可驾驶区域分割、车道线分割-CSDN博客

Playwright UI 自动化测试实战

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

Semantic Kernel 学习笔记1

1. 挂代理跑通openai API 2. 无需魔法跑通Azure API 下载Semantic Kernel的github代码包到本地&#xff0c;主要用于方便学习python->notebooks文件夹中的内容。 1. Openai API&#xff1a;根据上述文件夹中的.env.example示例创建.env文件&#xff0c;需要填写下方两个内…

Vue 简单的语法

1.插值表达式 1.插值表达式的作用是什么&#xff1f; 利用表达式进行插值&#xff0c;将数据渲染到页面中&#xff1b; 2.语法结构&#xff1f; {{表达式}} 3.插值表达式的注意点是什么&#xff1f; &#xff08;1&#xff09;使用的数据要存在&#xff0c;在data中&…

错误:ERROR:torch.distributed.elastic.multiprocessing.api:failed

在多卡运行时&#xff0c;会出现错误&#xff08;ERROR:torch.distributed.elastic.multiprocessing.api:failed&#xff09;&#xff0c;但是单卡运行并不会报错&#xff0c;通常在反向梯度传播时多卡梯度不同步。但我是在多卡处理数据进行tokenizer阶段报错&#xff0c;这竟然…

仿真算法收敛与初值的关系

问题&#xff1a; 当电路中存在大电容时&#xff0c;由于初值设置不合理可能导致的仿真算法不收敛的问题。 解决方法&#xff1a;设置初始节点值。 疑问&#xff1a;Node set和Initial Condition的区别。 [求助] node set 和initial condition有很么区别呢&#xff1f; 注&…

Kafka 的应用场景

Kafka 是一个开源的分布式流式平台&#xff0c;它可以处理大量的实时数据&#xff0c;并提供高吞吐量&#xff0c;低延迟&#xff0c;高可靠性和高可扩展性。 Kafka 最初是为分布式系统中海量日志处理而设计的。它可以通过持久化功能将消息保存到磁盘&#xff0c;并让消费者按…

Xshell+Xftp通过代理的方式访问局域网内网服务器

最近在部署项目时遇到只有1台服务器拥有公网ip&#xff0c;其它服务器只有局域网ip&#xff0c;当然其它服务器可以正常访问网络&#xff0c;例如如下模型。之前访问其它几台服务器&#xff0c;都是先通过登录公网IP服务器&#xff0c;然后在Xshell里面执行ssh远程连接&#xf…

uniapp: 实现pdf预览功能

目录 第一章 实现效果 第二章 了解并解决需求 2.1 了解需求 2.2 解决需求 2.2.1 方法一 2.2.2 方法二 第三章 资源下载 第一章 实现效果 第二章 了解并解决需求 2.1 了解需求 前端需要利用后端传的pdf临时路径实现H5端以及app端的pdf预览首先我们别像pc端一样&#…

单相过压继电器DVR-G-100-1 0~500V AC/DC220V 导轨安装

系列型号 DVR-G-100-1X3数字式过压继电器&#xff1b; DVR-G-100-3三相过压继电器&#xff1b; DVR(H)-G-100-1单相过压继电器&#xff1b; DVR-Q-100-3三相欠压继电器&#xff1b; DVR(H)-Q-100-3三相欠压继电器 一、用途 主要应用于电机、变压器等主设备以及输配电系统的继…

实现高值医疗耗材智能化管理的RFID医疗柜解决方案

一、行业背景 医疗物资管理面临着一系列问题&#xff0c;如高值耗材种类激增导致准入标准弱化、信息追踪困难、管理责任不明确等&#xff0c;医院内部设备、财务和临床科室相互独立&#xff0c;兼容性不佳&#xff0c;高值耗材储备不足&#xff0c;缺乏合理的预警机制&#xf…

libusb获取Windows设备实例路径DevicePath

libusb 当前版本&#xff08;1.0.26&#xff09;libusb.h 头文件提供的接口似乎没有办法获取 Windows 平台相关的设备实例路径&#xff0c;其形如&#xff1a; \\?\usb#vid_04ca&pid_7070#5&20d34a76&0&6#{a5dcbf10-6530-11d2-901f-00c04fb951ed} 只是提供了…

浙江大学数据结构陈越 第一讲 数据结构和算法

数据结构 数据结构是计算机科学中用来组织和存储数据的方式。它可以理解为一种组织数据的方式&#xff0c;能够有效地管理和操作数据&#xff0c;以及提供对数据进行存储、检索、更新和删除等操作的方法。常见的数据结构包括数组、链表、栈、队列、树和图等&#xff0c;它们各自…