源码梳理(2)SpringMVC的执行流程及涉及到的相关组件

news2024/11/26 13:33:08

文章目录

          • 1,Spring MVC核心组件DispatcherServlet
            • 1.1 DispatcherServlet的继承关系
            • 1.2 DispatcherServlet的doDispatch方法
          • 2,核心组件HandlerMapping(处理器映射器)
          • 3,核心组件HandlerAdapter(处理器适配器)
          • 4,HandlerInterceptor拦截器
          • 5,核心组件Handler(处理器)
          • 6,ModelAndView(模型和视图)
          • 7,核心组件HandlerExceptionResolver(异常处理器解析器)

1,Spring MVC核心组件DispatcherServlet

所有源码都来自springboot3.0.2,spring-webmvc-6.0.4

1.1 DispatcherServlet的继承关系

DispatcherServlet的继承关系

HttpServlet是抽象类,用于处理基于HTTP协议的请求和响应。

package jakarta.servlet.http;

public abstract class HttpServlet extends GenericServlet

HttpServletBean是Spring Framework中提供的一个抽象基类,用于简化开发自定义的Servlet类在 Spring环境中的配置和使用。

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware

FrameworkServlet是Spring MVC框架中的一个重要组件,它是 DispatcherServlet的抽象基类之一。作为一个抽象类,FrameworkServlet提供了一些基本的功能,比如Servlet生命周期管理等

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware

DispatcherServlet是Spring MVC 框架中的核心组件,实现整个请求处理的流程,接下来会做详细分析

public class DispatcherServlet extends FrameworkServlet
1.2 DispatcherServlet的doDispatch方法

当HTTP请求打到服务器后,会先经过HttpServlet的service方法,然后执行到FrameworkServlet重写的service方法

	// HttpServlet.class
    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {
        HttpServletRequest  request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        // FrameworkServlet重写了这个方法
        service(request, response);
    }

FrameworkServlet的service方法,如果是"DELETE", “HEAD”, “GET”, “OPTIONS”, “POST”, “PUT”, "TRACE"方法的请求会交给父类也就是HttpServlet的另一个(参数是HttpServletRequest,HttpServletResponse重载的)service方法,该service方法会根据请求类型,将任务再分发给doGet(),doPost()等方法,而这写方法都被FrameworkServlet重写了

	// FrameworkServlet.class
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		if (HTTP_SERVLET_METHODS.contains(request.getMethod())) {
			super.service(request, response);
		}
		else {
			processRequest(request, response);
		}
	}

FrameworkServlet重写的doGet,doPost等方法都是调用了processRequest方法

	// FrameworkServlet.class
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}
	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}

FrameworkServlet的核心方法processRequest,作用于处理客户端请求的整个流程,包括初始化上下文、调用doService方法处理请求、记录请求处理的结果和时间、无论结果如何发布请求处理完毕事件等。其中处理请求的核心方法doService由子类DispatcherServlet重写实现

	// FrameworkServlet.class
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 省略...
		try {
			// DispatcherServlet重写了这个方法
			doService(request, response);
		}
		// 省略...
	}

DispatcherServlet的doService方法的核心环节doDispatch方法

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// 省略...
		try {
			doDispatch(request, response);
		}
		// 省略...
	}

DispatcherServlet的doDispatch方法

	// DispatcherServlet.class
	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 = 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 = 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;
				}
				// 实际调用处理程序
				// Actually invoke the handler.
				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) {
				// 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 ServletException("Handler dispatch failed: " + err, err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new ServletException("Handler processing failed: " + err, 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);
				}
			}
		}
	}
2,核心组件HandlerMapping(处理器映射器)

HandlerMapping是处理器映射器,其作用是根据请求的URL路径,通过注解或者XML配置,寻找匹配的处理器(Handler)信息。

	HandlerExecutionChain mappedHandler = null;
	// 省略...
	mappedHandler = getHandler(processedRequest);

在doDispatcher方法中,通过执行getHandler方法,获取到HandlerExecutionChain处理器执行链,并且准备好了针对当前请求最佳的处理器(比如自定义Controller组件中请求映射符合当前请求URL路径的某个方法),以及拦截器集合

	// DispatcherServlet.class
	@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;
	}

HandlerExecutionChain的主要属性

// 处理器执行链
public class HandlerExecutionChain {
	// 处理器
	private final Object handler;
	// 拦截器集合
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
	// 拦截器索引
	private int interceptorIndex = -1;
	// 省略...
}
3,核心组件HandlerAdapter(处理器适配器)

HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

在doDispatcher方法中,通过执行getHandlerAdapter方法,遍历所有处理器适配器,返回支持当前处理器的HandlerAdapter处理器适配器

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

HandlerAdapter接口的两个方法

public interface HandlerAdapter {
	// 配置当前处理器适配器是否支持传入的处理器
	boolean supports(Object handler);
	// 执行处理
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
4,HandlerInterceptor拦截器

HandlerInterceptor的三个方法:
preHandle方法在处理程序执行之前被调用。它可以拦截请求,并在处理程序执行之前进行一些预处理操作。方法返回一个布尔值,如果是true,则继续执行处理程序;如果是false,则终止请求的处理。
postHandle 方法在处理程序执行之后、视图渲染之前被调用。它允许拦截并修改处理程序的结果模型和视图。您可以在此方法中添加一些公共的模型数据、修改视图或进行一些资源清理操作。
afterCompletion 方法是在整个请求处理完成后被调用,包括视图渲染完毕。此时拦截器可以进行一些资源清理工作,或者进行一些日志记录等。它接收一个 Exception 参数,用于捕获并处理请求处理过程中的异常。

public interface HandlerInterceptor {
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return true;
	}
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}

DispatcherServlet的doServlet方法中拦截器的处理时机

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// 省略...
		try {
			// 省略...
			try {
				// 省略...
				// 执行HandlerInterceptor的preHandle方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}
				// 执行处理程序
				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				applyDefaultViewName(processedRequest, mv);
				//  执行HandlerInterceptor的postHandle方法
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			// 省略...
			// 在processDispatchResult方法最后调用了mappedHandler.triggerAfterCompletion(request, response, null)执行HandlerInterceptor的afterCompletion方法
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		// 省略
	}
5,核心组件Handler(处理器)

Handler是处理器,和Java Servlet扮演的角色一致。HandlerAdapter接口的handle方法通过Handler执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至ModelAndView对象中。

	ModelAndView mv = null;
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

RequestMappingHandlerAdapter的继承关系

举例当HandlerAdapter的实现类RequestMappingHandlerAdapter调用handle方法时,因为自身没有实现handle方法,所以会执行父类AbstractHandlerMethodAdapter的handle方法,这个handle的类型是HandlerMethod。

	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return handleInternal(request, response, (HandlerMethod) handler);
	}

RequestMappingHandlerAdapter实现了handleInternal方法,走后面的处理逻辑最终处理并返回ModelAndView

	@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}
6,ModelAndView(模型和视图)

handle处理程序返回的模型和视图,该模型和视图将由 DispatcherServlet 解析。视图可以是String 类型的视图名称,该名称需要由ViewResolver对象解析也可以直接指定View对象。该模型是一个 Map用于存储传输数据。

public class ModelAndView {

	/** View instance or view name String. */
	@Nullable
	private Object view;

	/** Model Map. */
	@Nullable
	private ModelMap model;

	/** Optional HTTP status for the response. */
	@Nullable
	private HttpStatusCode status;
}
7,核心组件HandlerExceptionResolver(异常处理器解析器)

HandlerExceptionResolver
异常处理器解析器是用于处理全局异常的组件,它可以捕获和处理控制器抛出的异常,然后决定如何对异常进行处理,例如返回错误页面、返回 JSON 错误信息等。
HandlerExceptionResolver接口就一个resolveException方法,通过这个方法来进行异常处理。

public interface HandlerExceptionResolver {
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

在DispatcherServlet的doDispatch执行到processDispatchResult方法的时候,会在前面处理请求过程中捕获的可能出现的异常作为参数传递进去

