Spring MVC的原理

Spring MVC中的MVC即模型-视图-控制器,该框架围绕一个DispatcherServlet设计而成,DispatcherServlet会把请求分发给各个处理器,并支持可配置的处理器映射和视图渲染等功能。Spring MVC的具体工作流程如下:
(1)客户端发起HTTP请求:客户端将请求提交到DispatcherServlet
(2)寻找处理器:由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理该请求的Controller
(3)调用处理器:DispatcherServlet将请求提交到Controller
(4)调用业务处理逻辑并返回结果;Controller在调用业务处理逻辑后,返回ModelAndView
(5)处理视图映射并返回模型:DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
(6)HTTP响应:视图负责将结果在客户端浏览器上渲染和展示。
在这里插入图片描述核心代码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);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            /*
            	mappedHandler:调用链
                包含handler、interceptorList、interceptorIndex
            	handler:浏览器发送的请求所匹配的控制器方法
            	interceptorList:处理控制器方法的所有拦截器集合
            	interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
            */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
           	// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
            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;
                }
            }
			
            // 调用拦截器的preHandle()
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            // 调用拦截器的postHandle()
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            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()) {
            // 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);
            }
        }
    }
}

HandlerMapping介绍

HandlerMapping在这个SpringMVC体系结构中有着举足轻重的地位,充当着url和Controller之间映射关系配置的角色。HandlerMapping是接口,Spring MVC提供了一系列HandlerMapping的实现,根据一定的规则选择controller。如果当前的HandlerMappign实现中没有能够满足你所需要的规则是,可以通过实现HandlerMapping接口进行扩展。它主要有三部分组成:HandlerMapping映射注册、根据url获取对应的处理器、拦截器注册。
总结
HandlerMapping是处理器映射器,根据请求找到处理器Handler,但并不是简单的返回处理器,而是将处理器和拦截器封装,形成一个处理器执行链(HandlerExecuteChain)。
核心伪代码如下:
getHandler伪代码

 1 // 获取处理器执行链对象
 2 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 3    // SpringMVC默认实现三种类型的HandlerMapping: 
 4    // RequestMappingHandlerMapping、BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping
 5    if (this.handlerMappings != null) {
 6       // 遍历MVC容器中的处理器映射器
 7       for (HandlerMapping mapping : this.handlerMappings) {
 8          // 获取请求对应的处理器执行链
 9          HandlerExecutionChain handler = mapping.getHandler(request);
10          // 处理器执行链不为空
11          if (handler != null) { 
12             // 返回匹配到的处理器执行链 
13             return handler;  
14          }
15       }
16    }
17    // 返回null
18    return null;  
19 }

getHandler获取执行器链

1 // 为请求获取handler处理器,若没有Handler处理器,获取mvc默认的handler处理器
 2 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 3    // 获得处理器(HandlerMethod或者HandlerExecutionChain),该方法是抽象方法,由子类实现
 4    Object handler = getHandlerInternal(request);
 5    // 获得不到,则使用默认处理器
 6    if (handler == null) {
 7       handler = getDefaultHandler();
 8    }
 9    // 获得不到,则返回 null
10    if (handler == null) {
11       return null;
12    }
13    
14    // 如果找到的处理器是String类型,则从Spring容器中找到对应的Bean作为处理器
15    if (handler instanceof String) {
16       String handlerName = (String) handler;
17       handler = obtainApplicationContext().getBean(handlerName);
18    }
19 
20    // 创建HandlerExecutionChain对象(包含处理器和拦截器)
21    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
22 
23    // 跨域请求的处理
24    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
25       CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
26       CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
27       config = (config != null ? config.combine(handlerConfig) : handlerConfig);
28       executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
29    }
30 
31    return executionChain;
32 }

