SpringMVC源码解析
我们都知道SpringMVC的请求流程是这个样子的:
而在控制器端,最主要的就是DispatcherServlet,我们来看看它的由来:
从图中,我们可以轻而易举的发现:DispatcherServlet说到底就是一个Servlet。说到Servlet,我们就得先来说一说,Servlet的生命周期。
Servlet生命周期:首先加载servlet的class,实例化servlet,然后初始化servlet调用init()的方法,接着调用服务的service的方法处理doGet和doPost方法,最后是我们关闭容器的时候调用destroy 销毁方法。
1. 初始化阶段
类加载及实例化servlet,我们就不说了,直接来说一说init()方法。
HttpServletBean继承了HttpServlet,重写了init()方法
我们一起来看一下这个方法。首先进行了debug日志打印,这一步没啥说的。
来看try{}cathch{}里边的方法。
在刚才的代码中,我们发现有一个属性叫做requiredProperties,这个是用来存放参数的,之后传给servlet用来初始化。
PropertyValues pvs
= new ServletConfigPropertyValues(
getServletConfig(), this.requiredProperties);
这一步加载了我们的web.xml里的参数,来看我的示例。我的web.xml是这样的。
我们在这一步打个断点,看看这一步去到的参数是什么?
取到了我们的初始化参数contextConfigLocation。
接着往下走,
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
↑ 这一步得到了我们的DispatcherServlet实例对象
↑ 这一步得到资源加载器
接下来依次是:为Servlet装配资源编辑器 → 初始化DispatcherServlet → 设置参数。
接下来的重中之重来了,initServletBean()方法,这个方法的注释这样写的“让子类做他们喜欢的任何初始化”。所以此时,我们的重点要放在子类上了,看看子类是如何进行初始化servletBean的。
我们回到FrameworkServlet这一层,来看一下这个类里边重写的initServletBean()方法。注释中这样写道“在任何bean属性之后调用
已经准备好了。创建此servlet的WebApplicationContext。”
在这个方法中,重要的方法就是initWebApplicationContext()
这个方法先去查找根节点上是否存在上下文实例
-
如果存在则会进行赋值操作,将这个上下文实例作为返回的对象,在返回之前会判断这个上下文对象是否是配置类上下文对象,
- 如果是则会创建配置类上下文对象,若这个对象不是存活状态,执行configureAndRefreshWebApplicationContext(),这个方法主要是装配上下文对象
-
如果不存在,则会先执行findWebApplicationContext(),这个方法的主要功能是:在构造时没有注入上下文实例->查看是否有已在servlet上下文中注册。如果存在,则假定父上下文(如果有)已经设置,并且用户已执行任何初始化,如设置上下文ID
-
上述操作后,如果没有注册上下文实例,则会执行createWebApplicationContext(rootContext)方法,意思就是没有我就创建一个本地的上下文对象
一起来看看这个方法createWebApplicationContext(),这个方法里最终会调用configureAndRefreshWebApplicationContext()
这个方法里会增加ApplicationListener来监听servlet事件。
-
判断上下文对象是否是自动刷新,如果上下文不是具有刷新功能的configurableapplicationcontext,在构建时注入的支持或上下文已经
刷新->在此处手动触发初始刷新 -
判断是否应该将上下文发布为servletcontext属性,如果是则进行配置
-
最后将上下文对象返回
至此,初始化工作告一段落,当然以上的分析只是一部分,还有好多方法没有分析到
2. service阶段
这一阶段,才是servlet实际工作的重要部分,接下来我们一起来看看
我们都知道servlet最主要的就是service()方法,DispatcherServlet继承自FrameworkServlet,FrameworkServlet间接的继承了HttpServlet,我们来看重写的service()方法。
这段代码很好理解,获取请求方式,如果是PATCH请求,则调用processRequest()方法,否则,调用父类的service()方法。补充说一下,请求方式现在一共有GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE八种,而PATCH是新增对于PUT的补充,所以在这个service()方法中会这样写,因为父类的service()中只包含7种方法,否则报错,所以说8中请求方式,最终都会调用processRequest()方法。
我们先来看一下父类的service()方法。
这个方法实际上就是对于不同的请求方式进行不同的处理,否则报501错误,究竟怎么处理的各种请求呢,doGet()、doPost()等等这些方法都是在子类里重写的,我们拿一个doGet()来看。
我们看到了主要的方法就是processRequest(),让我们继续跟进去
这里的发布事件,Spring在请求处理结束后会发布一个ServletRequestHandledEvent类型的事件,可以通过ApplicationListener接收。
这个方法前面和后面做的工作是保留现场,请求处理结束后恢复现场。真正处理请求的方法是doService。这个方法在DispatcherServlet中.
这个方法主要是把现在有的一些参数比如上下文对象加到Request中,然后转发到doDispatch方法去处理,终于到了最最最最关键的方法了。
checkMultipart()
首先来看这个方法,checkMultipart(),这个方法主要处理的就是我们常用的文件上传请求,如果是文件上传请求,将该请求封装为标准servletrequest请求。
getHandler()
接下来一个重要的方法getHandler(),
我们在这段代码中,可以看出,这个方法的返回值是HandlerExecutionChain类型,意思就是“处理程序执行链”,在这段代码中,核心的代码就是 AbstractHandlerMapping类中的getHandler(request)方法。在看这个方法之前,我们先来看一下handlerMappings,这段代码中循环的handlerMappings是什么时候有的值呢?里边的值又是什么呢?
这我们就要来看一下initWebApplicationContext()方法里的onRefresh()方法
initStrategies()这个方法里边进行很多组件的初始化工作,前三个时初始化解析器,点进去可以看到三个方法里边就是解析器的名字不一样,做的工作都是一样的,这里我就只贴关键代码了。
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
接下来,我们来看一下initHandlerMappings(context)方法,其实我们只看这个方法名和参数就可以知道,它是从上下文实例中,获取handlerMapping放到handlerMappings集合中。是不是像我们想的这样呢?
确实像我们想的那样,获取了上下文实例中的handlerMapping放到handlerMappings集合中,那我们就打个断点看看例子中handlerMappings集合中都有什么?
接着看
发现了什么?我们自己在配置文件写的接口被添加进去了
既然是已经被添加进去了,我们就接着之前的请求到达getHandler(request)方法来看。
这个里边有两个主要方法getHandlerInternal(request)、getHandlerExecutionChain(handler, request),先来看getHandlerInternal(request)
这个方法里边呢,又有两个重要方法getLookupPathForRequest和lookupHandlerMethod,说一下实现原理吧,其实RequestMappingHandlerMapping在初始化的时候已经将系统中所有的@RequestMapping注解解析了,放在一个Map里面。实现过程如下:
RequestMappingHandlerMapping的父类AbstractHandlerMethodMapping实现了InitializingBean接口,在Bean设置完参数后会调用afterPropertiesSet方法,而它在这个方法里面做了初始化的工作。
另一个方法getHandlerExecutionChain(),就是默认实现只是使用给定的处理程序、处理程序映射的公共侦听器和任何与当前请求URL匹配的mappedInterceptor生成一个标准的handlerExecutionChain.
其实这个方法就是加了自定义拦截器
这样的话,getHandler()方法我们就分析的差不多了,接下来来看
getHandlerAdapter()
接下来来看getHandlerAdapter()方法
我们前边提过initHandlerAdapters(ApplicationContext context)已经初始化了handlerAdapters这个集合,所以这一步应该很好理解了.
handle()
我们直接来看AbstractHandlerMethodAdapter里的handle方法
handleInternal()实际上调用的是子类实现的方法
在这个里边主要的方法有两个checkAndPrepare()和invokeHandleMethod()
我们来看一下checkAndPrepare()做了什么工作
这个方法根据此生成器的设置检查并准备给定的请求和响应。检查支持的方法和所需的会话,并应用给定的缓存秒数,applyCacheSeconds()就是用来处理缓存时间戳
所以重要的还是这个方法invokeHandleMethod()
主要执行的方法是invokeAndHandle(),我们来看看
这个方法里主要的方法就是invokeForRequest(),我们跟进去看看
再来看这个方法里边的getMethodArgumentValues()
在这个方法里边的supportsParameter()方法,这个方法实际上调用了supportsParameter()方法,这个方法就是调用每个Resolver的support方法,看看是否能够进行类型转换。类型处理器在argumentResolvers.resolveArgument处理完参数后,会把request的参数转成一个Object[]的列表返回,就是Controller中方法的参数列表。
再来回头看invokeForRequest()中的invoke()方法
实际上就是带着所有参数,去执行方法
视图及数据的返回
applyDefaultViewName(request, mv)方法主要是如果没有视图名,则设置视图的名字,mappedHandler.applyPostHandle(processedRequest, response, mv)主要是拦截器的操作
来看下一个方法processDispatchResult()
在这里边主要调用了render方法
方法里的resolveViewName()
至此我们获得了ModelAndView