热门框架系列 (一) -- Spring M-V-C 的 “ 核心源码 “;

@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 的生命周期是什么?

Init
service
doGet
doPost
destroy

我们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 父子容器篇;

see you !

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Like Java Long Time

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值