SpringMVC源码-SpringMVC源码请求执行流程及重点方法doDispatch讲解

news2024/11/17 21:51:07

一、开始请求

在浏览器访问http://localhost:8080/spring_mymvc/userlist这个接口,是个get请求。
FrameworkServlet类的service方法会被请求到:
调用路径如下:

service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

在这里插入图片描述
可以看到该请求时get请求,走下面分支处理。进入super.service(request, response);发现方法跟丢了。。。。。。。
因为源码时在tomcat里的
在这里插入图片描述
super.service(request, response);Ctrl加鼠标左键点进去发现,进入到HttpServlet类的service方法:
在这里插入图片描述
会进入到FrameworkServlet的doGet方法,然后走processRequest(request, response);最后执行doService(request, response);方法
FrameworkServlet #doGet 执行路径:

doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

可以看到无论git还是post请求最终都会执行processRequest(request, response);方法
在这里插入图片描述
因为不管是什么请求方式除了参数的处理不一样 其他的都有相似之处 所以都执行一个最终的公共方法。
processRequest

/**处理此请求,发布一个事件,而不管结果如何。<p>实际的事件处理是由抽象的{@link doService}模板方法执行的。
	 * Process this request, publishing an event regardless of the outcome.
	 * <p>The actual event handling is performed by the abstract
	 * {@link #doService} template method.
	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		// 记录当前时间,用于计算处理请求花费的时间
		long startTime = System.currentTimeMillis();
		// 记录异常,用于保存处理请求过程中发送的异常
		Throwable failureCause = null;

		// 获取LocaleContextHolder中原来保存的LocaleContext(保存的本地化信息)  事务中就是这样 先把最开始的保存 把新的放进去 用完新的再把最开始保存的恢复回去
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		// 获取当前请求的LocaleContext
		LocaleContext localeContext = buildLocaleContext(request);

		// 获取RequestContextHolder总原来保存的RequestAttribute(管理request和session的属性) spring事务处理的时候 有类似的操作 获取当前的 保存起来 后续恢复
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		// 获取当前请求的ServletRequestAttribute
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		// 获取异步管理器
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		// 将当前请求的LocaleContext和ServletRequestAttribute设置到LocaleContextHolder和RequestContextHolder
		initContextHolders(request, localeContext, requestAttributes);

		try {
			// 执行真正的逻辑
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			// 记录抛出的异常
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			// 记录抛出的异常
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
			// 恢复原来的LocaleContext和ServletRequestAttributes到LocaleContextHolder和RequestContextHolder中
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}
			// 如果日志级别为debug,则打印请求日志
			logResult(request, response, failureCause, asyncManager);
			// 发布ServletRequestHandledEvent请求处理完成事件
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

执行真正的逻辑
doService(request, response);

/**公开dispatcherservlet特定的请求属性,并委托给{@link doDispatch}进行实际的调度。
	 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
	 * for the actual dispatching.
	 */
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// 如果日志级别为 DEBUG,则打印请求日志
		logRequest(request);

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		// 当include请求时对request的Attribute做快照备份
		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));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		// 设置Spring框架中的常用对象到request属性中,这四个属性会在handler和view中使用
		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());
         //重定向的时候方便参数的传递
		// FlashMap的相关配置,主要用于Redirect转发时参数的传递,此处有一个应用场景:如果post请求是提交表单,提交完之后redirect到一个显示订单的页面,
		// 此时需要知道一些订单的信息,但redirect本身没有提交参数的功能,如果想传递参数,那么就必须要写到url,而url有长度的限制同时还容易对外暴露,此时
		// 可以使用flashMap来传递参数,
		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 {
			// 执行请求的分发
			doDispatch(request, response);
		}
		finally {
			// 异步处理相关
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				// 还原request快照的属性
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

执行请求的分发
doDispatch(request, response);

/**
	 * 处理实际的分发到处理器中
	 * 内层是捕获在对请求进行处理的过程中抛出的异常,在处理异常的时候会设置到dispatcherException变量,然后在processorDispatcherResult方法中进行处理
	 * 外层是处理渲染页面时抛出的异常,主要是处理processDispatchResult方法抛出的异常
	 *
	 * Process the actual dispatching to the handler.
	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
	 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
	 * to find the first that supports the handler class.
	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
	 * themselves to decide which methods are acceptable.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @throws Exception in case of any kind of processing failure
	 */
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// 实际处理时所用的request,如果不是上传请求,则直接使用接收到的request,否则封装成上传类型的request
		HttpServletRequest processedRequest = request;
		// 处理请求的处理器链(包含处理器和对应的interceptor)
		HandlerExecutionChain mappedHandler = null;
		// 是不是上传请求的标志
		boolean multipartRequestParsed = false;

		// 获取异步管理器
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
          //这里划为两个try ,第一个是处理请求后端,第二个是处理数据渲染。分别处理异常了
		try {
			// 封装model和view的容器
			ModelAndView mv = null;
			// 处理请求过程中抛出的异常,但是不包含渲染过程中抛出的异常
			Exception dispatchException = null;

			try {
				// 检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象
				processedRequest = checkMultipart(request);
				// 设置上传请求的标志
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 获得请求对应的HandlerExecutionChain对象(HandlerMethod和HandlerInterceptor拦截器们)
				mappedHandler = getHandler(processedRequest);//请求对应的是哪个controller
				//  如果获取不到,则根据配置抛出异常或返回404错误
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// 获得当前handler对应的HandlerAdapter对象 controller或者控制器有多种不同的实现方式 为了方便后续过程中调用 使用适配器模式来 解决
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				// 处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器会在返回的请求头里包含一个last_modified的属性,
				// 代表资源最后时什么时候修改的,在浏览器以后发送请求的时候,会同时发送之前接收到的Last_modified.服务器接收到带last_modified的请求后,
				// 会跟实际资源的最后修改时间做对比,如果过期了返回新的资源,否则直接返回304表示未过期,直接使用之前缓存的结果即可
				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;
					}
				}

				// 执行响应的Interceptor的preHandler
				// 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				// 真正的调用handler方法,也就是执行对应的方法,并返回视图
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				// 如果需要异步处理,直接返回
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				// 当view为空时,根据request设置默认的view
				applyDefaultViewName(processedRequest, mv);
				// 执行响应的interceptor的postHandler方法
				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);
			}
			// 处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
			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);
				}
			}
		}
	}