getHandlerExectuionChain 构建执行器链

 1 //  为handler处理器构建HandlerExecutionChain对象,包含应用拦截器
 2 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
 3    // 创建 HandlerExecutionChain 对象
 4    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
 5          (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
 6 
 7    // 获得请求路径
 8    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
 9    // 遍历 adaptedInterceptors 数组,获得请求匹配的拦截器
10    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
11       // 需要匹配,若路径匹配,则添加到 chain 中
12       if (interceptor instanceof MappedInterceptor) {
13          MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
14          if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
15             chain.addInterceptor(mappedInterceptor.getInterceptor());
16          }
17       }
18       // 无需匹配,直接添加到 chain 中
19       else {
20          chain.addInterceptor(interceptor);
21       }
22    }
23    return chain;
24 }

相关问题

1.为什么不直接调用对应handler处理请求,而是先找到适配器
提到适配器,就知道它应用了适配器模式。
为什么要用适配器模式呢?
因为控制器接受请求的方式不同,即实现Controller的方式不同。有三种实现方式,一种是实现Controller接口方式,一种是用@Controller注解的方式,还有一种是直接接受的Http请求。
不同的Controller的实现方式,如果用if…else判断去处理的话,就会违背开放封闭原则 。

if(controller typeof  @Controller){
	处理@Controller注解逻辑
}
if(controller typeof ControllerInterface){
	处理实现controller接口的逻辑
}
if(controller typeof http){
	处理实现http请求类型的逻辑
}

因为多加一种controller实现方式就得多一个判断。因此SpringMVC就引入了适配器模式
而HandlerAdapter组件具有接口转换的能力,可以统一使用该接口来处理不同的controller的实现。
方便controller的扩展。
这里就是把变化的controller上面建立一层统一处理层,封装、屏蔽底层的controller实现方式的变化。
在这里插入图片描述

2.HandlerExecutionChain使用了什么设计模式,结合源码分析
责任链模式属于三种设计模式分类中行为型模式,责任链模式为请求对象创建一个接收者处理链,通常接收者中会存在下一个接收者的引用,如果当前接收者无法处理当前请求时,则将请求对象传递到下一个接收者进行处理,从而实现责任链,直到可以处理请求对象为止。责任链模式中请求发送者无需知道是哪个接收者处理的请求对象以及请求对象处理的具体过程,能够很好的将请求发送者和接收者进行解耦。责任链模式通常用于各种过滤器链、拦截处理链中,例如JavaWeb中的Filter过滤器等等。Spring框架中责任链模式的应用有很多,包括web请求设计,security安全权限设计,aop切面设计等等。

SpringMVC中实际上是基于Servlet的框架,当客户端发送请求到web时,都会进入DispatcherServlet中,然后根据Servlet的生命周期去执行doService方法,在doService方法中有一个关键方法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);
				multipartRequestParsed = (processedRequest != request);
				//获取处理对象
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(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());
				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);
				}
			}
		}
	}

HandlerExecutionChain的责任链模式,将需要执行的HandlerInterceptor拦截器,添加在HandlerExecutionChain责任链的interceptorList集合中,然后依次执行HandlerInterceptor相应的处理方法。以preHandle方法为例,在处理方法中,根据返回的处理boolean判断是否要继续进行下面的HandlerInterceptor对象的处理,最终完成整个调用链的拦截处理。HandlerExecutionChain的实现实际上是责任链模式的一种变性用法,它减少了不同处理对象的引用依赖,采用集合的方式来存放处理链对象,降低了耦合度

	//HandlerExecutionChain的applyPreHandle方法
	//责任链模式,将处理对象给接收者,并不关心内部如何处理与调用
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
			for (int i = 0; i < this.interceptorList.size(); i++) {
				HandlerInterceptor interceptor = this.interceptorList.get(i);
				//调用拦截器的preHandle方法,执行所有拦截器的preHandle处理方法
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
			return true;
	}
 

参考:http://www.manongjc.com/detail/41-uiajoseglafvxgf.html
https://blog.csdn.net/shang_0122/article/details/119335479
转载:https://blog.csdn.net/weixin_41605937/article/details/133376446

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/331261.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度解析 Compose 的 Modifier 原理 -- Modifier.layout()、LayoutModifier

