(八)Spring源码解析:Spring MVC

news/2024/2/27 2:03:09

一、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;
List interceptorList】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(...)方法,代码如下所示:

image.png

image.png

在该方法中,主要是执行了两个步骤:
步骤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);
}

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

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

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


http://www.ppmy.cn/news/985495.html

相关文章

MQ, RocketMQ, 安装

文章说明 本文主要说明RocketMQ的几种常见的安装方式。之前在工作中也用过RocketMQ&#xff0c;但是一直用的是测试环境上的&#xff0c;也没有自己动手安装过。这次专门抽了时间学习了一下。 文章目录 文章说明参考文献安装windows安装环境要求下载配置环境变量启动注意事项 …

自动化运维工具—Ansible概述

Ansible是什么&#xff1f; Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、…

linux学成之路(基础篇)(二十三)MySQL服务(数据库备份——补充)

目录 一、MySQL数据库备份 概述 重要性 造成数据丢失的原因 二、备份类型 一、物理与逻辑角度 一、物理备份 二、逻辑备份 二、数据库备份策略角度 一、完整备份 二、增量备份 三、常见的备份方法 一、物理备份 二、使用专用备份工具 三、通过启用二进制日志增量…

Linux 两种GPIO控制方式

1、采用sysfs的方式控制&#xff0c;这是内核标准的sysfs接口 如&#xff1a; echo 25 > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio25/direction echo 1 > /sys/class/gpio/gpio25/value 2、采用libgpiod 控制内核生成的节点来控制/d…

【Maven】Maven 中 pom.xml 文件

文章目录 前言什么是 pom&#xff1f;pom配置一览 1. dependencies2.scope3.properties4.plugin参考 前言 Maven 是一个项目管理工具&#xff0c;可以对 Java 项目进行构建和管理依赖。 本文&#xff0c;我们认识下 pom.xml 文件。POM(Project Object Model&#xff0c; 项目…

【Maven】Maven配置国内镜像

文章目录 1. 配置maven的settings.xml文件1.1. 先把镜像mirror配置好1.2. 再把仓库配置好 2. 在idea中引用3. 参考资料 网上配置maven国内镜像的文章很多&#xff0c;为什么选择我&#xff0c;原因是&#xff1a;一次配置得永生、仓库覆盖广、仓库覆盖全面、作者自用的配置。 1…

HttpServletResponse类全解析

HttpServletResponse 是 Java Servlet API 中的接口&#xff0c;它是 ServletResponse 接口的子接口&#xff0c;专门用于处理 HTTP 响应。HttpServletResponse 定义了用于设置 HTTP 响应信息的方法。 以下是 HttpServletResponse 接口的一些主要属性和方法的解析&#xff0c;…

【Linux】冯诺依曼体系结构思想

冯诺依曼体系结构 冯诺依曼体系结构冯诺依曼体系结构的五大部分冯诺依曼体系结构的运行过程存储器中的木桶效应扩展&#xff1a;计算机存储设备金字塔实例&#xff1a;qq聊天数据传输过程 &#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &…

什么是前端工程化?

工程化介绍 什么是前端工程化&#xff1f; 前端工程化是一种思想&#xff0c;而不是某种技术。主要目的是为了提高效率和降低成本&#xff0c;也就是说在开发的过程中可以提高开发效率&#xff0c;减少不必要的重复性工作等。 tip 现实生活举例 建房子谁不会呢&#xff1f;请…

github token使用方法

git remote set-url origin https://<githubtoken>github.com/<username>/<repositoryname>.git 在私有仓库的HTTPS的url上加入<githubtoken>即为token url&#xff0c;可以免ssh key登录

自考毕业论文答辩,高情商回答模板

自考本科毕业论文申请流程&#xff1a; 缴费报名-选题-联系指导老师-开题报告-初稿-终稿查重-终稿-答辩。 论文答辩技巧 一、熟悉内容 作为将要参加论文答辩同学&#xff0c;首先而且必须对自己所著的毕业论文内容有比较深刻理解和比较全面的熟悉。 这是为回答毕业论文答辩…

mysql常用时间相关函数

在我们平常开发的工作中&#xff0c;我们在数据库中经常会用到时间相关的函数&#xff0c;比如格式化当前时间&#xff0c;求当前时间或者计算某个特定间隔后的时间&#xff0c;那么我们主要会用到哪些函数呢&#xff1f; 1&#xff1a;求当前时间&#xff1a; select now();…

虚拟机 RHEL8 安装 MySQL 8.0.34

目录 安装步骤一、清除所有残留的旧MySQL二、安装MySQL 报错问题1. 提示未找到匹配的参数&#xff1a; mysql-community-server2. 公钥问题 安装步骤 一、清除所有残留的旧MySQL 1. 关闭MySQL [rootlocalhost /]# service mysqld stop Redirecting to /bin/systemctl stop …

【业务功能篇56】SpringBoot 日志SLF4J Logback

3.5.1 日志框架分类与选择 3.5.1.1 日志框架的分类 日志门面 (日志抽象)日志实现JCL(Jakarta Commons Logging) SLF4J(Simple Logging Facade for Java)Jul(Java Util Logging) , Log4j , Log4j2 , Logback 记录型日志框架 Jul (Java Util Logging)&#xff1a;JDK中的日志…

Microsoft todo 数据导出

文章目录 官方说明&#xff1a; https://support.microsoft.com/zh-cn/office/导出您的-microsoft-待办事项帐户-d286b243-affb-4db4-addc-162e16588943 由于 微软待办 会自动与 Outlook 中的任务同步&#xff0c;因此您可以从 Outlook 中导出所有列表和任务。 若要导出列表和…

Java中的wait()方法与sleep()方法的区别

在Java多线程编程中&#xff0c;经常会用到wait()方法和sleep()方法&#xff0c;这两个方法都可以用来暂停当前线程&#xff0c;但它们的工作方式和使用场景存在一些重要的区别。在本文中&#xff0c;我们将详细探讨这两个方法的性质以及它们之间的差异。 1. 基本区别 wait()方…

SSL证书是什么?

SSL证书是什么&#xff1a;SSL证书&#xff08;Secure Sockets Layer Certificate&#xff09;是一种数字证书&#xff0c;用于在计算机网络上进行安全通信。它可以验证通信双方的身份&#xff0c;并通过加密传输数据来实现安全通信。 SSL证书申请&#xff1a;通常&#xff0c…

每日一道面试题之介绍一下Iterator

Iterator是Java中的一个接口&#xff0c;用于遍历集合&#xff08;Collection&#xff09;中的元素。通过Iterator&#xff0c;可以按顺序访问集合中的每个元素&#xff0c;而无需了解集合的内部实现细节。 使用Iterator的一般步骤如下&#xff1a; 通过调用集合的iterator()…

redis群集(主从复制)

---------------------- Redis 主从复制 ---------------------------------------- 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(Master)&#xff0c;后者称为从节点(Slave)&#xff1b;数据的复制是单向的&#xf…

KubeVela篇06:Kubevela Addon插件安装原理

addon支持从本地、git仓库、helm chart仓库安装,最终原理都相同,因此我们以本地安装为例。 完整流程如下: 从指定目录读取一个完整的addon安装包。 根据metadata.yaml配置文件,校验插件要求的kubevela、k8s的版本,不满足版本要求则终止安装。 根据metadata.yaml配置文件…
最新文章