重要的方法:

  • 检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象
    processedRequest = checkMultipart(request);
  • 获得请求对应的HandlerExecutionChain对象(HandlerMethod和HandlerInterceptor拦截器们)
    mappedHandler = getHandler(processedRequest);//请求对应的是哪个controller
  • 获得当前handler对应的HandlerAdapter对象 controller或者控制器有多种不同的实现方式 为了方便后续过程中调用 使用适配器模式来 解决
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  • 处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器会在返回的请求头里包含一个last_modified的属性,
    // 代表资源最后时什么时候修改的,在浏览器以后发送请求的时候,会同时发送之前接收到的Last_modified.服务器接收到带last_modified的请求后,
    // 会跟实际资源的最后修改时间做对比,如果过期了返回新的资源,否则直接返回304表示未过期,直接使用之前缓存的结果即可
    lastModified
  • 执行响应的Interceptor的preHandler
    // 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
  • 真正的调用handler方法,也就是执行对应的方法,并返回视图
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  • 执行响应的interceptor的postHandler方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
  • 处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

SpringMVC的九大内置组件:
在这里插入图片描述

doDispatch方法的执行流程:
在这里插入图片描述

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

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

相关文章

数据结构(7.3_5)——红黑树的插入和删除

红黑树的插入 注意&#xff1a;插入时候重点考察“不红红”特性&#xff08;红黑树的性质&#xff09; 染色&#xff1a;哪几个结点有变动就红的变黑&#xff0c;黑的变红 RR、LL、LR、RL从爷结点开始算起 非根结点的插入只需要判断是否违背的“不红红”特性 练习&#xff1…

