【Spring源码分析】Spring的启动流程源码解析

阅读此需阅读下面这些博客先
【Spring源码分析】Bean的元数据和一些Spring的工具
【Spring源码分析】BeanFactory系列接口解读
【Spring源码分析】执行流程之非懒加载单例Bean的实例化逻辑
【Spring源码分析】从源码角度去熟悉依赖注入(一)
【Spring源码分析】从源码角度去熟悉依赖注入(二)
【Spring源码分析】@Resource注入的源码解析
【Spring源码分析】循环依赖的底层源码剖析

Spring的启动流程源码解析

  • 一、AnnotationConfigApplicationContext()具体流程
    • reader 构造逻辑
    • scanner 构造逻辑
  • 二、refresh 源码分析
    • prepareRefresh()
    • obtainFreshBeanFactory()
    • !prepareBeanFactory(beanFactory) !
    • postProcessBeanFactory(beanFactory);
    • !invokeBeanFactoryPostProcessors(beanFactory);!
    • registerBeanPostProcessors(beanFactory)
    • initMessageSource()
    • initApplicationEventMulticaster()
    • onRefresh() 和 registerListeners()
    • finishBeanFactoryInitialization(beanFactory)
  • 三、总结

阐述这篇要讲述的内容,这篇主要是去阐述Spring在启动过程中会做哪些事情,具体指示的代码是下面这三行(没直接说AnnotationConfigApplicationContext重载的那个构造,是因为SpringBoot就是用的下三行,所以干脆就这样了):

		AnnotationConfigApplicationContext
				context = new AnnotationConfigApplicationContext();
		context.register(AppConfig.class);
		context.refresh();

那么这篇就主要就是说这三行代码都做了啥,Spring启动流程就是啥,其中 refresh() 方法是主要的,内容也有点多。

一、AnnotationConfigApplicationContext()具体流程

可以说就三步:

  1. 构造 DefaultListableBeanFactory 实例;
    • 调用构造方法首先是先执行的父类构造,父类构造会实例化一个 DefaultListableBeanFactory。
    • 在这里插入图片描述
  2. 实例化 AnnotatedBeanDefinitionReader 实例;
  3. 实例化 ClassPathBeanDefinitionScanner 扫描器。
	public AnnotationConfigApplicationContext() {
		// super(); 这里会构造 BeanFactory
		StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
		// 额外会创建StandardEnvironment
		this.reader = new AnnotatedBeanDefinitionReader(this);
		createAnnotatedBeanDefReader.end();

		// 会填充 Environment 和 ClassLoader 类加载器;
		// 会将 @Component 注解放入到 includeFilter 集合中,扫描的时候筛选出对应的资源好封装成BeanDefinition
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

有必要看一下 AnnotationConfigApplicationContext 关系图:

在这里插入图片描述那有关 BeanFactory 接口的功能呢其实就是调用 beanFactory#对应方法.

reader 构造逻辑

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
		this(registry, getOrCreateEnvironment(registry));
	}

会去获取一个 StandardEnvironment 对象,然后调用另一个构造方法
在这里插入图片描述

	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		this.registry = registry;
		// 用来解析@Conditional注解的
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		// 注册
		// 这里主要是去设置比较器和是否允许注入的解析器
		// 而后续的后置处理器是等扫描完还会将这些后置处理器拿到然后去放进去,这里的话没用
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

流程也是比较简单,就是构建个 @Conditional 注解的解析器,后续扫描BeanDefinition的候选者用到了;然后就是去向BeanFactory中注入 Order 比较器 AnnotationAwareOrderComparator 和 注入候选者解析器 ContextAnnotationAutowireCandidateResolver

后者那个 ContextAnnotationAutowireCandidateResolver 其实就是在我们@Autowired或者说@Resource注解注入的时候,在通过类型去找Bean的时候,会有一段责任链设计模式的筛选——autowireCandidate 参数值应该为 true->泛型判断->@Qualifier 解析,不知道还记不记得,不记得没关系,不影响流程的阅读。