" Jetpack Compose - - Modifier 原理系列文章 " &#x1f4d1; 《 深入解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》 &#x1f4d1; 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》 &#x1f4d1; 《 深…

redis安装-Linux为例

可以下载一个Shell或者MobaXterm工具&#xff0c;便于操作 在redis官网下载压缩包 开始安装 安装依赖 yum install -y gcc tcl切换目录 切换目录后直接把redis安装包拖到/user/local/src/下 cd /user/local/src/解压然后安装 #解压 tar -zxvf redis-7.2.4.tar.gz #安装 …

C语言——小细节和小知识12

一、倒置句子 将句子中的单词位置倒置&#xff0c;标点不用倒置&#xff0c;例如i love you.倒置结果是&#xff1a;you. love i。 1、两步翻转法 采用两步翻转法来实现单词位置的倒置。首先&#xff0c;它整体翻转整个字符串&#xff0c;然后再逐个翻转每个单词内的字符。 …

环形链表问题

环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&a…

如何录制屏幕视频?让视频制作更简单!

随着数字化时代的来临&#xff0c;录制屏幕视频成为一种常见的传播和教学方式。无论是制作演示文稿、教学视频&#xff0c;还是记录游戏操作&#xff0c;屏幕录制为用户提供了强大而灵活的工具。可是您知道如何录制屏幕视频吗&#xff1f;本文将深入介绍两种常见的屏幕录制方法…

vue el-select自定义搜索选择案例

开发中常见的有选择框并且输入关键词可以快速检索功能&#xff0c;刚好这次项目需求&#xff0c;就开始吧 需求&#xff1a;1、生成1000到100000的数可选择&#xff0c;递增1000 2、这些数必须三位数用逗号隔开&#xff0c;比如1,000.00这样的形式显示 3、输入关键词比如10&am…

zabbix监控平台(agent端)

引言&#xff1a;明人不说暗话&#xff0c;上一篇文章我们讲了zabbix的serrver端部署和配置&#xff0c;今天详细讲解一下agent端服务器&#xff08;客户端&#xff09;的配置和关联 1.进入官网 Zabbix&#xff1a;企业级开源监控解决方案 2.进入下载页面选择需要下载的版本信…

【JVM】JVM概述

JVM概述 基本介绍 JVM&#xff1a;全称 Java Virtual Machine&#xff0c;即 Java 虚拟机&#xff0c;一种规范&#xff0c;本身是一个虚拟计算机&#xff0c;直接和操作系统进行交互&#xff0c;与硬件不直接交互&#xff0c;而操作系统可以帮我们完成和硬件进行交互的工作特…

【网络安全】2024年一个漏洞4w+,网安副业挖SRC漏洞,躺着把钱挣了!

一个漏洞奖励2w&#xff0c;这是真实的嘛&#xff01; 作为资深白帽&#xff0c;入行网安这些年也一直在接私活&#xff0c;副业赚的钱几乎是我工资的三倍&#xff01;看到最近副业挖漏洞的内容非常火爆&#xff0c;我便决定将自己的经验分享出来&#xff0c;带我的粉丝们一起…

Vue3在点击菜单切换路由时,将ElementPlus UI库中el-main组件的内容滚动恢复到顶部

功能&#xff1a;Vue3在点击菜单切换路由时&#xff0c;将页面el-main的内容滚动到顶部&#xff0c;布局如下&#xff0c;使用UI组件库为ElementPlus 在网上搜很多都是在route.js中的router.beforeEach中使用window.scrollTop(0,0) 或 window.scrollTo(0,0) 滚动&#xff0c;但…

深入Docker5:安装nginx部署完整项目