通过人工智能AI大模型定制的完美旅游行程

人工智能在购票与乘车体验优化方面发挥着重要作用&#xff0c;通过智能技术的应用&#xff0c;不仅提升了购票效率&#xff0c;还改善了乘车体验。以下是人工智能赋能购票与乘车体验优化的具体表现&#xff1a; 一、购票体验优化 智能推荐系统&#xff1a; 购票平台如12306利…

USB3.0线束质量的影响

一. 前言 近一年频繁与USB3.0打交道&#xff0c;深受USB3.0线束之苦&#xff0c;在USB3.0相机和USB3.0 U盘上栽了很多跟头&#xff0c;问题反反复复。前期通过比对&#xff0c;发现USB3.0线束的特征阻抗特性对传输速度影响很大。市场上普通的USB3.0线束无法满足要求&#xff0…

免费送源码:Javaspringboot++MySQL springboot 社区互助服务管理系统小程序 计算机毕业设计原创定制

摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受居民的喜爱&#xff0c;社区互助服务管理系统小程序被居民普遍使用&#xff0c;为…

FastAPI 第七课 -- Pydantic 模型

目录 一. 前言 二. 定义 Pydantic 模型 三. 使用 Pydantic 模型 3.1. 请求体验证 3.2. 查询参数验证 四. 自动文档生成 五. 数据转换和序列化 一. 前言 Pydantic 是一个用于数据验证和序列化的 Python 模型库。 它在 FastAPI 中广泛使用&#xff0c;用于定义请求体、响…

鸢尾花书实践和知识记录[编程1-10可视化]

一个人可以被摧毁&#xff0c;但不能被打败。 A man can be destroyed but not defeated 原作者的github 主要内容Matplotlib,Plotly如何绘制线图 文章目录 思维导图解剖图代码:图像的要素和标注 使用Matplotlib绘制线图代码2:绘制正弦和余弦的图像图像存储格式的说明 代码3子…

Linux 默认内核版本更改

随笔记录 目录 1. 背景介绍 2. 解决方法 2.1 查看所有可用版本 2.2 安装指定版本内核 2.3 检查当前内核列表 2.4 检查当前默认内核 2.5 设置新的默认内核 2.6 确认内核是否成功加载 2.7 重启 2.8 删除其他版本内核 1. 背景介绍 linux 一般安装多个内核版本&…

97、配置 VXLAN 不同子网互访 (分布式网关)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、基础配置SW1SW2IGP IS-IS 二、VXLAN1.引入库 总结 前言 一、基础配置 SW1 vlan 10 vlan 20interface GigabitEthernet0/0/1port link-type accessport de…

[Python学习日记-33] Python 中的嵌套函数、匿名函数和高阶函数

[Python学习日记-33] Python 中的嵌套函数、匿名函数和高阶函数 简介 嵌套函数 匿名函数 高阶函数 简介 在 Python 当中函数除了能减少重复代码、扩展性强和易维护外&#xff0c;其实还有挺多不通的玩法的&#xff0c;例如嵌套函数、匿名函数、高阶函数等&#xff0c;它们是…

酒店智能开关的组成与功能

酒店智能开关作为智能家居系统的重要组成部分&#xff0c;其应用日益广泛。本文将深入探讨酒店智能开关的组成部分及其功能特点。 酒店智能开关的组成 酒店智能开关的构成相对复杂&#xff0c;但主要可以归纳为以下几个核心部分&#xff1a; 开关主体&#xff1a;开关主体是智能…

带链的队列,入队,退队,检测带链队列的状态

代码&#xff1a; #include<iostream> using namespace std; template<class T> struct node {T d;node *next;}; template<class T> class linked_Queue {private:node<T> *front;node<T> *rear;public:linked_Queue();void prt_linked_Queue(…

联想电脑怎么开启vt_联想电脑开启vt虚拟化教程(附intel和amd主板开启方法)

