【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就是用的下三行,所以干脆就这样了):

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

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

一、AnnotationConfigApplicationContext()具体流程

可以说就三步:

  1. 构造 DefaultListableBeanFactory 实例;
    • 调用构造方法首先是先执行的父类构造,父类构造会实例化一个 DefaultListableBeanFactory。
    • 在这里插入图片描述
  2. 实例化 AnnotatedBeanDefinitionReader 实例;
  3. 实例化 ClassPathBeanDefinitionScanner 扫描器。
	public AnnotationConfigApplicationContext() {// super(); 这里会构造 BeanFactoryStartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");// 额外会创建StandardEnvironmentthis.reader = new AnnotatedBeanDefinitionReader(this);createAnnotatedBeanDefReader.end();// 会填充 Environment 和 ClassLoader 类加载器;// 会将 @Component 注解放入到 includeFilter 集合中,扫描的时候筛选出对应的资源好封装成BeanDefinitionthis.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类型的BeanDefinitionif (!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类型的BeanDefinitionif (!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,只要是单例就行,是懒加载也会解析为对应的ApplicationListenerif (!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 源码分析

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

	@Overridepublic 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. 添加三个单例BeanprepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 子类来设置一下BeanFactorypostProcessBeanFactory(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,要么是用户设置的,要么是DelegatingMessageSourceinitMessageSource();// Initialize event multicaster for this context.// 设置ApplicationContext的applicationEventMulticaster,要么是用户设置的,要么是SimpleApplicationEventMulticasterinitApplicationEventMulticaster();// 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.// 实例化所有非懒加载的单例beanfinishBeanFactoryInitialization(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到单例池中。

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

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

相关文章

2 月 7 日算法练习- 数据结构-并查集

并查集 并查集是一种图形数据结构&#xff0c;用于存储图中结点的连通关系。 每个结点有一个父亲&#xff0c;可以理解为“一只伸出去的手”&#xff0c;会指向另外一个点&#xff0c;初始时指向自己。 一个点的根节点是该点的父亲的父亲的的父亲&#xff0c;直到某个点的父亲…

【NodeJS】006- API模块与会话控制介绍d

1.简介 1.1 接口是什么 接口是 前后端通信的桥梁 简单理解&#xff1a;一个接口就是 服务中的一个路由规则 &#xff0c;根据请求响应结果 接口的英文单词是 API (Application Program Interface)&#xff0c;所以有时也称之为 API 接口 这里的接口指的是『数据接口』&#…

游戏服务器租用价格表_TOP3费用对比

游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;华为云26元&#xff0c;游戏服务器配置从4核16G、4核32G、8核32G、16核64G等配置可选&#xff0c;游戏专业服务器公网带宽10M、12M、15M…

大模型训练所需的硬件配置

1. 引入 训练一个大模型&#xff0c;到底需要投入多少块GPU&#xff0c;需要多少数据&#xff0c;训练多长时间能达到一个不错的效果&#xff1f; 本文引用靠谱的数据&#xff0c;来回答这些问题。 2. 全流程训练 大模型的训练&#xff0c;简单来说&#xff0c;分为Pretrain…

C#,普洛尼克数(Pronic Number)的算法与源代码

1 普洛尼克数(pronic number) 普洛尼克数(pronic number)&#xff0c;也叫矩形数、欧波朗数(oblong number)&#xff0c;是两个连续非负整数的积&#xff0c;即mn*(n1)。第n个普洛尼克数侪是n个三角形数个两倍。 2 计算结果 3 源程序 using System; namespace Legalsoft.Tru…

02 数据库管理 数据表管理

文章目录 数据库管理数据表管理基础数据类型表的基本操作 数据库管理 查看已有库 show databases; 创建库 create database 库名 [character set utf8]; e.g. 创建stu数据库&#xff0c;编码为utf8 create database stu character set utf8; create database stu charsetutf8;…

禁止文件外发,文件禁止外发的方法

在当今的企业环境中&#xff0c;数据安全至关重要。 什么是企业文件外发&#xff1f; 企业文件外发指的是将企业内部的电子文件发送给组织外部的人员使用。 这种行为可能带来数据安全风险&#xff0c;因为电子文件自身具有易拷贝、易扩散、易传播的特性。 如果带有核心资产或…

LLMs之Llama2 70B:《Self-Rewarding Language Models自我奖励语言模型》翻译与解读

LLMs之Llama2 70B&#xff1a;《Self-Rewarding Language Models自我奖励语言模型》翻译与解读 目录 《Self-Rewarding Language Models》翻译与解读 Abstract 5 Conclusion结论 6 Limitations限制 《Self-Rewarding Language Models》翻译与解读 地址 文章地址&#xff1…

疑似针对安全研究人员的窃密与勒索

前言 笔者在某国外开源样本沙箱平台闲逛的时候&#xff0c;发现了一个有趣的样本&#xff0c;该样本伪装成安全研究人员经常使用的某个渗透测试工具的破解版压缩包&#xff0c;对安全研究人员进行窃密与勒索双重攻击&#xff0c;这种双重攻击的方式也是勒索病毒黑客组织常用的…

Scrum敏捷开发管理全流程-敏捷管理工具

Leangoo领歌是款永久免费的专业的敏捷开发管理工具&#xff0c;提供端到端敏捷研发管理解决方案&#xff0c;涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 Leangoo领歌上手快、实施成本低&#xff0c;可帮助企业快速落地敏捷&#xff0c;提质增效、缩短周期、加速创新。…

娱乐直播APP开发:引领潮流,创新无界

随着互联网技术的飞速发展&#xff0c;娱乐直播APP已经成为现代人生活的重要组成部分。它以其独特的互动性、即时性和个性化&#xff0c;吸引了大量用户。本文将深入探讨娱乐直播APP开发的关键要素&#xff0c;以及如何在这个竞争激烈的市场中脱颖而出。 一、娱乐直播APP的核心…

第4章 表单与类视图

学习目标 熟悉Flask处理表单的方式&#xff0c;能够归纳在Flask程序中如何处理表单 掌握Flask-WTF扩展包的安装&#xff0c;能够借助pip工具安装Flask-WTF扩展包 掌握使用Flask-WTF创建表单的方式&#xff0c;能够独立使用Flask-WTF创建表单 掌握在模板中渲染表单的方式&…

基于vue+node.js的校园跳蚤市场系统多商家

校园跳蚤市场系统可以在短时间内完成大量的数据处理、帮助用户快速的查找校园跳蚤市场相关信息&#xff0c;实现的效益更加直观。校园跳蚤市场系统中采用nodejs技术和mysql数据库。主要包括管理员、发布者和用户三大部分&#xff0c;主要功能是实现对个人中心、用户管理、发布者…

CSS3弹性布局

传统的布局,基于盒状模型&#xff0c;依赖 display 属性 position属性 float属性。它对于那些特殊布局实现起来比较麻烦&#xff0c;就比如垂直居中&#xff0c;伸缩等。实现起来就不是很容易。 弹性布局是CSS3一种新的布局模式&#xff0c;是一种当页面需要适应不同的屏幕大…

uv机器电机方向极性

爱普生主板设置X、Y 电机方向极性&#xff1a;请根据实际情况设置&#xff0c;开机初始化时如果电机运动方向反了则修改此极性。 理光主板设置X、Y 电机方向极性

网课:[NOIP2017]奶酪——牛客(疑问)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 现有一块大奶酪&#xff0c;它的高度为 h&#xff0c;它的长度和宽度我们可以认为是无限大的&#xff0c;奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系&a…

Leecode之反转链表

一.题目及剖析 https://leetcode.cn/problems/reverse-linked-list/description/ 二.思路引入 设定三个指针,n1指向空, n2指向head,n3指向下一个元素,将n2->next指向n1,然后三个指针向后遍历重复即可 三.代码引入 /*** Definition for singly-linked list.* struct List…

[论文总结] 深度学习在农业领域应用论文笔记12

文章目录 1. 3D-ZeF: A 3D Zebrafish Tracking Benchmark Dataset (CVPR, 2020)摘要背景相关研究所提出的数据集方法和结果个人总结 2. Automated flower classification over a large number of classes (Computer Vision, Graphics & Image Processing, 2008)摘要背景分割…

开源版发卡小程序源码,云盘发卡微信小程序源码带PC端

一款发卡小程序。带PC端 系统微信小程序前端采用nuiapp 后端采用think PHP6 PC前端采用vue开发 使用HBuilderX工具打开&#xff0c;运行到微信小程序工具&#xff0c;系统会自动打包微信小程序代码 修改文件common/request/request.js 改成你的后端网址 微信小程序端完全…

python coding with ChatGPT 打卡第19天| 二叉树:合并二叉树

相关推荐 python coding with ChatGPT 打卡第12天| 二叉树&#xff1a;理论基础 python coding with ChatGPT 打卡第13天| 二叉树的深度优先遍历 python coding with ChatGPT 打卡第14天| 二叉树的广度优先遍历 python coding with ChatGPT 打卡第15天| 二叉树&#xff1a;翻转…