Spring IOC 的理解

news/2024/4/19 0:28:20

IoC容器是什么?

IoC文英全称Inversion of Control,即控制反转,我么可以这么理解IoC容器:

把某些业务对象的的控制权交给一个平台或者框架来同一管理,这个同一管理的平台可以称为IoC

容器。

我们刚开始学习spring的时候会经常看到的类似下面的这代码:

上面代码中,在创建ApplicationContext实例对象过程中会创建一个spring容器,该容器会读取配置文

"cjj/models/beans.xml",并统一管理由该文件中定义好的所有bean实例对象,如果要获取某个bean

实例,使用getBean方法就行了。例如我们只需要将Person提前配置在beans.xml文件中(可以理解为

注入),之后我们可以不需使用new Person()的方式创建实例,而是通过容器来获取Person实例,这就

相当于将Person的控制权交由spring容器了,差不多这就是控制反转的概念。

那在创建IoC容器时经历了哪些呢?为此,先来了解下SpringIoC容器分类,继而根据一个具体的容器

来讲解IoC容器初始化的过程。

Spring中有两个主要的容器系列:

1. 实现BeanFactory接口的简单容器;

2. 实现ApplicationContext接口的高级容器。

ApplicationContext appContext = new

ClassPathXmlApplicationContext("cjj/models/beans.xml");

Person p = (Person)appContext.getBean("person");ApplicationContext比较复杂,它不但继承了BeanFactory的大部分属性,还继承其它可扩展接口,扩

展的了许多高级的属性,其接口定义如下:

public interface ApplicationContext extends EnvironmentCapable,

ListableBeanFactory, //继承于

BeanFactory HierarchicalBeanFactory,//继承于

BeanFactory

MessageSource,

//

ApplicationEventPublisher,// ResourcePatternResolver

//继承ResourceLoader,用于获取resource对象

BeanFactory子类中有一个DefaultListableBeanFactory类,它包含了基本Spirng IoC容器所具有的

重要功能,开发时不论是使用BeanFactory系列还是ApplicationContext系列来创建容器基本都会使用

DefaultListableBeanFactory类,可以这么说,在spring中实际上把它当成默认的IoC容器来使用。下

文在源码实例分析时你将会看到这个类。

