@TOC# 热门框架系列
记录在程序走的每一步___auth:huf
从新的篇章开始;篇章阅读需要先关注; 因为笔者想参与技术文章的评选.;需要一定的粉丝量; 粉丝量达到一定数量.所有文章阅读限制将会全面放开;谢谢大家的支持~
我们从今天开始 就开始了新的一个篇章 这个系列为长文篇章; 一篇文章 就是一个知识点原理 文章序号 是作者写文章的序号; 并非跟Spring原理系列一样 学习的序号; 每个篇章关联并不是特别紧密;
Spring M-V-C 为什么我有点迫切的想讲. 因为在我们SpringBoot 里面 就自动组装了Spring M-V-C的源码; 采用了Spring M-V-C的核心思想; SpringBoot 是我们以后要说的组件的基础. 或者是SpringCloud 原生那一套分布式处理方案 .是最基本架构的 核心原件之一. SpringBoot 自动装置了配置; 使得我们既插即用. 非常的方便;我们也知道 在网上有非常多的文章写SpringMVC 都非常得优秀;
作者尝试着 从Spring的角度 解析SpringM-V-C 我们这个篇章是述说SpringMVC吗? 我想 并不是的 这个篇章 我们书述说的到底是什么样的一个技术点?; SpringBoot 是SpringMVC吗? 是! 但又也不完全是 ! SpringMVC是SpringBoot吗? 不是!完全不是!
SpringBoot 在我的理解; 是一种思想. SpringBoot 是一种装配思想; 只不过它恰巧装配了Spring M-V-C 而已;
SpringMVC 在启动的时候做了什么? 一定要耐心看完; 跟你们平时理解的 会更细腻; 更通透;
首先 在我们Spring 源码篇 我们介绍了一个接口 叫做 InitializingBean 的接口 该接口的实现了一个方法 叫做 afterPropertiesSet 凡是我们作为 InitializingBean 实现类; 再Spring启动的,类初始化的时候. 我们都会进行对afterPropertiesSet调用;
我们的Spring MVC 在启动的时候 就做了这么一件事情;
AbstractHandlerMethodMapping 实现了 InitializingBean 在启动的过程中. 执行了 afterPropertiesSet 方法;
以下是图
那么在启动的时候就会调用 afterPropertiesSet 我们就以这里为基础 一步一步往下深究;
这里 通过 getCandidateBeanNames() 拿到所有Bean 的名字. 进行挨个匹配; 这里我们快速阅读; 不留念这个;
当我们通过 processCandidateBean(String beanName) 方法 里面的isHandler 判断的时候; 这里判断主要判断2个;
这里主要判断2个 第一个 就是类型是否是 Controller的类型 或者是RequestMapping的类型; 如果是就返回true;
返回True 我们就会进入到detectHandlerMethods(Object handler) 方法中;
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
"他会通过这个selectMethods 找到该Controller的所有的method"
"以下几个点很观点 它会把Method对象作为Key, 路径作为value 保存在这个Map中"
"下面会有图文解释"
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
else if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappings(userType, methods));
}
"开始遍历该Controller的所有方法对象. "
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
"然后进入这个registerHandlerMethod 方法; "
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
然后调度 register(T mapping, Object handler, Method method) 方法; 以下为源码Copy部分
public void register(T mapping, Object handler, Method method) {
"加锁.我们之后有篇章专门讲JUC里面的锁机制;并发;以及其使用"
this.readWriteLock.writeLock().lock();
try {
"这个封装了 很关键的 HandlerMethod"
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
"获取其路径"
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
"把路径作为作为Key mapping 作为Key 进行保存."
"mapping 里面其实就是很多不同类型的Condition"
"图下会有解释;"
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
"设置HandlerMethod"
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
""
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
mapping参数查看;
mapping 会再之前 detectHandlerMethods 回调方法中 getMappingForMethod进行设置 最后builder (会在这里 通过设计模式 进行Bean的实例化;)
这就是一个Controller 在Spring启动中加载进容器 或者是 装配到 一个名为 pathLookup 中的全过程; 这是前置知识点; 也是必须要了解的; 很多同学私下也问我; 在讲解Spring的时候;希望可以再细致 细腻一些; 作者也努力去把这一块东西讲得透彻; 接下来就进入到了SpringMVC的基本常识.
从深到浅; 以上 我们讲解了Spring 启动的时候 如何把Controller作为配置 加载进入pathLookup 我们根据pathLookup 作为一个垫脚石; 开始详细分析我们SpringMVC 的启动流程; pathLookup有什么作用?
Servlet 的生命周期是什么?
我们SpringBoot 使用了自动化配置. 但是核心原理不变. 我们的SpringMVC 核心类: DispatcherServlet;; 我们在玩SpringMVC的时候. 我们首先要做的第一件事 就是 在web.xml里面配置DispatcherServlet; 使用其方法拦截我们所有的请求; 分发我们所有的请求;
我们假设说 我们的容器已经启动成功; 现在开始 我们现在将会进行请求
(家里电脑快要报废;没有谷歌 或者火狐; 就随意用了一个游览器.并不影响我们对原理的追溯)
DispatcherServlet;
这个类不陌生吧? 这个类 DispatcherServlet 的父类是->FrameworkServlet 的父类是-> HttpServletBean 的父类是-> HttpServlet 的父类是-> GenericServlet 实现了 Servlet 以上Servlet的生命周期 也就是DispatcherServlet的生命周期; 只不过它的功能更加强大; 我们请求进来被捕获到;
下面我们开始了 SpringMVC 的主流程运转过程:
我们请求的 所有URL 都会被 FrameworkServlet 的service进行拦截; 是所有请求;不管Url后面请求路径是否正确; 凡是这这个端口的请求(包括项目名字)都会被该方法拦截.
该段源码的逻辑很简单。就是获取到请求信息里面的httpMethod; heetMethod 放的 就是Post,Get,Put,Patch,Delete…等等; 在这个方法里面 凡是htteMethod == null的 或者是 PATCH 的就会调用processRequest;其他的 调用HttpServlet 进行分发;
就是得到请求是什么 是Post 还是 Get 或者其他; 我们最后进入super.service 实际上就会进去HttpServlet 的service方法;
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
"得到httpServlet 就获取到Get 或者 Post 以下源码非常简单。"
"各位同学请自读就行; 我们以Post为例子;"
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
"假设我们 是Post 的请求. 最终会调用HttpServlet 的doPost方法 "
"并且将 request response传入;"
"首先我问一下。 这个this 指的是哪个对象?"
"是HttpServlet 吗? 还是说不是HttpServlet? 下面有图给出答案"
"但是作者希望读者能够自己心里揣着一个答案 往下看"
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
但是DispatcherServlet 没有 doPost方法! 只有他的父类 实现了doPost方法 也就是我们的FrameworkServlet.
来到我们的FrameworkServlet.doPost 方法 :
这里面直接调用了 processRequest
这里注解写得很清楚 只管处理请求 不会去处理结果 结果交给Do service进行处理;
在doService 之前 是封装了 ServletRequestAttributes 实际上就是request response;
这里源码不去细看;因为跟整体流程关系并不是非常大。 学习源码就是 高速公路一路上的风景;或者有警示牌; 或者有加油站;我们要顺着流程继续往下走。 最终到达终点; 其中不明白的地方 我们可以回头再细细的研究;
我们继续往下 到doService 就是我们 DispatcherServlet 的doService 方法中;
@Override
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 上下文参数进行装在。Web_application_context"
"这里留一个悬念; 这个悬念很重要; 如果之前看过源码的同学 应该知道"
"这是什么。之后会有一片详细的文章 介绍"
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 {
"我们纵观我们的do Service方法 他们就是为Request 设置了一堆 Attribute"
"然后委托 doDispatch 调度"
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
开始调度我们的核心方法 DispatcherServlet doDispatch 我们的所有SpringMVC的原理 在市面上的面试 都基本上围绕这个doDispatch 展开的. 如果说问到了doDispatch 前置知识. 在doDispatch前面 做了什么; 细节方面 估计没有多少个人会回答出来; 以下我会把原理 用 ①②③…点全部标记出来 然后配上网上的一张图; 就会很清晰了;
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
"1:我们请求进来 DispatcherServlet "
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
"WebAsyncManager主要用来管理异步请求的处理"
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
"ModelAndView 是返回模型; 目前仅仅是申明 在这里明没有得到具体的 modelAndview"
ModelAndView mv = null;
Exception dispatchException = null;
try {
"封装了当前传入的request请求信息;"
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
"我们在该方法体内 可以跟前面 的pathLookup 对应起来 在getHandler"
"在getHandler方法内部,会根据url 去 pathLookup 里面取MappingInfo"
"2 根据URL 去寻找 HandlerMapping 然后 返回 mappedHandler 回来 DispatcherServlet"
mappedHandler = getHandler(processedRequest);
"如果根据url没找到相对应的 MappingInfo信息 抛出异常;"
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
"3:我们根据 mappedHandler 去找到 HandlerAdapter "
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
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;
}
"4: 通过HandlerAdapter 调用我们具体的requestMapping 请求方法"
"也就是Controller里面的方法 mv 也就是ModelAndView 返回ModelAndView"
"执行完 handle 方法之后 就返回到游览器了;"
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
"找到相对应的是图名称 然后放在ModelAndView的 ViewName 里面"
applyDefaultViewName(processedRequest, mv);
"里面执行HandlerInterceptor 拦截器;"
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
"5: DispatcherServlet将ModelAndView传给ViewReslover视图解析器"
"ViewReslover解析后返回具体View"
"6: 这个不再详细说render方法, 通过render方法 解析后返回具体View"
" DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)"
"DispatcherServlet响应用户。"
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);
}
}
}
}
以下是 流程配图:
这样 我们就详细 且明白了 SpringMVC 的核心源码; 最后我们总结一波:
1 Spring 如何讲SpringMVC 无缝承接; (主讲)
通过 InitializingBean 在Spring 容器 启动的时候 调度afterPropertiesSet 封装 所有Controller 里面的requestMapping 请求; 封装成为MappingInfo 对象 在一个叫做 pathLookup 的Map 里面;
2 SpringMVC 的 doDispatch 处理请求的原理;(随讲)
DispatcherServlet: 前端调度器 , 负责将请求拦截下来分发到各控制器方法中 HandlerMapping: 负责根据请求的URL和配置@RequestMapping映射去匹配, 匹配到 会返回Handler(具体控制器的方法)
HandlerAdaper: 负责调用Handler-具体的方法- 返回视图的名字 Handler将它封装到
ModelAndView(封装视图名,request域的数据)
ViewReslover: 根据ModelAndView里面的视图名地址去找到具体的jsp封装在View对象中
View:进行视图渲染(将jsp转换成html内容 --这是Servlet容器的事情了) 最终response 到的客户端
随后我们会扩展 SpringMVC 父子容器篇;