目录 准备 为什么要使用nginx mysql容器构建 1.删除容器 2.创建文件夹 3.上传配置文件 4.命令构建mysql容器 5.进入mysql容器&#xff0c;授予root所有权限 6.在mysql中用命令运行sql文件 7.创建指定数据库shop 8.执行指定的sql文件 nginx安装与部署 1.拉取镜像 2…

c语言:用一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。

题目 用一个宏&#xff0c;可以将一个整数的二进制位的奇数位和偶数位交换。 如&#xff1a;01&#xff0c;是1&#xff0c;交换完是10&#xff0c;是2. 思路 1.分别取出奇数位上的数字和偶数位上的数字 举个例子&#xff1a;1001 0110 1001 0110 奇…

4. 示例:更改监听端口

默认Spring Boot启动是监听在8080上的。 如果8080被使用&#xff0c;就会报以下错误。 这个时候可以更换一个新的端口。 server: port: 8180 然后再启动&#xff0c;启动成功并且绑定到端口8180。

【计算机硬件】2、指令系统、存储系统和缓存

文章目录 指令系统计算机指令的组成计算机指令执行过程指令的寻址方式&#xff08;怎么样找到操作数&#xff1f;&#xff09;1、顺序寻址2、跳跃寻址 指令操作数的寻址方式&#xff08;怎么样找到操作数&#xff1f;&#xff09;1、立即寻址方式2、直接寻址方式3、间接寻址方式…

JRT和springboot比较测试

想要战胜他&#xff0c;必先理解他。这两天系统的学习Maven和跑springboot工程&#xff0c;从以前只是看着复杂到亲手体验一下&#xff0c;亲自实践的才是更可靠的了解。 第一就是首先Maven侵入代码结构&#xff0c;代码一般要按约定搞src/main/java。如果是能严格执行测试的项…

Windows系统还原打印机系统教程

同时按下键盘WinR键&#xff0c;在运行窗口输入services.msc后点击确定或者回车。 找到Print Spooler&#xff0c;选中右键&#xff0c;点击停止。 复制地址&#xff1a;C:\Windows\System32\spool\drivers 打开我的电脑&#xff0c;复制到地址栏后回车&#xff0c;分别删除文件…

Tomcat10.X部署老版本axis2 webservice项目不生效

目录 一、使用场景 二、问题描述 三、原因排查 四、解决方案 一、使用场景 原来项目是OpenJDK8tomcat9构建&#xff0c;现在需要升级到OpenJDK17tomcat10的组合。原来的webservice项目打包成aar格式&#xff0c;通过axis2部署在tomcat上。 二、问题描述 在配置好jdk和to…

android 自定义八边形进度条

自定义八边形动画效果图如下 绘制步骤&#xff1a; 1.先绘制橙色底部八边形实心 2.黑色画笔绘制第二层&#xff0c;让最外层显示一条线条宽度即可 3.再用黄色画笔绘制黄色部分 4.使用渐变画笔根据当前进度绘制覆盖黄色部分 5.使用黑色画笔根据当前进度绘制刻度条 6.黑色画笔绘制…

SQL-窗口函数

什么是窗口函数 可以像聚合函数一样对一组数据进行分析并返回结果&#xff0c;二者的不同之处在于&#xff0c;窗口函数不是将一组数据汇总成单个结果&#xff0c;而是为每一行数据都返回一个结果。 窗口函数组成部分 1.创建数据分区 窗口函数OVER子句中的PARTITION BY选项用…

【Flutter 问题系列第 80 篇】TextField 输入框组件限制可输入的最大长度后,输入的内容中包含表情符号时,获取输入的内容数还是会超出限制的问题

这是【Flutter 问题系列第 80 篇】&#xff0c;如果觉得有用的话&#xff0c;欢迎关注专栏。 博文当前所用 Flutter SDK&#xff1a;3.10.5、Dart SDK&#xff1a;3.0.5 一&#xff1a;问题描述 在输入用户名称、简介等内容时&#xff0c;一般我们都会限制输入框内最大可输入…
最新文章