本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试。
记录日期:2022.1.4
大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习。
文章目录
框架原理 - SpringMVC
概念
介绍
SpringMVC含义
SpringMVC
是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。
SpringMVC是Spring的一部分,它属于 Spring 三层架构中的Web层,它在Spring框架属于Web模块。
MVC含义
MVC的全名是Model View Controller
,是模型(model)-视图(view)-控制器(controller)
的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面,在需要改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,达到减少编码的时间。
MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器。
在网页当中,他们三者代表如下:
- V 即View视图是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操纵的方式。
- M 即model模型是指模型表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
- C 即controller控制器是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
使用目的
在于将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如Windows系统资源管理器文件夹内容的显示方式,下面两张图中左边为详细信息显示方式,右边为中等图标显示方式,文件的内容并没有改变,改变的是显示的方式。不管用户使用何种类型的显示方式,文件的内容并没有改变,达到M和V分离的目的。
运行流程(刻在DNA)
首先先介绍一下SpringMVC的工作执行流程,后面逐步解析源码。
用尚硅谷的SpringMVC中的图来看:
我们可以把流程分为这十一步:
- 用户发送请求至前端控制器
DispatcherServlet
。 DispatcherServlet
收到请求调用HandlerMapping
处理器映射器。- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给
DispatcherServlet
。 DispatcherServlet
调用HandlerAdapter
处理器适配器。HandlerAdapter
经过适配调用具体的处理器(Controller,也叫后端控制器)。Controller
执行完成返回ModelAndView
。HandlerAdapter
将Controller
执行结果ModelAndView
返回给DispatcherServlet
。DispatcherServlet
将ModelAndView
传给ViewReslover
视图解析器。ViewReslover
解析后返回具体View
。DispatcherServlet
根据View
进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet
响应用户。
上面的流程不太好记,那么我们就可以把主要流程概括一下:
- 首先我们用户端发送请求是给
DispatcherServlet
。 - 后面发生的交互是在于内部的执行链过程:
DispatcherServlet
与HandlerMapping
;DispatcherServlet
与HandlerAdapter
与Handler
;DispatcherServlet
与ViewReslover
;
- 最后
DispatcherServlet
响应结果。
具体的过程我们下面通过源码来了解。
SpringMVC组件
DispatcherServlet
DispatcherServlet
作为前端控制器,整个流程控制的中心,它的作用是接收请求,响应结果,并控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
源码解读
org.springframework.web.servlet.DispatcherServlet
类,首先来看一下它的类图:
我们发现它其实也是一个Servlet
,因为它继承自 HttpServlet
,通过使用 Servlet API
对 HTTP
请求进行响应。
在最早学习SSM框架的时候,我们知道要在web.xml中配置DispatcherServlet。配置了这个Servlet后,在web容器加载时,便会由Servlet容器来控制它的生命周期。
容器初始化
如果有不理解WebApplicationContext和ServletContext等,先可以看一下博客链接:Spring中DispacherServlet与WebApplicationContext、ServletContext的关系和工作机制
当tomcat启动时会初始化一个web对应的IOC容器,用于初始化和注入各种我们在web运行过程中需要的对象。
web.xml中的配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
ContextLoaderListener初始化容器(前置)
先来看一下它的类图:
org.springframework.web.context.ContextLoaderListener
是一个***,其实现了ServletContextListener
接口,它是用来监听Servlet
,当tomcat
启动时会初始化一个Servlet容器,这样 ContextLoaderListener
会监听到Servlet
的初始化,这样在Servlet
初始化之后我们就可以在 ContextLoaderListener中也进行一些初始化操作。
给出SpringMVC父容器初始化流程图:
看下面的 ServletContextListener
的源码也是比较简单的,ContextLoaderListener
实现了ServletContextListener
接口,所以会有两个方法contextInitialized
和contextDestroyed
。web容器初始化时会调用方法 contextInitialized
,web容器销毁时会调用方法 contextDestroyed
。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
// 初始化启动上下文,也叫做根(Root)上下文,即WebApplicationContext,这里实现类是XmlWebApplicationContext
@Override
public void contextInitialized(ServletContextEvent event) {
//在父类ContextLoader中实现
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
ContextLoaderListener
的方法contextInitialized()
的默认实现是在他的父类ContextLoader
的initWebApplicationContext()
方法中实现的,意思就是初始化web应用上下文。他的主要流程就是创建一个IOC容器,并将创建的IOC容器存到servletContext
中,ContextLoader
的核心实现如下:
//初始化WebApplicationContext,IOC容器
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//判断web容器中是否已经有WebApplicationContext,有则抛出异常
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("...");
}
// ...log
try {
//创建WebApplicationContext
if (this.context == null) {
//最终得到的是XmlWebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//判断应用上下文是否激活
if (!cwac.isActive()) {
//判断是否已经有父容器
if (cwac.getParent() == null) {
//获得父容器
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//设置并刷新WebApplicationContext容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将初始化的WebApplicationContext设置到servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
// ...log
//初始化 WebApplicationContext完成并返回
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
具体的细枝末节的代码实现就不看了,可以参考博客:springMVC源码分析–容器初始化(一)ContextLoaderListener
初始化DispatcherServlet(主要)
给出初始化的运行流程图,如下:
初始化部分由HttpServletBean
的init()
方法开始的,代码如下:
@Override
public final void init() throws ServletException {
// 将Servlet初始化参数设置到该组件上,如contextAttribute、contextClass、namespace、contextConfigLocation...
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 留给子类拓展的钩子方法
initServletBean();
}
它调用了自身的钩子方法initServletBean()
方法,它由子类FrameworkServlet
来实现,这个方法是用来进行Web上下文初始化的,主要做了两件事情:
- 初始化web上下文。
- 提供给子类初始化扩展点。
具体代码如下:
@Override
protected final void initServletBean() throws ServletException {
// ...打日志
try {
// 初始化web上下文
this.webApplicationContext = initWebApplicationContext();
// 留给子类拓展的钩子方法
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
// ...打日志
}
我们来看一下它初始化web上下文的initWebApplicationContext()
方法,从initWebApplicationContext()
方法可以看出,基本上如果ContextLoaderListener
加载了上下文就作为根上下文(DispatcherServlet的父容器)。
protected WebApplicationContext initWebApplicationContext() {
//Root 上下文(ContextLoaderListener所加载的)
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 1、在创建该Servlet注入的上下文
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//2、查找已经绑定的上下文
wac = findWebApplicationContext();
}
if (wac == null) {
//3、如果没有找到相应的上下文,并指定父亲为ContextLoaderListener
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
//4、刷新上下文(执行一些初始化)
onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
最后调用了onRefresh()方法执行容器的一些初始化,这个方法由子类实现,来进行扩展。
DispatcherServlet
继承FrameworkServlet
,并实现了onRefresh()
方法提供一些前端控制器相关的配置。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 具体的初始化逻辑
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
从如上代码可以看出,DispatcherServlet
启动时会进行我们需要的Web层Bean的配置,如HandlerMapping
、HandlerAdapter
等,而且如果我们没有配置,还会给我们提供默认的配置。
整个DispatcherServlet初始化的过程\具体主要做了如下两件事情:
- 初始化Spring Web MVC使用的Web上下文,并且可能指定父容器为(ContextLoaderListener加载了根上下文)。
- 初始化DispatcherServlet使用的策略,如HandlerMapping、HandlerAdapter等。
SpringMVC中提供的默认配置在DispatcherServlet.properties
中,是当Spring配置文件中没有指定配置时使用的策略,配置如下:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
声明一下我这的版本是spring-webmvc-5.2.5.RELEASE。
从如上配置可以看出DispatcherServlet
在启动时会自动注册这些特殊的Bean,无需我们注册,如果我们注册了,默认的将不会注册。
DispatcherServlet的组件
从DispatcherServlet.properties
可以看出有许多特殊的Bean,接下来我们就看看Spring Web MVC
主要有哪些特殊的Bean。
DispatcherServlet
默认使用WebApplicationContext
作为上下文,因此我们来看一下该上下文中有哪些特殊的Bean:
- Controller:处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理。
- HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器。
- **HandlerAdapter:**HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且掉处理器的handleRequest方法进行功能处理。
- **ViewResolver:**ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图。
- **LocalResover:**本地化解析,因为Spring支持国际化,因此LocalResover解析客户端的Locale信息从而方便进行国际化;
- **ThemeResovler:**主题解析,通过它来实现一个页面多套风格,即常见的类似于软件皮肤效果。
- **MultipartResolver:**文件上传解析,用于支持文件上传。
- **HandlerExceptionResolver:**处理器异常解析,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息)。
- **RequestToViewNameTranslator:**当处理器没有返回逻辑视图名等相关信息时,自动将请求URL映射为逻辑视图名。
- **FlashMapManager:**用于管理FlashMap的策略接口,FlashMap用于存储一个请求的输出,当进入另一个请求时作为该请求的输入,通常用于重定向场景,后边会细述。
从源码中我们能看到:
// 文件上传组件
/** MultipartResolver used by this servlet */
private MultipartResolver multipartResolver;
// 资源定位组件
/** LocaleResolver used by this servlet */
private LocaleResolver localeResolver;
// 主题解析组件
/** ThemeResolver used by this servlet */
private ThemeResolver themeResolver;
// 处理器映射器组件集合
/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;
// 处理器适配器组件集合
/** List of HandlerAdapters used by this servlet */
private List<HandlerAdapter> handlerAdapters;
// 异常处理解析器集合
/** List of HandlerExceptionResolvers used by this servlet */
private List<HandlerExceptionResolver> handlerExceptionResolvers;
// 视图名解析器
/** RequestToViewNameTranslator used by this servlet */
private RequestToViewNameTranslator viewNameTranslator;
// 重定向及FlashMap存储组件
/** FlashMapManager used by this servlet */
private FlashMapManager flashMapManager;
// 视图解析组件集合
/** List of ViewResolvers used by this servlet */
private List<ViewResolver> viewResolvers;
下面我们来简单介绍一下几种组件,介绍它的概念、初始化过程,具体的作用我们可以在待会讲执行链全流程的源码我清楚。
MultipartResolver
MultipartResolver 用于处理文件上传,需要添加multipart解析器。
需要在配置文件中配置:
<!-- 文件上传,id名称固定 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!-- 文件最大size限制为10m -->
<property name="maxInMemorySize" value="4096" /> <!-- 文件上传过程中使用的最大内存块 -->
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
初始化
看一下刚才的initMultipartResolver(context)
方法:
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
// ...log
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isTraceEnabled()) {
logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
}
}
}
initMultipartResolver
就是获取 CommonsMultipartResolver
的 bean 赋给 DispatcherServlet#multipartResolver
。
HandlerMapping
HandlerMapping
接口,通过扩展处理器映射器实现不同的映射方式。
它的作用是根据请求的url
映射Handler
。
HandlerMapping
负责根据用户请求找到Handler
即处理器,SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
DispatcherServlet
按优先级的高低来选择 HandlerMapping
,如果当前的 HandlerMapping
能够返回可用的handler
,则使用当前返回的 handler
来处理请求。
初始化
我们来看一下刚才的initHandlerMappings()
方法:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 默认为true,从所有IOC容器中去获取
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
// 根据名称从当前的IOC容器中通过getBean获取handlerMapping
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
// 仍没有找到的化需要为servlet生成默认的handlerMappings,
// 默认值设置在 DispatcherServlet.properties
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
// ......
}
}
参数this.detectAllHandlerMappings
默认为true,可以通过配置修改:
<init-param>
<param-name>detectAllHandlerMappings</init-param>
<param-value>false</param-value>
</init-param>
如果设置以上参数,则 SpringMVC 将查找名为“handlerMapping”
的bean,并作为唯一的handlerMapping
。否则会加载该WebApplicationContext
下的所有handlerMapping
。若是没有则调用 getDefaultStrategies
创建。
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName(); // 这里是org.springframework.web.servlet.HandlerMapping
String value = defaultStrategies.getProperty(key); // DispatcherServlet.properties中的默认配置
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);
strategies.add((T) strategy);
}
......
}
return strategies;
}
else {
return new LinkedList<>();
}
}
默认配置defaultStrategies
是通过读取DispatcherServlet.properties
来配置的,读取配置代码如下:
private static final Properties defaultStrategies; // 默认配置properties
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
......
}
默认配置前面也说过了,在DispatcherServlet.properties
中,指定HandlerMapping
的配置如下:
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
HandlerAdapters
HandlerAdapters
接口,通过扩展处理器适配器实现不同的适配方式。
它的作用用一句话概括就是调用具体的方法对用户发来的请求来进行处理。当handlerMapping
获取到执行请求的controller
时,DispatcherServltet
会根据controller
对应的controller
类型来调用相应的HandlerAdapter
来进行处理。
初始化
代码逻辑与上面一样,同样可以设置 detectAllHandlerAdapters
值为 false
, 来加载名为 handlerAdapter
的 HandlerAdapter
的 bean, 用户可以通过此来加载自定义的 HandlerAdapter
。来看看 Spring 配置文件DispatcherServlet.properties
中指定的 HandlerAdapter
。
初始化源码如下:
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
// ...log
}
}
后面其他的初始化我们就不看了。
SpringMVC处理请求链路解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxufKn18-1641917777192)(C:\Users\jiangcx\AppData\Roaming\Typora\typora-user-images\image-20220108105732364.png)]
doService()
是作为SpringMVC
承接Servlet
的入口函数,调用doDispatch()
实现。doDispatch()
这个函数核心逻辑如下:
- 根据
request
所含有的路径属性查找handlerMapping
。 - 找到
handler
后,要根据handler
查找adapter
,这里的adapter
的作用就是屏蔽handler
的实现差别,提供一致的invoke
调用。 - 找到
adapter
后就调用handler
接口,来进行最核心的业务请求。
展示一下doDispatch()
的大致流程图:
FrameworkServlet#processRequest
首先我们来看一下一个请求的入口,FrameworkServlet
复写所有的doGet/doPost
等等都交给了processRequest(request, response)
方法。
我们就先来解析一下**processRequest()**方法,FrameworkServlet#processRequest
,该方法作为FrameworkServlet
的实现,其实它也是提供了一些模版实现,最终会开口给子类的 模版设计模式,在Spring源码中大量存在。此处我们关注点在于FrameworkServlet
为我们做了哪些事情:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
// 记录抛出的异常
Throwable failureCause = null;
//拿到之前的LocaleContext上下文(因为可能在Filter里已经设置过了)
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 以当前的request创建一个Local的上下文,后面会继续处理
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 这里面build逻辑注意:previousAttributes若为null,或者就是ServletRequestAttributes类型,那就new ServletRequestAttributes(request, response);
// 若不为null,就保持之前的绑定结果,不再做重复绑定了(尊重原创)
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 拿到异步管理器。这里是首次获取,会new WebAsyncManager(),然后放到request的attr里面
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//这里需要注意:给异步上下文恒定注册了RequestBindingInterceptor这个拦截器(作用:绑定当前的request、response、local等)
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//这句话很明显,就是吧request和Local上下文、RequestContext绑定
initContextHolders(request, localeContext, requestAttributes);
try {
//模版设计模式:由子类DispatcherServlet去实现实际逻辑
doService(request, response);
} catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
} catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
//这个时候已经全部处理完成,视图已经渲染了
//doService()方法完成后,重置上下文,也就是解绑
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
//关键:不管执行成功与否,都会发布一个事件,说我处理了这个请求(有需要监听的,就可以监听这个事件了,每次请求都会有)
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
publishRequestHandledEvent()
是用来发布请求处理完成后的事件:
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
//当publishEvents设置为true和 webApplicationContext 不为空就会处理这个事件的发布
if (this.publishEvents && this.webApplicationContext != null) {
// 计算出处理该请求花费的时间
long processingTime = System.currentTimeMillis() - startTime;
this.webApplicationContext.publishEvent(
//ServletRequestHandledEvent这个事件:目前来说只有这里会发布
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, response.getStatus()));
}
}
如果我们想的话,我们就可以监听这个ServletRequestHandledEvent
事件:
// 专门监听ServletRequestHandledEvent时间的***
@Slf4j
@Component
public class ServletReqestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
// url=[/test/hello]; client=[127.0.0.1]; method=[GET]; servlet=[dispatcher]; session=[null]; user=[null]; time=[143ms]; status=[OK]
log.info(event.getDescription());
log.info("返回状态码为:" + event.getStatusCode()); //返回状态码为:200
log.info("异常信息为:" + event.getFailureCause()); //异常信息为:null
log.info("处理请求耗时为:" + event.getProcessingTimeMillis()); //处理请求耗时为:143
log.info("事件源为:" + event.getSource()); //事件源为:org.springframework.web.servlet.DispatcherServlet@3e7fadbb
}
}
DispatcherServlet#doService
子类DispatcherServlet
实现doService()
方法:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 如果该请求是include的请求(请求包含) 那么就把request域中的数据保存一份快照版本
// 等doDispatch结束之后,会把这个快照版本的数据覆盖到新的request里面去
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// 说得很清楚,把一些常用对象放进请求域 方便Handler里面可以随意获取
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); //这个是web子容器
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);
}
try {
// DispatcherServlet最重要的方法,交给他去分发请求你、找到handler处理等等
doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
//如果是include请求 会将上面的数据快照,重新放置到request里面去
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
DispatcherServlet#doDispatch
首先根据请求的路径找到HandlerMethod
(带有Method反射属性,也就是对应Controller中的方法),然后匹配路径对应的拦截器,有了HandlerMethod
和拦截器构造个HandlerExecutionChain
对象。
HandlerExecutionChain
对象的获取是通过HandlerMapping
接口提供的方法中得到。
有了HandlerExecutionChain
之后,通过HandlerAdapter
对象进行处理得到ModelAndView
对象,HandlerMethod
内部handle
的时候,使用各种HandlerMethodArgumentResolver
实现类处理HandlerMethod
的参数(非常重要),使用各种HandlerMethodReturnValueHandler
实现类处理返回值。 最终返回值被处理成ModelAndView
对象,这期间发生的异常会被HandlerExceptionResolver
接口实现类进行处理。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 此处用processedRequest 需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象
HttpServletRequest processedRequest = request;
// 异常链处理器
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//checkMultipart 这个方法很重要,判断是否是上传需求
//如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求
processedRequest = checkMultipart(request);
// 标记一下:是否是文件上传的request了
multipartRequestParsed = (processedRequest != request);
// 找到一个处理器,如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler,因此不再解释
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 如果是GET请求,如果内容没有变化的话,则直接返回
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行处理器链里的拦截器集合
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
// 这也是一个很复杂的过程(序列化、数据绑定等等)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的
applyDefaultViewName(processedRequest, mv);
// 执行所有的拦截器的postHandle方法,并且把mv给他
// 这里有一个小细节:这个时候拦截器是【倒序】执行的
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
// 这两个catcher什么都不做,只是把异常记录下来
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//这个方法很重要,顾名思义,他是来处理结果的,渲染视图、处理异常等等的
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
checkMultipart
这个方法是用来判断是否是上传需求,如果是处理上传的请求,返回的 request
将和原先请求参数的processedRequest
不再指向同一对象。
具体checkMultipart
的实现如下:
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 配置了multipartResolver && 文件上传的请求 才会继续执行内部代码块
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
// 如果该请求已经是MultipartHttpServletRequest 那就输出日志并结束
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("...");
} else if (hasMultipartException(request) ) {
// 判断是否有MultipartException 一般没有
logger.debug("...");
} else {
try {
// 这里特别注意,不管是哪种multipartResolver的实现,内部都是new了一个新的MultipartHttpServletRequest的实现类,所以不再指向原来的request了,所以一定要注意
return this.multipartResolver.resolveMultipart(request);
} catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("...", ex);
} else {
throw ex;
}
}
}
}
// 如果前面return的代码没有执行到,就返回原来的request,什么都没执行
return request;
}
这里需要注意的是:org.springframework.web.multipart.support.MultipartFilter
,如果在web.xml中配置这个过滤器的话,则会在过滤器中提前判断是不是文件上传的请求,并将请求转换为MultipartHttpServletRequest
类型。这个过滤器中默认使用的MultipartResolver
为StandardServletMultipartResolver
。
在CommonsMultipartResolve
r中有一个属性叫resolveLazily
。
private boolean resolveLazily = false;
这个属性值代表是不是延迟解析文件上传,默认为false
。最终返回的是一个DefaultMultipartHttpServletRequest
的类。这里有一个重要的方法是:parseRequest
,这个方法干的事是解析文件上传请求。它的底层是commons-fileupload
那一套,不同的是Spring在获取FileItem之后,又进行了一下封装,封装为便于Spring框架整合。
getHandler
getHandler()
主要进行的操作是循环遍历handlerMappings
,从里面匹配一个返回。
SpringMVC
默认加载三个请求处理映射类:RequestMappingHandlerMapping
、SimpleUrlHandlerMapping
、和BeanNameUrlHandlerMapping
。这三个类有一个共同的父类:AbstractHandlerMapping
。
getHandler()
返回的是HandlerExecutionChain
,里面包括handler和拦截器集合。
public class HandlerExecutionChain {
private final Object handler; // 拦截器
@Nullable
private HandlerInterceptor[] interceptors; // 拦截器数组
@Nullable
private List<HandlerInterceptor> interceptorList; // 拦截器集合
// 执行拦截器链的方法
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
// 注意:如果是拦截器返回了false,就立马触发所有拦截器的AfterCompletion 方法。并且马上return false
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
}
我们来看一下getHandler()
方法,handlerMappings
是我们前面初始化好的。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
其中mapping.getHandler()
是由AbstractHandlerMapping
实现的,主要有以下几个功能:
- 查找handler处理器的,具体留给子类去重写实现的, 比如根据URL去查找匹配等等。
- 构建出一个处理器链 ,和handler绑定,并且内部获取所有的拦截器,然后添加到处理器链里面去。
- 如果设置了跨域,配置跨域信息并放入handler中。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这个是留给子类去重写实现的:查找handler处理器的 比如根据URL去查找匹配等等
// 获取hadnler的过程,非常的复杂,后面就提一点点,详细过程不提了
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 构建HandlerMethod和过滤器链的包装类,和handler绑定
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//...log
// 是否是跨域请求
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
//重写的模板方法
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
//方法上@CrossOrigin注解信息
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
//往拦截器链里添加new CorsInterceptor(config)
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandlerInternal
类 AbstractHandlerMethodMapping< T >
,它实现了 InitializingBean
接口,意味着肯定重写了 afterPropertiesSet
方法。
这个方法的重写参考文章: Spring5 源码阅读笔记(5.2.1)AbstractHandlerMapping 里的 afterPropertiesSet
AbstractHandlerMethodMapping#getHandlerInternal
我们来看一下AbstractHandlerMethodMapping
中实现的getHandlerInternal
这个方法吧。
//假设当前访问路径:http://localhost:10000/product/category/list/tree
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//从request对象中获取uri
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // /product/category/list/tree
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock(); // 加读锁
try {
//根据uri从映射关系中找到对应的HandlerMethod对象
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
//把Controller类实例化
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock(); // 解读锁
}
}
AbstractHandlerMethodMapping#lookupHandlerMethod
我们来看一下AbstractHandlerMethodMapping
中getHandlerInternal
调用了lookupHandlerMethod
找到对应的HandlerMethod
对象,我们来看一下实现方法吧。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// getMappingsByUrl(lookupPath) 其实就是 this.urlLookup.get(urlPath)
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果为空,在这个handlerMapping里没找到这个映射
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
AbstractHandlerMethodMapping#addMatchingMappings
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
//T:RequestMappingInfo,如果@RequestMapping和request不匹配就会返回null
T match = getMatchingMapping(mapping, request);
if (match != null) {
//RequestMappingInfo对象和HandlerMethod对象封装到Match对象中
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
getHandlerExecutionChain
获取拦截器链。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//把HandlerMethod对象包装到HandlerExecutionChain对象中,这个对象中有一个拦截器数组
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//获取uri
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//是否有拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
getHandlerAdapter
handlerAdapters
在初始化九大组件的时候已经完成,默认配置在DispatcherServlet.properties
文件中,其中能否适配主要取决于适配器的supports方法如何实现。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
// 如果对应的adapter支持handler,则返回,这由不同的子类实现
return adapter;
}
}
}
// 没有适配器则抛出异常
throw new ServletException("...");
}
默认的HandlerAdapter处理器适配器在DispatcherServlet.properties
配置文件中如下:
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
默认的HandlerAdapter处理器适配器有四个:
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- RequestMappingHandlerAdapter
- HandlerFunctionAdapter
HttpRequestHandlerAdapter
public class HttpRequestHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
}
SimplerControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
}
AbstractHandlerMethodAdapter
下面是 AbstractHandlerMethodAdapter的supports方法,里面的supportsInternal
方法由子类RequestMappingHandlerAdapter
实现,并且固定返回值true
, 而请求处理链中存的就是HandlerMethod
,所以适配器使用的是RequestMappingHandlerAdapter
。
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
}
RequestMappingHandlerAdapter
// RequestMappingHandlerAdapter 继承 AbstractHandlerMethodAdapter
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
}
HandlerFunctionAdapter
public class HandlerFunctionAdapter implements HandlerAdapter, Ordered {
public boolean supports(Object handler) {
return handler instanceof HandlerFunction;
}
}
processDispatchResult
processDispatchResult
是用来处理结果的,渲染视图、处理异常等等的。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//如果有异常,就进入异常处理逻辑,返回到异常页面
if (exception != null) {
// 含有异常页面视图的异常
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("...", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//1、会执行所有的我们的自己配置(或者默认配置)了的HandlerExceptionResolver处理器
//2、上面需要注意了,但凡处理方法返回的不是null,有mv的返回。那后面的处理器就不会再进行处理了。具有短路的效果,一定要注意 是通过null来判断的
//3、处理完成后,得到error的视图mv,最后会设置一个viewName,然后返回出去
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 若视图不为空,不为null,就开始执行render()方法,开始渲染视图了
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
// 如果有错误视图,这里清除掉所有的请求域里的所有的错误属性
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
//处理异步 我们发现,它不执行后面的AfterCompletion方法了,注意一下即可
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
// 执行拦截器的AfterCompletion 方法
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
至此,只剩一个视图渲染的方法:render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 通过localeResolver吧local解析出来,放到response里面去
Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
//==================视图:关键中的关键==================
View view;
String viewName = mv.getViewName();
// 如果已经有viewName了(绝大多数情况)
if (viewName != null) {
// 视图解析器 根据String类型的名字,解析出来一个视图(视图解析器有多个)
// 还是那个原理:只要有一个返回了不为null的,后面的就不会再解析了
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 如果解析不出来视图,那就抛出异常,说不能解析该视图
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
} else {
//没有视图名称,但是必须有视图内容,否则抛出异常
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
try {
//设置响应马 status
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 根据model里的数据,正式渲染(关于此部分逻辑,后续再说,也比较复杂)
view.render(mv.getModelInternal(), request, response);
} catch (Exception ex) {
throw ex;
}
}