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

news2024/10/6 20:25:54
阅读此需阅读下面这些博客先
【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到单例池中。

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

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

相关文章

【python5】闭包/装饰器,

文章目录 1.闭包和装饰器&#xff1a;函数里return就是闭包2.解析eeprom&#xff1a;如下是二进制文件&#xff0c;C8是一个字节3.json/configparser/optparse&#xff1a;json.dumps&#xff08;将字典转化为字符串&#xff0c;将json信息写进文件&#xff09;&#xff0c;jso…

PLC在物联网中位置—承上启下,与上位机下位机的关联。

谈到物联网&#xff0c;就绕不开PLC&#xff0c;本文着重介绍PLC的定义、与单片机的区分&#xff0c;价值、物联网中的位置&#xff0c;以及和上位机、下位机的关联&#xff0c;让友友们对PLC有个全面的认知。 一、什么是PLC PLC是可编程逻辑控制器&#xff08;Programmable L…

Java:字符集、IO流 --黑马笔记

一、字符集 1.1 字符集的来历 我们知道计算机是美国人发明的&#xff0c;由于计算机能够处理的数据只能是0和1组成的二进制数据&#xff0c;为了让计算机能够处理字符&#xff0c;于是美国人就把他们会用到的每一个字符进行了编码&#xff08;所谓编码&#xff0c;就是为一个…

python-自动化篇-终极工具-用GUI自动控制键盘和鼠标-pyautogui

文章目录 用GUI自动控制键盘和鼠标pyautogui 模块鼠标屏幕位置——移动地图——pyautogui.size鼠标位置——自身定位——pyautogui.position()移动鼠标——pyautogui.moveTo拖动鼠标滚动鼠标 键盘按下键盘释放键盘 开始与结束通过注销关闭所有程序 用GUI自动控制键盘和鼠标 在…

2024 CKS 题库 | 4、RBAC - RoleBinding

CKS 题库 4、RBAC - RoleBinding Context 绑定到 Pod 的 ServiceAccount 的 Role 授予过度宽松的权限。完成以下项目以减少权限集。 Task 一个名为 web-pod 的现有 Pod 已在 namespace db 中运行。 编辑绑定到 Pod 的 ServiceAccount service-account-web 的现有 Role&#…

shell脚本之文件处理命令及字符切片处理

目录 一、文件处理工具 1、tr命令 1.1 转换字符 1.2 压缩字符及删除字符 2、seq命令 3、cut命令 ​4、tac命令 5、rev命令 6、sort命令 ​​​​​7、uniq命令 ​8、echo命令 9、date命令 二、字符串切片处理 1、取字符串的长度 2、跳过字符串最前边的字符 3、…

利用Python和pandas库进行股票技术分析:移动平均线和MACD指标

利用Python和pandas库进行股票技术分析&#xff1a;移动平均线和MACD指标 介绍准备工作数据准备计算移动平均线计算MACD指标结果展示完整代码演示 介绍 在股票市场中&#xff0c;技术分析是一种常用的方法&#xff0c;它通过对股票价格和交易量等历史数据的分析&#xff0c;来…

LeetCode Python - 9.回文数

文章目录 题目答案运行结果 题目 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 例如&am…

Python基础语法(内置Python, pycharm配置方式)

一.工具安装与配置 1.Python解释器的安装 官网网址:https://www.python.org/ 选择downloads即可(Windows用户点击Windows, 苹果用户点击macOS) 找到最新版本, 并选择 Download Windows installer (64-bit) 下载完成后可在得到一个安装包进行安装(安装时间较长) 安装完成后…

Stable Diffusion 模型下载:DreamShaper(梦想塑造者)

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 DreamShaper 是一个分格多样的大模型&#xff0c;可以生成写实、原画、2.5D 等多种图片&#xff0c;能生成很棒的人像和风景图。 条目内容类型大模型基础模型SD 1…

C++多态:定义、实现及原理/继承关系中的虚函数表

目录​​​​​​​ 一、多态的定义及实现 1.1多态的概念​​​​​​​ 1.2多态的构成条件 1.3virtual虚函数 1.4虚函数的重写 二、override和final 三、抽象类 3.1概念 3.2接口继承和实现继承 四、多态的原理 4.1虚函数表 4.2 多态的原理 4.3动态绑定与静态绑定…

自动化AD域枚举和漏洞检测脚本

linWinPwn 是一个 bash 脚本&#xff0c;可自动执行许多 Active Directory 枚举和漏洞检查。该脚本基于很多现有工具实现其功能&#xff0c;其中包括&#xff1a;impacket、bloodhound、netexec、enum4linux-ng、ldapdomaindump、lsassy、smbmap、kerbrute、adidnsdump、certip…

华为OD机试 - 最长子字符串的长度(一) (Python C C++ JavaGo JS PHP)

题目描述 给定一个字符串s&#xff0c;将其视为环形&#xff0c;要求找出其中出现偶数次的最长子字符串的长度。 输入描述 输入一个字符串s。 输出描述 输出一个整数&#xff0c;表示出现偶数次的最长子字符串的长度。 示例 解析题目 本题要求在给定的字符串中找出出现偶…

OpenCV入门:图像处理的基石

在数字图像处理领域&#xff0c;OpenCV&#xff08;开源计算机视觉库&#xff09;是一个不可或缺的工具。它包含了一系列强大的算法和函数&#xff0c;使得开发者可以轻松地处理图像和视频数据。本文将带你走进OpenCV的世界&#xff0c;了解其基本概念和常见应用。 1. OpenCV简…

Java 集合、迭代器

Java 集合框架主要包括两种类型的容器&#xff0c;一种是集合&#xff08;Collection&#xff09;&#xff0c;存储一个元素集合&#xff0c;另一种是图&#xff08;Map&#xff09;&#xff0c;存储键/值对映射。Collection 接口又有 3 种子类型&#xff0c;List、Set 和 Queu…

【EAI 015】CLIPort: What and Where Pathways for Robotic Manipulation

论文标题&#xff1a;CLIPort: What and Where Pathways for Robotic Manipulation 论文作者&#xff1a;Mohit Shridhar1, Lucas Manuelli, Dieter Fox1 作者单位&#xff1a;University of Washington, NVIDIA 论文原文&#xff1a;https://arxiv.org/abs/2109.12098 论文出处…

sheng的学习笔记-docker部署springboot

部署文章目录&#xff1a;目录 docker部署&#xff0c;原理&#xff0c;命令&#xff0c;可以参考&#xff1a;docker原理图&#xff0c;部署&#xff0c;命令 目录 将springboot部署到docker中 遇到过的问题&#xff1a; pom配置 操作步骤 生成jar 构建镜像 查看镜像d…

C语言之预处理详解

目录 1. 预定义符号2. #define定义常量3. #define定义宏练习 4. 带有副作用的宏参数5. 宏替换的规则6. 宏函数的对比宏和函数的一个对比 7. #和###运算符##运算符 8. 命名约定9. #undef10. 命令行定义11. 条件编译常见的条件编译 12. 头文件的包含头文件的包含方式库文件包含嵌…

移动端web开发布局

目录 flex布局&#xff1a; flex布局父项常见属性&#xff1a; flex布局子项常见属性&#xff1a; REM适配布局&#xff1a; 响应式布局&#xff1a; flex布局&#xff1a; 需要先给父类盒子设置display&#xff1a;flex flex是flexiblebox的缩写&#xff0c;意为"弹…

【DC渗透系列】DC-4靶场

主机发现 arp-scan -l┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:6b:ed:27, IPv4: 192.168.100.251 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.100.1 00:50:56:c0:00:08 …