随后会注入一些 BeanPostProcessor 和 BeanFactoryPostProcessor,这里是还未实例化的,是注入到 BeanDefinitionMapBeanDefinitionNames 里的注入。

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		// 注册ConfigurationClassPostProcessor类型的BeanDefinition
		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// 注册AutowiredAnnotationBeanPostProcessor类型的BeanDefinition
		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// 注册CommonAnnotationBeanPostProcessor类型的BeanDefinition
		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// 注册EventListenerMethodProcessor类型的BeanDefinition,用来处理@EventListener注解的
		// 处理 @EventListener 会遍历所有的 beanNames 对应的Class,只要是单例就行,是懒加载也会解析为对应的ApplicationListener
		if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
		}

		// 注册DefaultEventListenerFactory类型的BeanDefinition,用来处理@EventListener注解的
		if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
		}

  • 首先是注入了 ConfigurationClassPostProcessor,它类的结构如下,后面扫描要用:
    • 在这里插入图片描述
  • 然后是注入 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor,我们的@Value、@Resource、@Autowired、@Qualified、@PreDestroy、@PostConstruct…等注解作用都是靠它俩完成的
  • 随后注入 EventListenerMethodProcessor ,它是用来 @EventListener 注解的,主要解析对象是 beanDefinitionNames 里存在滴;
  • 随后注入 DefaultEventListenerFactory 它是用来将 @EventListener 修饰的方法构建成 ApplicationListener 实例,然后放入到监听集中。

scanner 构造逻辑

	public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
										  Environment environment, @Nullable ResourceLoader resourceLoader) {
		this.registry = registry;

		if (useDefaultFilters) {
			registerDefaultFilters();
		}
		setEnvironment(environment);
		setResourceLoader(resourceLoader);
	}

在这里插入图片描述

也没做啥就是将 @Component 注解放入进 includeFilters 集合中,后续筛选候选者 BeanDefinition 用,然后把环境对象和资源加载器构建一手,就后续扫描资源用的。

看完 AnnotationConfigApplicationContext 的无参构造可以知道:
它无非就是去构建后续 register、refresh和扫描需要的环境

context.register 其实没做啥,就是将我们上面写的 AppConfig 直接通过 reader 将其对应的BeanDefinition和beanName容器中就是了。

二、refresh 源码分析

这个好长,我只能一个方法一个方法解析了,如果不愿看的可以直接跳到总结看流程图。

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// Prepare this context for refreshing.
			// 一些标志位和验证,咱别管
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 这里会判断能否刷新,并且返回一个BeanFactory, 刷新不代表完全情况,主要是先执行Bean的销毁,然后重新生成一个BeanFactory,再在接下来的步骤中重新去扫描等等
			// 对于Spring来说,对于AnnotationConfigApplicationContext来说,不允许下一次refresh也是在这个阶段进行的
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 准备BeanFactory
			// 1. 设置BeanFactory的类加载器、SpringEL表达式解析器、类型转化注册器
			// 2. 添加三个BeanPostProcessor,注意是具体的BeanPostProcessor实例对象
			// 3. 记录ignoreDependencyInterface
			// 4. 记录ResolvableDependency
			// 5. 添加三个单例Bean
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				// 子类来设置一下BeanFactory
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");

				// Invoke factory processors registered as beans in the context.
				// BeanFactory准备好了之后,执行BeanFactoryPostProcessor,开始对BeanFactory进行处理
				// 默认情况下:
				// 此时beanFactory的beanDefinitionMap中有6个BeanDefinition,5个基础BeanDefinition+AppConfig的BeanDefinition
				// 而这6个中只有一个BeanFactoryPostProcessor:ConfigurationClassPostProcessor
				// 这里会执行ConfigurationClassPostProcessor进行@Component的扫描,扫描得到BeanDefinition,并注册到beanFactory中
				// 注意:扫描的过程中可能又会扫描出其他的BeanFactoryPostProcessor,那么这些BeanFactoryPostProcessor也得在这一步执行
				invokeBeanFactoryPostProcessors(beanFactory);  // scanner.scan()

				// Register bean processors that intercept bean creation.
				// 将扫描到的BeanPostProcessors实例化并排序,并添加到BeanFactory的beanPostProcessors属性中去
				registerBeanPostProcessors(beanFactory);

				beanPostProcess.end();

				// Initialize message source for this context.
				// 设置ApplicationContext的MessageSource,要么是用户设置的,要么是DelegatingMessageSource
				initMessageSource();

				// Initialize event multicaster for this context.
				// 设置ApplicationContext的applicationEventMulticaster,要么是用户设置的,要么是SimpleApplicationEventMulticaster
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 给子类的模板方法
				// Spring啥也没干,MVC倒是重写了,给其他的模板方法
				onRefresh();

				// Check for listener beans and register them.
				// 把定义的ApplicationListener的Bean对象,设置到ApplicationContext中去,并执行在此之前所发布的事件
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 实例化所有非懒加载的单例bean
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}