	// DispatcherServlet.class
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			ModelAndView mv = null;
			Exception dispatchException = null;
			try {
				// 省略...
			}
			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 ServletException("Handler dispatch failed: " + err, err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		// 省略...
	}

如果传递的异常参数不为null,且不属于ModelAndViewDefiningException,则走processHandlerException方法的异常处理逻辑,并通过异常处理结果获得新的ModelAndView

	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) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				// 异常处理
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isTraceEnabled()) {
				logger.trace("No view rendering, null ModelAndView returned.");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			// Exception (if any) is already handled..
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

在processHandlerException方法中将遍历HandlerExceptionResolver并执行resolveException方法进行实际异常逻辑的处理,整个方法执行完则返回一个新的ModelAndView

	@Nullable
	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {
		// Success and error responses may use different content types
		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
				// 核心环节,遍历并执行HandlerExceptionResolver的resolveException方法
				exMv = resolver.resolveException(request, response, handler, ex);
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
			if (exMv.isEmpty()) {
				request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
				return null;
			}
			// We might still need view name translation for a plain error model...
			if (!exMv.hasView()) {
				String defaultViewName = getDefaultViewName(request);
				if (defaultViewName != null) {
					exMv.setViewName(defaultViewName);
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Using resolved error view: " + exMv, ex);
			}
			else if (logger.isDebugEnabled()) {
				logger.debug("Using resolved error view: " + exMv);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
			return exMv;
		}

		throw ex;
	}

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

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

相关文章

iOS pod sdk开发到发布,记录

本文章记录从开发sdk到发布cocopod的问题和流程,省的每次都忘还得重新查 1:pod lib create (sdk名称) 命令创建 工程结构,然后根据命令行提示进行选择. What platform do you want to use?? [ iOS / macOS ]。~》 iOS What language do you want to use?? [ Swift / Obj…

Jmeter高级使用

文章目录 JMeter之计数器JMeter之集合点JMeter之断言JMeter之动态关联后置处理器&#xff1a;正则表达式提取器 JMeter之分布式测试JMeter之组件执行顺序元件的作用域元件的执行顺序配置元件Http Cookie管理器 多协议接口的性能测试Debug采样器Http请求中文乱码的解决Post参数设…

循环神经网络RNN专题(01/6)

一、说明 RNN用于处理序列数据。在传统的神经网络模型中&#xff0c;是从输入层到隐含层再到输出层&#xff0c;层与层之间是全连接的&#xff0c;每层之间的节点是无连接的。但是这种普通的神经网络对于很多问题却无能无力。例如&#xff0c;你要预测句子的下一个单词是什么&a…

Netty如何解决粘包以及半包问题,以及目前最常用的LengthFieldBasedFrameDecoder

粘包&#xff08;Sticky Packets&#xff09;和半包&#xff08;Half Packets&#xff09; 粘包&#xff08;Sticky Packets&#xff09;和半包&#xff08;Half Packets&#xff09;是在网络通信中常见的两种问题&#xff0c;特别是在基于流的传输协议&#xff08;如TCP&…

基于tidevice实现iOS app自动化使用详解

目录 1、IOS自动化工具概述 2、tidevice工具的原理和使用 2.1、tidevice的原理 2.2、tidevice实现的功能 2.3、tidevice的安装 2.4、tidevice的使用 2.4.1、设备管理 1、查看已连接的设备的列表 2、检测设备连接状态 3、等待设备连接&#xff0c;只要有就连接就结束监…

《区块链简易速速上手小册》第9章:区块链的法律与监管(2024 最新版)

文章目录 9.1 法律框架和挑战9.1.1 基础知识9.1.2 主要案例&#xff1a;加密货币的监管9.1.3 拓展案例 1&#xff1a;跨国数据隐私和合规性9.1.4 拓展案例 2&#xff1a;智能合约的法律挑战 9.2 区块链的合规性问题9.2.1 基础知识9.2.2 主要案例&#xff1a;加密货币交易所的合…

【读点论文】A Survey of Deep Learning Approaches for OCR and Document Understanding

A Survey of Deep Learning Approaches for OCR and Document Understanding Abstract 文档是许多领域(如法律、金融和技术等)中许多业务的核心部分。自动理解发票、合同和简历等文件是有利可图的&#xff0c;开辟了许多新的商业途径。通过深度学习的发展&#xff0c;自然语言…

cesium-场景出图场景截屏导出图片或pdf

cesium把当前的场景截图&#xff0c;下载图片或pdf 安装 npm install canvas2image --save npm i jspdf -S 如果安装的插件Canvas2Image不好用&#xff0c;可自建js Canvas2Image.js /*** covert canvas to image* and save the image file*/ const Canvas2Image (function…

elementUI中分开的时间日期选择组件,控制日期的禁用

<el-date-picker v-model"query.startTime" type"datetime" :picker-options"startPickerOptions" format"yyyy-MM-dd HH时" popper-class"date-picker" placeholder"选择日期时间"></el-date-picker>…

【Mysql】数据库架构学习合集

目录 1. Mysql整体架构1-1. 连接层1-2. 服务层1-3. 存储引擎层1-4. 文件系统层 2. 一条sql语句的执行过程2-1. 数据库连接池的作用2-2. 查询sql的执行过程2-1. 写sql的执行过程 1. Mysql整体架构 客户端&#xff1a; 由各种语言编写的程序&#xff0c;负责与Mysql服务端进行网…

[C#][opencvsharp]opencvsharp sift和surf特征点匹配

SIFT特征和SURF特征比较 SIFT特征基本介绍 SIFT(Scale-Invariant Feature Transform)特征检测关键特征&#xff1a; 建立尺度空间&#xff0c;寻找极值关键点定位&#xff08;寻找关键点准确位置与删除弱边缘&#xff09;关键点方向指定关键点描述子 建立尺度空间&#xff0…

python爬虫实战——获取酷我音乐数据

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 开发环境: 版 本&#xff1a; python 3.8 编辑器&#xff1a;pycharm 2022.3.2 模块使用: requests >>> pip install requests 如何安装python第三方模块: win R 输入 cmd 点击确定, 输入安装命令 pip install…

【数据分享】1929-2023年全球站点的逐日最低气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2023年全球气象站…

【云原生之kubernetes系列】--污点与容忍

污点与容忍 污点&#xff08;taints)&#xff1a;用于node节点排斥Pod调度&#xff0c;与亲和效果相反&#xff0c;即taint的node排斥Pod的创建容忍&#xff08;toleration)&#xff1a;用于Pod容忍Node节点的污点信息&#xff0c;即node节点有污点&#xff0c;也将新的pod创建…

GM8775C——DSI 转双通道 LVDS 发送器

1 产品概述 GM8775C 型 DSI 转双通道 LVDS 发送器产品主要实现将 MIPI DSI 转单 / 双通道 LVDS 功能&#xff0c; MIPI 支持 1/2/3/4 通道可选&#xff0c;每通道最高支持 1Gbps 速率&#xff0c;最大支持 4Gbps 速率。 LVDS 时钟频率高达 154MHz &#xff…

数据解构+算法(第07篇):动态编程!黄袍加身!

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

【数据分享】1929-2023年全球站点的逐月最高气温数据(Shp\Excel\无需转发)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2023年全球气象站…

vue如何使用vuedraggable实现不同面板之间的拖拽排序,拖拽复制功能?【vuedraggable】

vuedraggable官方文档链接使用说明https://www.itxst.com/vue-draggable/re7vfyfe.htmlhttps://www.itxst.com/vue-draggable/re7vfyfe.html 效果图&#xff1a; 使用vuedraggable拖动左边的字段和逻辑到右边形成不同的规则校验 <!-- ****--date 2024-02-01 11:34****-…

私募证券基金动态-23年12月报

成交量&#xff1a;12月日均7,696.93亿元 2023年12月A股两市日均成交7,696.93亿元&#xff0c;环比下降12.39%、同比下降2.26%。12月整体21个交易日&#xff0c;无单日交易日成交金额过万亿&#xff0c;单日交易日最低成交金额为6,122.84亿元&#xff08;12月25日&#xff09;…

GPT-4 Vision调试任何应用,即使缺少文本日志 升级Streamlit七

GPT-4 Vision 系列: 翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式一翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式二翻译: GPT-4 Vision静态图表转换为动态数据可视化 升级Streamlit 三翻译: GPT-4 Vision从图像转换为完全可编辑的表格 升级St…