转载

从源码入手分析SpringMVC的原理

SpringMVC作为控制层框架,具体的作用就不在此赘述了,本文主要针对其处理请求流程的原理来做一次较为细致的讲解

首先来看经典的MVC的三层架构,下面是一个模拟请求的调用和返回,我们把重点放在中间的控制层:

从源码入手分析SpringMVC的原理

SpringMVC就是用于管理这一层,负责将视图层发来的请求发送给下一层,然后再将结果返回,听起来很简单,但是真的这么简单吗?接下来,我们就来渐进式的分析,来研究其工作的原理

准备工作

首先我们选用的是springboot的2.1.4发布版,查看一下依赖树,发现springmvc的版本是5.1.6的版本,正好可以和现在网络上大部分针对3.x版本的源码讲解做一个比较

从源码入手分析SpringMVC的原理

这里为了不做多余的功夫,一切从简,就只添加了以下的类:

从源码入手分析SpringMVC的原理

具体的类信息如下

@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...";
    }
}
复制代码

我们启动测试一下:

从源码入手分析SpringMVC的原理

一切正常,接下来我们就要开始着手分析

工作原理

从刚才的测试我们会发现,我们发送了一个请求(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中的方法中,具体的执行流程就在上面,对一些重要的步骤我已经标注了注释,这里抛出掉多部分请求处理和异常捕获的部分,整个执行流程可以分为以下步骤:

  1. 获取请求对应的执行链
  2. 根据执行链的处理器来寻找能够适配该处理器的适配器
  3. 如果是Get请求,检查资源最后修改的时间,如果资源的最后修改时间早于请求发出的时间,则直接返回,让浏览器使用缓存
  4. 执行前置拦截方法
  5. 触发具体逻辑(我们写在controller中的方法)
  6. 执行后置处理方法
  7. 对最后的结果进行处理(使用返回的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的集合,就是下面这样:

从源码入手分析SpringMVC的原理
这些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()

最后再来看一个收尾的方法,这个方法就是用来对最终结果做处理,主要是负责将模型数据渲染到视图中,在如今前后端分离的项目中,这个方法的价值也越来越小了,不过还是有必要了解一下的,整个方法的源码我这里就不放了,其步骤主要就是两步:

  1. 如果抛出异常,就选择对应的异常处理器来处理
  2. 如果模型数据或视图不为空,就选择视图解析器来解析并渲染视图

没什么太难理解的地方,解析的含义就是根据视图名来找到对应的视图对象,渲染就是把模型数据填充到视图中,我这里就放一段解析视图名的resolveViewName()方法中的一段,其中会遍历查找以寻找合适的视图解析器:

if (this.viewResolvers != null) {
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
复制代码

总结

整个springmvc的核心流程已经讲完了,最后放一张具体的流程图来加深大家的理解

从源码入手分析SpringMVC的原理
原文  https://juejin.im/post/5cef629f5188257458417193
正文到此结束
Loading...