最近使用联想电脑的小伙伴们问我&#xff0c;联想电脑怎么开启vt虚拟。大多数可以在Bios中开启vt虚拟化技术&#xff0c;当CPU支持VT-x虚拟化技术&#xff0c;有些电脑会自动开启VT-x虚拟化技术功能。而大部分的电脑则需要在Bios Setup界面中&#xff0c;手动进行设置&#xff…

掌握DFIR报表: 法医专家指南

介绍 在数字安全领域&#xff0c;DFIR&#xff08;数字取证和事件响应&#xff09;是一门重要的学科&#xff0c;体现了调查网络事件和破坏行为所必不可少的方法和实践。作为DFIR的核心&#xff0c;对DFIR报告的重视怎么强调都不为过。这些报告不仅仅是文件&#xff1b;它们是…

每天3分钟,彻底弄懂神经网络的优化器(三)Momentum

前面从最初的SGD开始&#xff0c;介绍SGD的算法细节&#xff0c;以及其在深度神经网络中的劣势&#xff0c;本文引入Momentum算法&#xff0c;解决SGD的一些问题。 1. Momentum算法的提出 动量&#xff08;Momentum&#xff09;方法最初由B.T.Polyak在1964年提出。这一方法被…

IAR全面支持国科环宇AS32X系列RISC-V车规MCU

IAR嵌入式开发解决方案将全面支持国科环宇AS32X系列ASIL-B MCU&#xff0c;共同推动汽车高品质应用的安全开发 中国上海&#xff0c;2024年9月29日 – 全球领先的嵌入式系统开发软件解决方案供应商IAR与北京国科环宇科技股份有限公司&#xff08;以下简称“国科环宇”&#xf…

Rust语言桌面应用开发GTK3 Gtk3-rs Glade

文章目录 GTK-RSGithub官网Rust 教程Rust 环境安装 GTK安装 Gladedemo.glade 文件完整示例 main.rs创建 Rust 项目Cargo.toml 文件main.rs 文件 编译运行GTK主题 GTK-RS gtk-rs 是一个用于在 Rust 编程语言中使用 GTK 图形用户界面工具包的库。GTK 是一个流行的跨平台 GUI 工具…

每日论文7-17MWCL基于IMOS的小vco增益变化的VCO

《Small VCO-Gain Variation Adding a Bias-Shifted Inversion-Mode MOS Varactor》17MWCL 对于PLL来说&#xff0c;其中VCO的调谐增益KVCO越线性&#xff0c;其变化程度ΔKvco越小&#xff0c;对PLL的稳定有较大的好处。这篇文章给了一个很简单朴素而有效的补偿var非线性的方…

Maven 编译和Nexus 构建私有仓库

Java 程序编译 编译流程 C 语言源码编译过程&#xff0c;对于单文件&#xff0c;我们可以使用 gcc 命令直接编译即可&#xff0c;但如果是大型商业项目&#xff0c;源码文件多&#xff0c;存在各种依赖&#xff0c;各种配置路径&#xff0c;各种库的支持等&#xff0c;几乎无法…

C0004.Qt中QComboBox设置下拉列表样式后,下拉列表样式无效的解决办法

问题描述 我们平时在使用Qt Creator对控件QComboBox的样式进行设置后,在运行程序启动界面时,发现设置的样式无效,效果如下: /* 设置下拉菜单框的样式 */ QComboBox QAbstractItemView {border: 1px solid rgb(161,161,161); /* 下拉菜单框的边框样式 */ }/* 设置下拉菜单…

现在转行AI晚不晚,应该怎么做呢?

对于40岁以上的非AI程序员来说&#xff0c;转行进入AI领域虽然可能面临一些挑战&#xff0c;但并非不可实现。凭借你已有的编程经验和技术背景&#xff0c;加上适当的学习策略和实践&#xff0c;你可以成功跨入AI领域。以下是一些针对40岁程序员转行AI的建议&#xff0c;特别是…