(注:文章有点长,需要细心阅读,不同版本的spring中源码可能不同,但逻辑几乎是一样的,如果可

以建议还是看源码 ^_^

回到本文正题上来,关于Spirng IoC容器的初始化过程在《Spirng技术内幕:深入解析Spring架构与设

计原理》一书中有明确的指出,IoC容器的初始化过程可以分为三步:

1. Resource定位(Bean的定义文件定位)

2. Resource定位好的资源载入到BeanDefinition

3. BeanDefiniton注册到容器中

第一步 Resource定位

ResourceSping中用于封装I/O操作的接口。正如前面所见,在创建spring容器时,通常要访问XML

置文件,除此之外还可以通过访问文件类型、二进制流等方式访问资源,还有当需要网络上的资源时可

以通过访问URLSpring把这些文件统称为ResourceResource的体系结构如下:

常用的resource资源类型如下:

FileSystemResource以文件的绝对路径方式进行访问资源,效果类似于Java中的File;

ClassPathResourcee以类路径的方式访问资源,效果类似于

this.getClass().getResource("/").getPath();ServletContextResourceweb应用根目录的方式访问资源,效果类似于

request.getServletContext().getRealPath("");

UrlResource访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;

ByteArrayResource: 访问字节数组资源的实现类。

那如何获取上图中对应的各种Resource对象呢?

Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,该接口的实例对象中可以获取

一个resource对象,也就是说将不同Resource实例的创建交给ResourceLoader的实现类来处理。

ResourceLoader接口中只定义了两个方法:

Resource getResource(String location); //通过提供的资源location参数获取Resource实例

ClassLoader getClassLoader(); // 获取ClassLoader,通过ClassLoader可将资源载入JVM

注:ApplicationContext的所有实现类都实现RecourceLoader接口,因此可以直接调用

getResource(参数)获取Resoure对象不同的ApplicatonContext实现类使用getResource方法取得

的资源类型不同,例如:FileSystemXmlApplicationContext.getResource获取的就是

FileSystemResource实例;ClassPathXmlApplicationContext.gerResource获取的就是

ClassPathResource实例;XmlWebApplicationContext.getResource获取的就是

ServletContextResource实例,另外像不需要通过xml直接使用注解@Configuation方式加载资源的

AnnotationConfigApplicationContext等等。

在资源定位过程完成以后,就为资源文件中的bean的载入创造了I/O操作的条件,如何读取资源中的数

据将会在下一步介绍的BeanDefinition的载入过程中描述。

*第二步 通过返回的*resource对象,进行BeanDefinition的载入

1.什么是BeanDefinition? BeanDefinitionResource的联系呢?

官方文档中对BeanDefinition的解释如下:

A BeanDefinition describes a bean instance, which has property values, constructor argument

values, and further information supplied by concrete implementations.

它们之间的联系从官方文档描述的一句话:Load bean definitions from the specified resource中可见

一斑。

/**

* Load bean definitions from the specified resource.

* @param resource the resource descriptor

* @return the number of bean definitions found

* @throws BeanDefinitionStoreException in case of loading or parsing errors

*/

int loadBeanDefinitions(Resource resource) throws

BeanDefinitionStoreException;

总之,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源

对象中的bean而来的,这些beanSpirng IoC容器内部表示成了的BeanDefintion这样的数据结构,

IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。

2.如何将BeanDefinition载入到容器?

Spring中配置文件主要格式是XML,对于用来读取XML型资源文件来进行初始化的IoC 容器而

言,该类容器会使用到AbstractXmlApplicationContext类,该类定义了一个名为

loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于获取BeanDefinition// 该方法属于AbstractXmlApplicationContect

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws

BeansException, IOException {

XmlBeanDefinitionReader beanDefinitionReader = new

XmlBeanDefinitionReader(beanFactory);

beanDefinitionReader.setEnvironment(this.getEnvironment());

beanDefinitionReader.setResourceLoader(this);

beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

this.initBeanDefinitionReader(beanDefinitionReader);

// 用于获取BeanDefinition

this.loadBeanDefinitions(beanDefinitionReader);

}

此方法在具体执行过程中首先会new一个与容器对应的BeanDefinitionReader型实例对象,然后将生成

BeanDefintionReader实例作为参数传入loadBeanDefintions(XmlBeanDefinitionReader),继续往

下执行载入BeanDefintion的过程。例如AbstractXmlApplicationContext有两个实现类:

FileSystemXmlApplicationContextClassPathXmlApplicationContext,这些容器在调用此方法时会

创建一个XmlBeanDefinitionReader对象专门用来载入所有的BeanDefinition

下面以XmlBeanDefinitionReader对象载入BeanDefinition为例,使用源码说明载入BeanDefinition

过程:

// 该方法属于AbstractXmlApplicationContectprotected void

loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException,

IOException {

Resource[] configResources = getConfigResources();//获取所有定位到的

resource资源位置(用户定义)

if (configResources != null) {

reader.loadBeanDefinitions(configResources);//载入resources

}

String[] configLocations = getConfigLocations();//获取所有本地配置文件的位置

(容器自身)

if (configLocations != null) {

reader.loadBeanDefinitions(configLocations);//载入resources

}

}

通过上面代码将用户定义的资源以及容器本身需要的资源全部加载到reader中,

reader.loadBeanDefinitions方法的源码如下:// 该方法属于AbstractBeanDefinitionReader, 父接口BeanDefinitionReader

@Override

public int loadBeanDefinitions(Resource... resources) throws

BeanDefinitionStoreException {

Assert.notNull(resources, "Resource array must not be null");

int counter = 0;

for (Resource resource : resources) {

// 将所有资源全部加载,交给AbstractBeanDefinitionReader的实现子类处理这些

resource

counter += loadBeanDefinitions(resource);

}

return counter;

}

BeanDefinitionReader接口定义了 int loadBeanDefinitionsResource resource)方法:

int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

int loadBeanDefinitions(Resource... resources) throws

BeanDefinitionStoreException;

XmlBeanDefinitionReader 类实现了BeanDefinitionReader接口中的loadBeanDefinitions(Resource)

方法,其继承关系如上图所示。XmlBeanDefinitionReader类中几主要对加载的所有resource开始进行

处理,大致过程是,先将resource包装为EncodeResource类型,然后处理,为生成BeanDefinition

象做准备,其主要几个方法的源码如下:

public int loadBeanDefinitions(Resource resource) throws

BeanDefinitionStoreException {

// 包装resourceEncodeResource类型

return loadBeanDefinitions(new EncodedResource(resource));

}

// 加载包装后的EncodeResource资源

public int loadBeanDefinitions(EncodedResource encodedResource) throws

