概述
SpringMVC作为控制层框架,具体的作用就不在此赘述了,本文主要针对其处理请求流程的原理来做一次较为细致的讲解
首先来看经典的MVC的三层架构,下面是一个模拟请求的调用和返回,我们把重点放在中间的控制层:
SpringMVC就是用于管理这一层,负责将视图层发来的请求发送给下一层,然后再将结果返回,听起来很简单,但是真的这么简单吗?接下来,我们就来渐进式的分析,来研究其工作的原理准备工作
首先我们选用的是springboot的2.1.4发布版,查看一下依赖树,发现springmvc的版本是5.1.6的版本,正好可以和现在网络上大部分针对3.x版本的源码讲解做一个比较
这里为了不做多余的功夫,一切从简,就只添加了以下的类: 具体的类信息如下@RestController
public class DemoController {
private final DemoService demoService;
public DemoController(DemoService demoService) {
this.demoService = demoService;
}
@RequestMapping("hi")
public String sayHi() {
return demoService.getInfo();
}
}
@Service
public class DemoService {
public String getInfo() {
return "Hello World...";
}
}
我们启动测试一下:
一切正常,接下来我们就要开始着手分析工作原理
从刚才的测试我们会发现,我们发送了一个请求(url)给springmvc,然后springmvc就执行了我们定义的sayHi()方法,接着我们的浏览器就收到了响应,这个过程究竟是怎么实现的呢?别急,我们在sayHi()方法处打上断点,进入调试模式
我们依然发起localhost:8080/hi
这个请求,程序停在了断点位置,同时也得到了一条调用链。我们沿着调用链从启动位置向上找,我们忽略所有core包下的内容,只找web包下的类,发现请求经过HttpServlet层层封装,最终交给了一个叫DispatcherServlet的类来处理
DispatcherServlet -- doService()
这个DispatcherServlet内部有一个doService方法,我们就以此作为起点,来分析这个请求的过程是怎么样的
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(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));
}
}
}
// 设置属性,使之可供处理程序和视图对象使用
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);
}
try {
// 执行具体的分发操作
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// 如果是include请求,通过之间的快照来恢复属性
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
整个操作具体可分为4个部分:
- 保存快照
- 设置属性
- 执行操作
- 恢复属性(如果是include请求)
这里先解释一下什么是include请求,include请求指在一个Servlet请求中包含了另一个请求,清楚了这一点,我们接着看处理流程,核心方法就是一个doDispatch方法,我们直接继续进入该方法
DispatcherServlet -- doDispatch()
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 {
// 将请求转化为多部分请求,如果没设置多部分解析,就使用原有的请求
processedRequest = checkMultipart(request);
// true表示开启了多部分解析
multipartRequestParsed = (processedRequest != request);
// 来确定具体的处理程序,返回结果是一个执行链
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 来确定处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 检查最后修改的标头
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
// 根据上次修改的时间戳来判断资源是否已被修改,如果资源未被修改,则直接返回,浏览器将使用缓存
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行前置拦截方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 触发具体的逻辑方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 返回true表示正在进行并发处理,应保持响应打开状态
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 执行后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
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()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
这个方法就是处理具体的分发流程,也就是说将我们的url对应到具体的controller中的方法中,具体的执行流程就在上面,对一些重要的步骤我已经标注了注释,这里抛出掉多部分请求处理和异常捕获的部分,整个执行流程可以分为以下步骤:
- 获取请求对应的执行链
- 根据执行链的处理器来寻找能够适配该处理器的适配器
- 如果是Get请求,检查资源最后修改的时间,如果资源的最后修改时间早于请求发出的时间,则直接返回,让浏览器使用缓存
- 执行前置拦截方法
- 触发具体逻辑(我们写在controller中的方法)
- 执行后置处理方法
- 对最后的结果进行处理(使用返回的Model来渲染视图)
这就是整个springmvc的核心流程,我们这里只关注第一个部分:获取请求对应的执行链,也就是getHandler()方法
DispatcherServlet -- getHandler()
这个方法虽然简单,但是因为这是整个方法的核心部分,我认为还是有必要拿出来说的,我们来看这个方法的源码:
@Nullable
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;
}
整个流程很容易懂,就是单纯的遍历handlerMappings这个集合,然后根据请求找到对应的处理器,听起来很简单,但是这里面是有门道的,我们先来看handlerMappings这个属性,其属性定义如下:
private List<HandlerMapping> handlerMappings;
原来是一个HandlerMapping的集合,就是下面这样:
这些HandlerMapping都是用于定义请求到处理器的映射,这一点很重要,因为我们的每一个请求最终都是要对应到具体的处理器方法上的,而我们真正要看的就是RequestMappingHandlerMapping这个类,这里面封装了我们自己编写的controller中的方法,在这个类中,有下面这样一个属性: private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
这里面保存的就是我们的请求到具体方法的映射,整个RequestMappingHandlerMapping类就是通过@RequestMapping注解来创建我们的RequestMappingInfo实例,而RequestMappingInfo这个实例就封装了我们具体需要执行的方法,在调用时就可以通过反射来创建具体的controller实例,然后调用对应的方法即可
这些映射关系都是在一开始随着ApplicationContext进行加载,当加载完成后,我们的请求映射关系也随之确定了下来,接着就可以根据请求来判断具体要调用的是哪些方法了
最后再回来看getHandler()方法,这个方法最终会返回一个HandlerExecutionChain,其中封装了处理器和拦截器,如下:
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
接下来的调用就完全依赖这两个属性,相信后面的步骤大家也应该都了解了
DispatcherServlet -- processDispatchResult()
最后再来看一个收尾的方法,这个方法就是用来对最终结果做处理,主要是负责将模型数据渲染到视图中,在如今前后端分离的项目中,这个方法的价值也越来越小了,不过还是有必要了解一下的,整个方法的源码我这里就不放了,其步骤主要就是两步:
- 如果抛出异常,就选择对应的异常处理器来处理
- 如果模型数据或视图不为空,就选择视图解析器来解析并渲染视图
没什么太难理解的地方,解析的含义就是根据视图名来找到对应的视图对象,渲染就是把模型数据填充到视图中,我这里就放一段解析视图名的resolveViewName()方法中的一段,其中会遍历查找以寻找合适的视图解析器:
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
总结
整个springmvc的核心流程已经讲完了,最后放一张具体的流程图来加深大家的理解