prepareRefresh()

一些标志位设置和验证啥的,跟咱开发关系不大,跳过~

obtainFreshBeanFactory()

这个方法是去得到 BeanFactory,我们之前阐述了,在 AnnotationConfigApplicationContext 的父类的无参构造中会构建一个 DefaultListableBeanFactory,这里就是将这个返回。

在这里插入图片描述在这里插入图片描述

但这里还需要注意的是 refresh 方法不能重复调用,就是在这里进行判断的。

在这里插入图片描述
内部封装了一个 refreshed 的标志为,类型是 AtomicBoolean 类型的,这里会通过 CAS 判断设置,如果已经是true了会抛出异常,表示不允许重复进行 refresh。
在这里插入图片描述

!prepareBeanFactory(beanFactory) !

这个方法做了挺多事的,主要是对 BeanFactory 的初始化,但是在我们之前都有遇到,它何时注入的呢?下面看看:

  1. 注入类加载器、Spel 解析器、类型转化器(在咱Bean生命周期createBean的第一步就是去尝试使用类加载器对类进行加载;在注入解析@Value的时候针对#{}就是Spel解析器在发挥作用;类型转化器在找到注入实例不就开始用类型转化器进行转化了)。
    在这里插入图片描述
  2. 添加一个 ApplicationContextAwareProcecssor 实例到 BeanFactory 容器中(它就是针对那些Aware注入嘛,在生命周期之初始化前会遍历BeanPostProcessor那里),可以发现它是第一个填充到 BeanFactory 容器中的 BeanPostProcessor

在这里插入图片描述

  1. 向 ignoreDependecyInterfaces 中添加点东西,这个无所谓的,这个是Spring提供的那种ByName/ByType的注入方式,前面参数 populateBean 一 的时候有说过,开发又不用不阐述了,就是说这里面的setter的话就不会进行注入了。
    在这里插入图片描述
  2. 注入一些Bean,表示已经解析过了。这个在我们去根据类型找Bean的时候,就是会拿注入的类型和这些去比对,如果比对成功就直接注入这里的 Bean(如果想不起来了,去看属性注入二,我阐述过了)。

在这里插入图片描述

  1. ApplicationListenerDetector 这个事件监听器的检测BeanPostProcessor。它主要是在Bean初始化后那个时候去判断一下是否是单例且是否实现了ApplicationListener接口,如果是就放入到applicationListeners集合中。可以说这是Spring启动阶段放入到BeanFactory中的第二个BeanPostProcessor,用于检测容器中的ApplicationListener。
    在这里插入图片描述在这里插入图片描述
  2. 将环境对象放到单例池里(这属于手动添加单例池)
    在这里插入图片描述

postProcessBeanFactory(beanFactory);

Spring 这个方法没有实现,这是一个模板方法,用于其他接入Spring的,用来填充BeanFactory的。

!invokeBeanFactoryPostProcessors(beanFactory);!

代码太多,这里就阐述个流程,其中如何解析的配置也是在这个方法中,其中在解析@ComponentScan的时候就会进行扫描,我下篇博客阐述。
首先里面是用的 BeanFactoryPostProcessor ,准确点说是它的子类 BeanDefinitionRegistryPostProcessor。前面我们阐述过一些 BeanPostProcessor,那是用来处理 Bean 的后置处理器,那这个 BeanFactoryBeanProcessor 就是 BeanFactory 的后置处理器。(阐述这个是为了你们若是根据这个博客去看源码的话不会懵,还有就是前面在构造reader的时候注入了 ConfigurationClassPostProcessor,在此之前就注入了这么个BeanFactoryPostProcessor)

下面直接阐述流程:

  • 解析配置类
    • 解析 @ComponentScan,扫描得到BeanDefinition并注册;
    • 解析 @Bean、@Import等注解得到BeanDefinition并注册;
    • 详细下篇博客阐述;
  • 执行 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()
  • 执行 BeanDefinitionRegistryPostProcessor#postProcessBeanFactory()

说白了就是解析注解后注册BeanDefinition。

registerBeanPostProcessors(beanFactory)

这里就是注册BeanPostProcessor到单例池中,然后添加到 beanPostProcessors 集合中,说是排了序,但感觉排不排一个样。就是 @PriorityOrdered > @Ordered > 啥都没有的。说是这样说,但我感觉Spring中的BeanPostProcessor没啥顺序可言,以下是Spring的顺序,大伙看看就行:

在这里插入图片描述其中那个AsyncAnnotationBeanPostProcessor那个是因为我开始加了@EnableAsync注解忘记去掉了。

initMessageSource()

就是设置 ApplicationContext 的 MessageSource,没啥好说的。

initApplicationEventMulticaster()

初始化事件转播器,默认是 SimpleApplicationEventMulticaster,如果自己构建了并且放入了容器,那就用咱的,但谁没事写这玩意啊。

在这里插入图片描述

onRefresh() 和 registerListeners()

onRefresh 是模版方法,Spring啥也没干;
registerListeners 的作用是把定义好的 ApplicationListener 的Bean对象注入到ApplicationContext中,但是Spring本质啥也没干,因为没有。

finishBeanFactoryInitialization(beanFactory)

这就核心来了,实例化所有的非懒加载的单例Bean。
前面博客讲述了这个的主要流程。

三、总结

可以看流程图:
https://www.processon.com/view/link/5f60a7d71e08531edf26a919

但是感觉看流程图挺累的,还是简单阐述一下吧(大概流程哈,不可能详细阐述):

  • 构建个DefaultBeanFactory注入进去;
  • 构建环境:reader、scanner、一些BeanPostProcessor这些;
  • 通过 ConfigurationClassPostProcessor 去扫描配置类;
  • 然后去实例化容器里的 BeanPostProcessor 到 beanPostProcessors 集合中;
  • 构建事件传播器;
  • 实例化非懒加载单例Bean到单例池中。

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

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

相关文章

【Linux环境基础开发工具的使用(yum、vim、gcc、g++、gdb、make/Makefile)】

Linux环境基础开发工具的使用yum、vim、gcc、g、gdb、make/Makefile Linux软件包管理器- yumLinux下安装软件的方式认识yum查找软件包安装软件如何实现本地机器和云服务器之间的文件互传卸载软件 Linux编辑器 - vimvim的基本概念vim下各模式的切换vim命令模式各命令汇总vim底行…

openresty (nginx)快速开始

文章目录 一、什么是openresty&#xff1f;二、openresty编译安装1. 编译安装命令1.1 编译完成后路径1.2 常用编译选项解释 2. nginx配置文件配置2.1 nginx.conf模板 3. nginx常见配置一个站点配置多个域名nginx配置中location匹配规则 三、OpenResty工作原理OpenResty工作原理…

实现自定义标记

实现自定义标记 问题陈述 New Tech Book的高级管理层决定在其用JSP设计的应用程序的所有页面上显示版权信息。它们还要去如何向应用程序中添加JSP页面,可以重用显示版本信息的代码。公司的软件开发人员Jerry Smith决定用自定义标记来创建应用程序的这一部分。 解决方案 要解…

【数学建模】【2024年】【第40届】【MCM/ICM】【D题 五大湖的水位控制问题】【解题思路】

一、题目 &#xff08;一&#xff09; 赛题原文 2024 ICM Problem D: Great Lakes Water Problem Background The Great Lakes of the United States and Canada are the largest group of freshwater lakes in the world. The five lakes and connecting waterways const…

社区团购系统-UMLChina建模知识竞赛第5赛季第6轮

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 只要最先答对前3题&#xff0c;即可获得本轮优胜。 如果有第4题&#xff0c;第4题为附加题&am…

SpringBoot3整合Knife4j

前置&#xff1a; 官网&#xff1a;快速开始 | Knife4j gitee&#xff1a;swagger-bootstrap-ui-demo: knife4j 以及swagger-bootstrap-ui 集成框架示例项目 - Gitee.com 1.依赖引入&#xff1a; ps&#xff1a;json处理需要引入相关包 <dependency><groupId>c…

sqli-labs-master靶场训练笔记(54-65|决战紫禁之巓)

2024.2.5 level-54 1、先判断类型和闭合类型 ?id1/0 #正常 证明不是数字型 ?id1 #错误 ?id1 -- a #正常 判断是 闭合 2、 判断列数 这里需要运气&#xff0c;但是根据前面50多关的经验直接猜测是3列 ?id-1 union select 1,2,3 -- a 3、爆表名&#xf…

leetcode:131.分割回文串

树形结构&#xff1a; 切割到字符串的尾部&#xff0c;就是叶子节点。 回溯算法三部曲&#xff1a; 1.递归的参数和返回值&#xff1a; 参数字符串s和startIndex切割线 2.确定终止条件&#xff1a; 当分割线到字符串末尾时到叶子节点&#xff0c;一种方案出现 3.单层搜索…

真、开源LLM-OLMo

论文&#xff1a;https://arxiv.org/pdf/2402.00838.pdf Weights https://huggingface.co/allenai/OLMo-7B Code https://github.com/allenai/OLMo Data https://huggingface.co/datasets/allenai/dolma Evaluation https://github.com/allenai/OLMo-Eval Adaptation http…

行业科普应用分享 | 用于安全和安保的仪器仪表

【前言】 物联网带来了对安全和安保的新要求。利用物联网&#xff0c;运营商可以从复杂和分布式的装置中获益。此外&#xff0c;自主系统在现代工业的运作中正变得越来越重要。 从制造业到农业&#xff0c;这些远程操作需要仪器提供持续监测&#xff0c;以提供安全和保障。这…

记录一次centos 使用selenium运行环境

这里写自定义目录标题 宝塔面板 安装 selenium安装google-chrome 宝塔面板 安装 selenium 安装google-chrome yum install https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm 查看chrome版本 google-chrome --version 下载对应chrome版本的chro…

uniapp微信小程序开发踩坑日记:Pinia持久化

如果你使用过Pinia&#xff0c;那你应该知道Pinia持久化插件&#xff1a;https://prazdevs.github.io/pinia-plugin-persistedstate/zh/ 但由于官方文档提供的说明并不是针对小程序开发&#xff0c;所以我们在使用这个插件实现uniapp小程序开发中Pinia持久化会出现问题 我在C…

java学习07---综合练习

飞机票 1.需求: 机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。 按照如下规则计算机票价格&#xff1a;旺季&#xff08;5-10月&#xff09;头等舱9折&#xff0c;经济舱8.5折&#xff0c;淡季&#xff08;11月到来年4月&#xff09;头等舱7…

人力资源智能化管理项目(day05:角色管理)

学习源码可以看我的个人前端学习笔记 (github.com):qdxzw/humanResourceIntelligentManagementProject 搭建页面结构 分页组件&#xff1a;设置layout&#xff0c;表示需要显示的内容&#xff0c;用逗号分隔&#xff0c;布局元素会依次显示。prev表示上一页&#xff0c;next为…

Ps:统计

Ps菜单&#xff1a;文件/脚本/统计 Scripts/Statistics 统计 Statistics脚本命令提供了一种高效的方法来处理和分析大量图像&#xff0c;使用户能够自动执行复杂的图像分析任务&#xff0c;并在多个图像间应用统计学方法。这个功能极大地扩展了 Photoshop 在科学研究、图像编辑…

ThreeDPose

还没跑: GitHub - moshoeu/ThreeDPoseModelDriver: 用3DPose骨骼位置驱动人形模型动画 GitHub - Doggerlas/3DPoseTracker: unity练手项目 https://github.com/digital-standard/ThreeDPoseTracker GitHub - xerifg/Real-time-3DposeTracking-Unity3D-UnityChan: capture hum…

Acwing 5469. 有效点对【正难则反+巧妙选择根节点】

原题链接&#xff1a;https://www.acwing.com/problem/content/5472/ 题目描述&#xff1a; 给定一个 n 个节点的无向树&#xff0c;节点编号 1∼n。 树上有两个不同的特殊点 x,y&#xff0c;对于树中的每一个点对 (u,v)(u≠v)&#xff0c;如果从 u 到 v 的最短路径需要经过…

C语言分钟计算

一.题目描述 给你同一天的两个时间(24小时制),求这两个时间内有多少分钟,保证第一个时间在第二个时间之前. 二.输入描述 输入两行,每行包括两个整数表示小时和分钟. 三.输出描述 输出分钟数. 四.示例 输入 10 10 11 05 输出 55 五.代码

【Linux中增加Nginx虚拟主机配置文件(conf.d)】后访问80端报403

在nginx.conf的http模块新增 include /etc/nginx/conf.d/*.conf; 后 重启nginx报403 处理办法&#xff1a; 1&#xff0c;如果nginx是root用户启动的 则需要 将nginx.config的user改为和启动用户一致 2.权限问题&#xff0c;如果nginx没有web目录的操作权限&#xff0c;也会出…

《21天精通IPv4 to IPv6》第3天:IPv6地址配置——如何为不同的系统配置IPv6?

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …