springboot系列--web相关知识探索四

一、前言 

web相关知识探索三中研究了请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。web相关知识探索三中主要研究了注解方式以及Servlet API方式。本次研究主要是复杂参数底层绑定原理。

二、 复杂参数底层绑定原理

一、测试用例

请求的接口当中,还可以放入这些类型作为参数,MapModel(map、model里面的数据会被默认放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder。

    /*** 请求进来这个接口,然后往map设置数据,同时也往model和request设置数据,* 处理完之后再转发到下一个接口,下一个接口再从请求域中拿出map和model中设置的数据* 这里主要是验证,接口参数map和moddel,请求进来时,携带到的值是放在请求域中的* Map<String,Object> map,  Model model, HttpServletRequest request 都是可以给request域中放数据,* @param map* @param model* @param request* @return*/@GetMapping("/test")public String testParam(Map<String,Object> map, Model model, HttpServletRequest request, HttpServletResponse response){map.put("test1","mapTest");model.addAttribute("test2","modelTest");request.setAttribute("test3","requestTest");Cookie cookie = new Cookie("test4","cookieTest");response.addCookie(cookie);return "forward:/success";}@GetMapping("/success")@ResponseBodypublic Map testSuccess(HttpServletRequest request){Object test1 = request.getAttribute("test1");Object test2 = request.getAttribute("test2");Object test3 = request.getAttribute("test3");Map mp = new HashMap();mp.put("test1",test1);mp.put("test2",test2);mp.put("test3",test3);return mp;}

二、底层原理

一、Map参数解析

请求进来以后,直接到匹配合适的参数解析器这一步,第一个参数是Map类型的,所以需要的是能够解析Map类型参数的解析器。

 

上面是判断那种解析器支持解析Map类型参数,下面开始解析Map类型参数 

 

    // 解析map类型参数@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");// 直接从一个ModelAndViewContainer(模型和视图容器)对象中获取一个modelreturn mavContainer.getModel();}// 获取模型对象public ModelMap getModel() {默认会进入这条路径if (this.useDefaultModel()) {return this.defaultModel;} else {if (this.redirectModel == null) {this.redirectModel = new ModelMap();}return this.redirectModel;}}

 获取到的model对象是一个

private final ModelMap defaultModel = new BindingAwareModelMap();

也就是说,获取到的是一个BindingAwareModelMap对象,可以查看他的继承和实现发现,BindingAwareModelMap 是Model 也是Map。

二、Model参数解析 

从HandlerMethodArgumentResolverComposite类中的方法resolveArgument里面的代码

resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);

这里是开始解析参数的地方,从这里进入会发现,和map的解析是一模一样的。也是获取到一个BindingAwareModelMap对象。而且和前一个map用的还是同一个对象。

 

 三、map参数和model参数会被放到请求域当中

一、处理返回值

刚开始BindingAwareModelMap对象是空的,等到执行完接口后,接口里面会进行赋值,这个对象就有数据了。目标方法执行完,会将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据

	// 处理请求接口以及处理接口返回数据public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 处理接口Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);this.setResponseStatus(webRequest);if (returnValue == null) {if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {this.disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}} else if (StringUtils.hasText(this.getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {// 处理返回结果,mavContainer也被作为参数传入进去了this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception var6) {if (logger.isTraceEnabled()) {logger.trace(this.formatErrorForReturnValue(returnValue), var6);}throw var6;}}// 处理返回值public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//找到返回值处理器,这里之后在返回值部分细细研究 HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());} else {//这里是处理返回值的具体逻辑handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}}// 具体处理返回值逻辑public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 如果返回值是字符串会进入到这里if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();// 将返回值保存到ModelAndViewContainer对象中,view属性一般就是地址,model也就是数据,ModelAndView就是携带数据到达指定的地址当中mavContainer.setViewName(viewName);if (this.isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}} else if (returnValue != null) {throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());}}

二、将map、model数据设置到请求域中

 下面是上图中invokeHandlerMethod方法中

invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);

这段代码涉及到的源码

	// 处理请求接口以及处理接口返回数据public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 处理接口Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);this.setResponseStatus(webRequest);if (returnValue == null) {if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {this.disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}} else if (StringUtils.hasText(this.getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {// 处理返回结果,mavContainer也被作为参数传入进去了this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception var6) {if (logger.isTraceEnabled()) {logger.trace(this.formatErrorForReturnValue(returnValue), var6);}throw var6;}}// 处理返回值public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//找到返回值处理器,这里之后在返回值部分细细研究 HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());} else {//这里是处理返回值的具体逻辑handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}}// 具体处理返回值逻辑public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 如果返回值是字符串会进入到这里if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();// 将返回值保存到ModelAndViewContainer对象中,view属性一般就是地址,model也就是数据,ModelAndView就是携带数据到达指定的地址当中mavContainer.setViewName(viewName);if (this.isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}} else if (returnValue != null) {throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());}}

下面是上图中invokeHandlerMethod方法中

var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);

涉及到的源码

 获取ModelAndView对象@Nullableprivate ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {// 这里会更新model主要逻辑在自定义参数对象这块再研究modelFactory.updateModel(webRequest, mavContainer);if (mavContainer.isRequestHandled()) {return null;} else {ModelMap model = mavContainer.getModel();// 获取modelMap中的对象,并保存到ModelAndView中ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());if (!mavContainer.isViewReference()) {mav.setView((View)mavContainer.getView());}// 处理重定向数据if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes();HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class);if (request != null) {// 将请求数据放到请求上下文当中RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav;}}

 以上使用处理器适配器调用具体handler就处理完了,让然后会返回一个ModelAndView对象。之后回到DispatcherServlet类中的doDispatch方法当中。这个时候需要关注这个方法里面

this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

这块代码片段,主要是处理派发结果,这里就会往请求域中设置数据。

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {// 这部分是处理异常的boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {this.logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException)exception).getModelAndView();} else {Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;mv = this.processHandlerException(request, response, handler, exception);errorView = mv != null;}}// ModelAndView不为空时,且没有被清理if (mv != null && !mv.wasCleared()) {// 这里是去渲染页面的this.render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}} else if (this.logger.isTraceEnabled()) {this.logger.trace("No view rendering, null ModelAndView returned.");}if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, (Exception)null);}}}// 开始去渲染页面protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();response.setLocale(locale);// 获取视图名String viewName = mv.getViewName();View view;// 开始解析视图if (viewName != null) {view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");}} else {view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");}}if (this.logger.isTraceEnabled()) {this.logger.trace("Rendering view [" + view + "] ");}try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}// 上面代码拿到了视图,这里就开始去渲染了view.render(mv.getModelInternal(), request, response);} catch (Exception var8) {if (this.logger.isDebugEnabled()) {this.logger.debug("Error rendering view [" + view + "]", var8);}throw var8;}}// 开始进行视图解析@Nullableprotected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {if (this.viewResolvers != null) {Iterator var5 = this.viewResolvers.iterator();while(var5.hasNext()) {ViewResolver viewResolver = (ViewResolver)var5.next();View view = viewResolver.resolveViewName(viewName, locale);if (view != null) {return view;}}}return null;}@Nullablepublic View resolveViewName(String viewName, Locale locale) throws Exception {// 拿到请求域中的所有数据,和ModelAndView中的model数据(也就是接口中设置的数据)无关RequestAttributes attrs = RequestContextHolder.getRequestAttributes();Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());if (requestedMediaTypes != null) {List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);// 拿到视图名,然后返回View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);if (bestView != null) {return bestView;}}String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : "";if (this.useNotAcceptableStatusCode) {if (this.logger.isDebugEnabled()) {this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);}return NOT_ACCEPTABLE_VIEW;} else {this.logger.debug("View remains unresolved" + mediaTypeInfo);return null;}}// 这里是开始渲染数据的部分public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {if (this.logger.isDebugEnabled()) {this.logger.debug("View " + this.formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));}// 这里是创建合并数据HashMap对象,其实就是将model中个数据放到这个map当中Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);// 准备响应this.prepareResponse(request, response);// 渲染合并输出的数据,就是在这里放到了请求域中this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);}// 渲染合并输出的数据,就是在这里放到了请求域中protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// 就是在这行代码里将值设置到请求域当中this.exposeModelAsRequestAttributes(model, request);this.exposeHelpers(request);String dispatcherPath = this.prepareForRendering(request, response);RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");} else {if (this.useInclude(request, response)) {response.setContentType(this.getContentType());if (this.logger.isDebugEnabled()) {this.logger.debug("Including [" + this.getUrl() + "]");}rd.include(request, response);} else {if (this.logger.isDebugEnabled()) {this.logger.debug("Forwarding to [" + this.getUrl() + "]");}rd.forward(request, response);}}}// 设置数据到请求域当中,其实就是循环遍历了合并数据后的Map集合,将里面的值设置到请求域中protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);} else {request.removeAttribute(name);}});}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

决策树随机森林-笔记

决策树 1. 什么是决策树&#xff1f; 决策树是一种基于树结构的监督学习算法&#xff0c;适用于分类和回归任务。 根据数据集构建一棵树&#xff08;二叉树或多叉树&#xff09;。 先选哪个属性作为向下分裂的依据&#xff08;越接近根节点越关键&#xff09;&#xff1f;…

Node脚本实现批量打包Vue项目(child_process子进程、window)

前言 前几天用pnpmworkspace实现了monorepo&#xff0c;也就是单仓库多个项目&#xff0c;并且互相之间可能存在一定的联系。所以就存在一个打包的问题&#xff0c;也就是说&#xff0c;我想在打包某个特定子项目时&#xff0c;其他项目也执行build的命令。主要用到的是node的…

HDLBits中文版,标准参考答案 | 3.2.5 Finite State Machines | 有限状态机(2)

关注 望森FPGA 查看更多FPGA资讯 这是望森的第 17 期分享 作者 | 望森 来源 | 望森FPGA 目录 1 Lemmings 1 2 Lemmings 2 3 Lemmings 3 4 Lemmings 4 5 One-hot FSM | 独热 FSM 6 PS/2 packet parser | PS/2 数据包解析器 7 PS/2 packet parser anddatapath | PS/2 数…

机器学习课程学习周报十五

机器学习课程学习周报十五 文章目录 机器学习课程学习周报十五摘要Abstract一、机器学习部分1. 统计推断与贝叶斯推断2. GMM和EM算法补充3. 马尔可夫链蒙特卡罗法3.1 蒙特卡罗法3.2 马尔可夫链3.3 Diffusion模型中的马尔可夫链 总结 摘要 本周的学习涵盖了统计推断和贝叶斯推断…

C语言 | Leetcode C语言题解之第468题验证IP地址

题目&#xff1a; 题解&#xff1a; char * validIPAddress(char * queryIP) {int len strlen(queryIP);if (strchr(queryIP, .)) {// IPv4int last -1;for (int i 0; i < 4; i) {int cur -1;if (i 3) {cur len;} else {char * p strchr(queryIP last 1, .);if (p…

演讲干货整理:泛能网能碳产业智能平台基于 TDengine 的升级之路

在 7 月 26 日的 TDengine 用户大会上&#xff0c;新奥数能 / 物联和数据技术召集人袁文科进行了题为《基于新一代时序数据库 TDengine 助力泛能网能碳产业智能平台底座升级》的主题演讲。他从泛能网能碳产业智能平台的业务及架构痛点出发&#xff0c;详细分享了在数据库选型、…

怎么选择合适的数据恢复软件?适用于 Windows 的数据恢复软件对比

针对 Windows 的领先数据恢复软件的全面回顾&#xff1a; 丢失重要数据对任何 Windows 用户来说都是一场噩梦。从意外删除到系统崩溃&#xff0c;数据丢失是一个非常普遍的问题。值得庆幸的是&#xff0c;有强大的数据恢复工具可以帮助找回丢失的文件。这篇评论深入探讨了适用于…

编译链接的过程发生了什么?

一&#xff1a;程序的翻译环境和执行环境 在 ANSI C 的任何一种实现中&#xff0c;存在两个不同的环境。 第 1 种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令。 第 2 种是执行环境&#xff0c;它用于实际执行代码 也就是说&#xff1a;↓ 1&#xff1…

R语言绘制折线图

折线图是实用的数据可视化工具&#xff0c;通过连接数据点的线段展示数据随时间或变量的变化趋势。在经济、科学、销售及天气预报等领域广泛应用&#xff0c;为决策和分析提供依据。它能清晰呈现经济数据动态、助力科学研究、反映企业销售情况、预告天气变化&#xff0c;以简洁…

std::list

std::list是C标准库中的一个序列容器&#xff0c;它提供了双向链表的功能。std::list允许在序列的任何位置高效地插入和删除元素&#xff0c;而不会引起其他元素的移动&#xff0c;这使得std::list在需要频繁插入和删除操作的场景中非常有用。 std::list的特性&#xff1a; 双…

阿里140滑块-滑块验证码逆向分析思路学习

一、声明&#xff01; 原创文章&#xff0c;请勿转载&#xff01; 本文内容仅限于安全研究&#xff0c;不公开具体源码。维护网络安全&#xff0c;人人有责。 文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;均已做脱敏处…

使用Go语言的gorm框架查询数据库并分页导出到Excel实例(包含源代码,可以直接运行)

文章目录 基本配置配置文件管理命令行工具: Cobra快速入门基本用法生成mock数据SQL准备gorm自动生成结构体代码生成mock数据查询数据导出Excel使用 excelize实现思路完整代码参考入口文件效果演示分页导出多个Excel文件合并为一个完整的Excel文件完整代码基本配置 配置文件管理…

Javascript 普通非async函数调用async函数

假设我们有一个异步函数 async function asyncFunction() {console.log("开始执行异步函数");await new Promise(resolve > setTimeout(resolve, 1000)); // 模拟异步操作console.log("异步函数执行完毕"); } 我们在调用这个异步函数时&#xff0c;比…

【差分数组】个人练习-Leetcode-3229. Minimum Operations to Make Array Equal to Target

题目链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-make-array-equal-to-target/description/ 题目大意&#xff1a;给出两个数组nums[]和target[]&#xff0c;可以对nums[]数组进行这样两种操作 给某个区间内的子列全加1给某个区间内的子列全减1 求…

C语言从头学66—学习头文件 <stdio.h>(二)

关于可变参数&#xff0c;我们曾经在《C语言从头学27》中接触过&#xff0c;下面学习能够接收可变参数作为 参数的几个函数。 一、printf函数的能够接收可变参数的变体函数&#xff1a; 1、函数vprintf() 功能&#xff1a;按照给定格式&#xff0c;将可变参数中的内容输…

Java 用属性名称字符串获取属性对象

一、场景分析 java 中没有 python 一样的方法&#xff0c;通过属性名称直接获取属性值。 getattr(obj, name[, default]) : 访问对象的属性。 getattr(student, name) java 中有 Map, 可以实现类似功能&#xff0c;但是如果我们现在有一个对象&#xff0c;要通过Map的方式获…

九大排序之交换排序

1.前言 所谓交换&#xff0c;就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置&#xff0c;交换排序的特点是&#xff1a;将键值较大的记录向序列的尾部移动&#xff0c;键值较小的记录向序列的前部移动。 重点&#xff1a; 冒泡排序和快速排序 2.冒泡排…

React Fiber 详解

why Fiber React Fiber的引入主要基于以下几个方面的考虑&#xff1a; 性能提升&#xff1a; 传统React的更新过程是同步的&#xff0c;一旦开始更新就会阻塞浏览器的主线程&#xff0c;直到整个组件树更新完成。这在处理大型组件树或高频用户交互时&#xff0c;可能会导致界…

数组合并与排序练习题

题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合并后数…

OpenCV库模块解析

1.OpenCV库每个模块解析 2.OpenCV的常用函数 它为计算机视觉应用程序提供了一个通用的基础设施&#xff0c;并加速了在商业产品中使用机器感知。作为BSD许可的产品&#xff0c;OpenCV使企业可以很容易地利用和修改代码。该库拥有超过2500个优化算法&#xff0c;其中包括经典和最…