BeanDefinitionStoreException {

Assert.notNull(encodedResource, "EncodedResource must not be null");

if (logger.isInfoEnabled()) {

logger.info("Loading XML bean definitions from " +

encodedResource.getResource());

}try {

// 通过resource对象得到XML文件内容输入流,并为IOInputSource做准备

InputStream inputStream =

encodedResource.getResource().getInputStream();

try {

// Create a new input source with a byte stream.

InputSource inputSource = new InputSource(inputStream);

if (encodedResource.getEncoding() != null) {

inputSource.setEncoding(encodedResource.getEncoding());

}

// 开始准备 load bean definitions from the specified XML file

return doLoadBeanDefinitions(inputSource,

encodedResource.getResource());

}

finally {

inputStream.close();

}

}

catch (IOException ex) {

throw new BeanDefinitionStoreException(

"IOException parsing XML document from " +

encodedResource.getResource(), ex);

}

}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource

resource)

throws BeanDefinitionStoreException {

try {

// 获取指定资源的验证模式

int validationMode = getValidationModeForResource(resource);

// 从资源对象中加载DocumentL对象,大致过程为:将resource资源文件的内容读入到

document

// DocumentLoader在容器读取XML文件过程中有着举足轻重的作用!

// XmlBeanDefinitionReader实例化时会创建一个DefaultDocumentLoader型的私有

属性,继而调用loadDocument方法

// inputSource--要加载的文档的输入源

Document doc = this.documentLoader.loadDocument(

inputSource, this.entityResolver, this.errorHandler,

validationMode, this.namespaceAware);

// document文件的bean封装成BeanDefinition,并注册到容器

return registerBeanDefinitions(doc, resource);

}

catch ...()

}

DefaultDocumentLoader大致了解即可,感兴趣可继续深究,其源码如下:(看完收起,便于阅读下

文)

View Code

上面代码分析到了registerBeanDefinitions(doc, resource)这一步,也就是准备将Document中的Bean

按照Spring bean语义进行解析并转化为BeanDefinition类型,这个方法的具体过程如下:/**

* 属于XmlBeanDefinitionReader

* Register the bean definitions contained in the given DOM document.

* @param doc the DOM document

* @param resource

* @return the number of bean definitions found

* @throws BeanDefinitionStoreException

*/

public int registerBeanDefinitions(Document doc, Resource resource) throws

BeanDefinitionStoreException {

// 获取到DefaultBeanDefinitionDocumentReader实例

BeanDefinitionDocumentReader documentReader =

createBeanDefinitionDocumentReader();

// 获取容器中bean的数量

int countBefore = getRegistry().getBeanDefinitionCount();

documentReader.registerBeanDefinitions(doc, createReaderContext(resource));

return getRegistry().getBeanDefinitionCount() - countBefore;

}

通过 XmlBeanDefinitionReader 类中的私有属性 documentReaderClass 可以获得一个

DefaultBeanDefinitionDocumentReader 实例对象:

private Class<?> documentReaderClass =

DefaultBeanDefinitionDocumentReader.class;

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {

return

BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.document

ReaderClass));

}

DefaultBeanDefinitionDocumentReader实现了BeanDefinitionDocumentReader接口,它的

registerBeanDefinitions方法定义如下:

// DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)

{

this.readerContext = readerContext;

logger.debug("Loading bean definitions");

// 获取docroot节点,通过该节点能够访问所有的子节点

Element root = doc.getDocumentElement();

// 处理beanDefinition的过程委托给BeanDefinitionParserDelegate实例对象来完成

BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);

// Default implementation is empty.

// Subclasses can override this method to convert custom elements into

standard Spring bean definitions

preProcessXml(root);

// 核心方法,代理

parseBeanDefinitions(root, delegate);postProcessXml(root);

}

上面出现的BeanDefinitionParserDelegate非常非常重要(需要了解代理技术,如JDK动态代理、

cglib动态代理等)。Spirng BeanDefinition的解析就是在这个代理类下完成的,此类包含了各种对符合

Spring Bean语义规则的处理,比如、、等的检测。

parseBeanDefinitions(root, delegate)方法如下:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate

delegate) {

if (delegate.isDefaultNamespace(root)) {

NodeList nl = root.getChildNodes();

// 遍历所有节点,做对应解析工作

// 如遍历到<import>标签节点就调用importBeanDefinitionResource(ele)方法对应

处理

// 遍历到<bean>标签就调用processBeanDefinition(ele,delegate)方法对应处理

for (int i = 0; i < nl.getLength(); i++) {

Node node = nl.item(i);

if (node instanceof Element) {

Element ele = (Element) node;

if (delegate.isDefaultNamespace(ele)) {

parseDefaultElement(ele, delegate);

}

else {

//对应用户自定义节点处理方法

delegate.parseCustomElement(ele);

}

}

}

}

else {

delegate.parseCustomElement(root);

}

}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate

delegate) {

// 解析<import>标签

if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {

importBeanDefinitionResource(ele);

}

// 解析<alias>标签

else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {

processAliasRegistration(ele);

}

// 解析<bean>标签,最常用,过程最复杂

else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {

processBeanDefinition(ele, delegate);

}

// 解析<beans>标签

else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {

// recurse

doRegisterBeanDefinitions(ele);}

}

这里针对常用的标签中的方法做简单介绍,其他标签的加载方式类似:

/**

* Process the given bean element, parsing the bean definition

* and registering it with the registry.

*/

protected void processBeanDefinition(Element ele,

BeanDefinitionParserDelegate delegate) {

// 该对象持有beanDefinitionnamealias,可以使用该对象完成beanDefinition向容

器的注册

BeanDefinitionHolder bdHolder =

delegate.parseBeanDefinitionElement(ele);

if (bdHolder != null) {

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

try {

// 注册最终被修饰的bean实例,下文注册beanDefinition到容器会讲解该方法

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,

getReaderContext().getRegistry());

}

catch (BeanDefinitionStoreException ex) {

getReaderContext().error("Failed to register bean definition with

name '" +

bdHolder.getBeanName() + "'", ele, ex);

}

// Send registration event.

getReaderContext().fireComponentRegistered(new

BeanComponentDefinition(bdHolder));

}

}

parseBeanDefinitionElement(Element ele)方法会调用parseBeanDefinitionElement(ele, null)方法,

并将值返回BeanDefinitionHolder对象,这个方法将会对给定的标签进行解析,如果在解析标签的

过程中出现错误则返回null

需要强调一下的是parseBeanDefinitionElement(ele, null)方法中产生了一个抽象类型的BeanDefinition

实例,这也是我们首次看到直接定义BeanDefinition的地方,这个方法里面会将标签中的内容解析到

BeanDefinition中,之后再对BeanDefinition进行包装,将它与beanName,Alias等封装到

BeanDefinitionHolder 对象中,该部分源码如下:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {

return parseBeanDefinitionElement(ele, null);

}

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele,

BeanDefinition containingBean) {

String id = ele.getAttribute(ID_ATTRIBUTE);String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

...()

String beanName = id;

...(略)

// 从上面按过程走来,首次看到直接定义BeanDefinition !!!

// 该方法会对<bean>节点以及其所有子节点如<property><List><Set>等做出解析,具

体过程本文不做分析(太多太长)

AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele,

beanName, containingBean);

if (beanDefinition != null) {

if (!StringUtils.hasText(beanName)) {

...()

}

String[] aliasesArray = StringUtils.toStringArray(aliases);

return new BeanDefinitionHolder(beanDefinition, beanName,

aliasesArray);

}

return null;

}

第三步,将BeanDefiniton注册到容器中

最终Bean配置会被解析成BeanDefinition并与beanName,Alias一同封装到BeanDefinitionHolder

中, 之后beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注册

DefaultListableBeanFactory.beanDefinitionMap中。之后客户端如果要获取Bean对象,Spring

器会根据注册的BeanDefinition信息进行实例化。

BeanDefinitionReaderUtils类:

public static void registerBeanDefinition(

BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory)

throws BeansException {

// Register bean definition under primary name.

String beanName = bdHolder.getBeanName(); // 注册beanDefinition!!!

beanFactory.registerBeanDefinition(beanName,

bdHolder.getBeanDefinition());

// 如果有别名的话也注册进去,Register aliases for bean name, if any.

String[] aliases = bdHolder.getAliases();

if (aliases != null) {

for (int i = 0; i < aliases.length; i++) {

beanFactory.registerAlias(beanName, aliases[i]);

}

}

}DefaultListableBeanFactory实现了上面调用BeanDefinitionRegistry接口的

registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,这一部分的主要逻辑是向

DefaultListableBeanFactory对象的beanDefinitionMap中存放beanDefinition,当初始化容器进行

bean初始化时,在bean的生命周期分析里必然会在这个beanDefinitionMap中获取beanDefition

例,有机会成文分析一下bean的生命周期,到时可以分析一下如何使用这个beanDefinitionMap

registerBeanDefinition( beanName, bdHolder.getBeanDefinition() )方法具体方法如下:

/** Map of bean definition objects, keyed by bean name */

private final Map<String, BeanDefinition> beanDefinitionMap = new

ConcurrentHashMap<String, BeanDefinition>(256);

public void registerBeanDefinition(String beanName, BeanDefinition

beanDefinition)

throws BeanDefinitionStoreException {

Assert.hasText(beanName, "Bean name must not be empty");

Assert.notNull(beanDefinition, "Bean definition must not be null");

if (beanDefinition instanceof AbstractBeanDefinition) {

try {

((AbstractBeanDefinition) beanDefinition).validate();

}

catch (BeanDefinitionValidationException ex) {

throw new

BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,

"Validation of bean definition failed", ex);

}

}

// beanDefinitionMap是个ConcurrentHashMap类型数据,用于存放

beanDefinition,它的key值是beanName

Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);

if (oldBeanDefinition != null) {

if (!this.allowBeanDefinitionOverriding) {

throw new

BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,

"Cannot register bean definition [" + beanDefinition + "]

for bean '" + beanName +

"': there's already [" + oldBeanDefinition + "] bound");

}

else {

if (logger.isInfoEnabled()) {

logger.info("Overriding bean definition for bean '" +

beanName +

"': replacing [" + oldBeanDefinition + "] with [" +

beanDefinition + "]");

}

}

}

else {

this.beanDefinitionNames.add(beanName);

} // 将获取到的BeanDefinition放入Map中,容器操作使用bean时通过这个

HashMap找到具体的BeanDefinition


http://www.ppmy.cn/news/73345.html

相关文章

Java读取txt文件:TSP问题测试算例att48.txt

目录 1读取文件1.1 JDK1.5的Scanner类读取1.2 JDK8的Files.linesStream流按行读取&#xff08;推荐&#xff09;1.3 JDK11提供的Files.readString()1.4 FileInputStreamInputStreamReaderBufferedReader按行读取1.5 经典管道流方式&#xff08;推荐&#xff09; 2 JAVA快速统计…

GEE遥感云大数据林业应用典型案例及GPT模型

详情点击链接&#xff1a;GEE遥感云大数据林业应用典型案例及GPT模型 一&#xff1a;平台及基础开发平台 GEE平台及典型应用案例&#xff1b;GEE开发环境及常用数据资源&#xff1b;ChatGPT、文心一言等GPT模型、帐号申请及林业遥感JavaScript基础&#xff1b;GEE遥感云重要概…

FLEXPART--空气块轨迹-扩散模式

模式简介&#xff1a; FLEXPART(Flexible Particle Dispersion Model)模式是由挪威大气研究所(Norwegian Institute for Air Research)和德国慕尼黑工业大学(Technical University of Munich)联合开发的空气块轨迹&#xff0d;扩散模式, 其通过计算区域内所有气块的运动轨迹进…

[CTF/网络安全] 攻防世界 backup 解题详析

[CTF/网络安全] 攻防世界 backup 解题详析 PHP备份文件名备份文件漏洞成因备份文件名常用后缀姿势总结 题目描述&#xff1a;X老师忘记删除备份文件&#xff0c;他派小宁同学去把备份文件找出来,一起来帮小宁同学吧&#xff01; PHP备份文件名 PHP 脚本文件的备份文件名&#…

(C语言版)力扣(LeetCode)+牛客网(nowcoder)二叉树基础oj练习

二叉树基础oj练习 965. 单值二叉树题目解法 100. 相同的树题目解法 101. 对称二叉树题目解法 144. 二叉树的前序遍历题目解法 94. 二叉树的中序遍历题目解法 145. 二叉树的后序遍历题目解法 572. 另一棵树的子树题目解法 KY11 二叉树遍历题目解法 结语 965. 单值二叉树 题目 …

springboot+java超市收银管理系统idea

考虑到实际生活中在超市 POS 收银管理方面的需要以及对该系统认真的分析&#xff0c;将系统权限按管理员和员工这两类涉及用户划分。 Spring Boot 是 Spring 家族中的一个全新的框架&#xff0c;它用来简化Spring应用程序的创建和开发过程。也可以说 Spring Boot 能简化我们之…

UML类图画法及其关系

UML类图画法及其关系 本文主要是介绍 UML类图画法及其关系&#xff0c;方便今后温习&#xff01;&#xff01;&#xff01; 一、类之间的关系汇总 泛化&#xff08;Generalization&#xff09;实现&#xff08;Realization&#xff09;关联&#xff08;Association&#xff…

Linux 学习笔记(七):时间片

一、时间片概念 时间片&#xff08;timeslice&#xff09;又称为 “量子”&#xff08;quantum&#xff09;或 “处理器片”&#xff08;processor slice&#xff09;&#xff0c;是分时操作系统分配给每个正在运行的进程微观上的一段 CPU 时间&#xff08;在抢占内核中是&…

将有序数组转换为二叉树

md这个破CSDN模板怎么没了&#xff0c;编辑器也死难用&#xff0c;气死 1、题目 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不…

异地研发团队都使用哪些研发协同工具?盘点7类最主流的研发管理协同软件

产品研发场景下好用的协同办公软件有哪些&#xff1f;分享7类研发过程中主流的协同办公软件&#xff0c;比如项目管理协作与问题跟踪工具PingCode、代码托管与版本控制平台github、持续集成与持续部署&#xff08;CI/CD&#xff09;工具jinkens、文档协作与知识管理工具conflue…

Node开发Web后台服务

简介 Node.js 是一个基于Google Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型&#xff0c;使其轻量又高效。Node.js 的包管理器 npm&#xff0c;是全球最大的开源库生态系统。 能方便地搭建响应速度快、易于扩展的网络应用&#…

支付宝沙箱支付(java电脑版)

目录 下载支付demo配置环境AlipayConfig 下载支付demo 网址&#xff1a;https://open.alipay.com/ 下载并打开项目发现无法运行&#xff1a; 手动转化项目&#xff1a; 等待下载整理一下maven pom 通过tomat部署运行测试。 导入阿里支付的pom依赖 <dependency> &l…

《计算机网络—自顶向下方法》 Wireshark实验(十):NAT 协议分析

NAT&#xff08;Network Address Translation&#xff09;网络地址转换&#xff0c;即在私有地址和全局地址之间转换的协议。私有地址是不能用在 Internet 上(路由器将丢弃寻址这种地址的包)的内部地址。这些地址是不能够在公网上面用的&#xff0c;只能用在局域网的内部。私有…

可以白嫖的语音识别开源项目whisper的搭建详细过程 | 如何在Linux中搭建OpenAI开源的语音识别项目Whisper

原文来自我个人的博客。 1、前提条件 服务器为GPU服务器。点击这里跳转到我使用的GPU服务器。我搭建 whisper 选用的是 NVIDIA A 100显卡&#xff0c;4GB显存。 Python版本要在3.8~3.11之间。 输入下面命令查看使用的Python版本。 python3 -V2、安装Anaconda 为啥要安装A…

教材管理系统

目 录 第一章 引言 3 1.1 背景 3 1.1.1教材管理系统 3 1.1.2信息管理系统 3 1.2开发教材管理系统的目的和原则 5 1.3开发环境介绍 6 1.3.1 开发平台 6 1.3.2 数据库查询语言——SQL 8 1.3.3 数据库设计工具——ACCESS数据库管理系统 9 第二章 系统设计 11 2.1 系统分析 11 2.2 …

惯性导航论文详解:神经惯性定位

来源&#xff1a;投稿 作者&#xff1a;小灰灰 编辑&#xff1a;学姐 论文标题&#xff1a;Neural Inertial Localization 论文链接: https://arxiv.org/pdf/2203.15851v1.pdf 图1.从IMU测量到位置估计。给定惯性传感器数据&#xff08;左&#xff09;&#xff0c;我们的方法…

面了一个测试工程师要求月薪26K,总感觉他背了很多面试题...

最近有朋友去华为面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

测试类的使用

1.在pom文件中添加依赖 <dependencies> <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>compile</scope> </dependency> </dependencies>2.在s…

Vmware Linux磁盘空间扩容

Linux磁盘空间扩容 VMware虚拟机中配置&#xff08;1&#xff09;进入虚拟机设置界面&#xff0c;选择扩展磁盘容量。&#xff08;2&#xff09; 本次是在原来30G的基础上扩展为50G。 Linux中设置&#xff08;1&#xff09; 可以看出sda3是根分区&#xff0c;下面按照博客提示&…

掌握XPath:安装配置、解析流程、语法和实战练习全攻略

目录 引言 xpath安装与使用 解析流程与使用 xpath语法 xpath实战练习 引言 众所周知&#xff0c;XPath是Web开发中重要的工具之一&#xff0c;可以帮助我们在HTML或XML文档中快速定位和选择内容。但是对于初学者来说&#xff0c;XPath的安装配置、语法解析以及实际应用可…