Spring系列 循环依赖

文章目录

  • 注入方式
  • 循环依赖的场景
  • 单例创建流程
    • getSingleton
    • createBean
      • doCreateBean
      • createBeanInstance
  • 循环依赖分析
    • 为什么都使用构造函数无法解决?
    • 为什么使用@Autowired可以解决?
    • 为什么要添加到 earlySingletonObjects 缓存中?
    • allowCircularReferences 的作用
    • 只用二级缓存能解决循环依赖吗?
    • 二级缓存什么时候放入一级缓存?
    • 循环依赖的暴露顺序
  • 参考资料

注入方式

一般注入方式可以分为自动注入和手动注入
自动注入我分为以下几类

  1. 构造函数参数
  2. setter 方法 + @Autowire,字段 + @Autowire/@Resource

除了自动注入,在 Bean 的生命周期回调中通过 ApplicationContext 进行手动赋值,我将这种方式称为手动注入

循环依赖的场景

下面是不同场景下循环依赖的解决情况的测试结果

依赖情况依赖注入方式循环依赖是否被解决
AB相互依赖均采用setter方法注入
AB相互依赖
allowCircularReferences = false
均采用setter方法注入
AB相互依赖均采用构造器注入
AB相互依赖A中注入B的方式为setter方法,
B中注入A的方式为构造器
AB相互依赖
allowCircularReferences = false
A中注入B的方式为setter方法,
B中注入A的方式为构造器
AB相互依赖B中注入A的方式为setter方法,
A中注入B的方式为构造器

单例创建流程

getSingleton

首先调用 getBean 时会先到单例缓存中去获取一次
在这里插入图片描述

getSingleton(beanName, true)这个方法实际上就是到缓存中尝试去获取Bean,在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry这个类中有三个Map,对应的就是这些缓存

  1. singletonObjects,存储的是所有创建好了的单例Bean
  2. earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
  3. singletonFactories,提前暴露的一个单例工厂

如下图所示:

在这里插入图片描述

下面我简化了一下 doGetBean 的部分,保留核心流程

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {// 最终要返回的对象Object beanInstance;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {// ... 此处简化了部分代码beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);} else {// ... 此处简化了部分代码if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);} catch (BeansException ex) {// ... 此处简化了部分代码destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}// ... 此处简化了部分代码}return beanInstance;
}

当获取一个 Bean 时会先去缓存中查一次,如果没有则走 getSingleton 带 ObjectFactory 参数的实现

Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)

这里传入的 ObjectFactory 是一个 lambda 表达式,就简单调用了 createBean 方法并返回,这个方法就是用来创建Bean的。

同样,简化后的方法内容如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {// ... 此处省略部分代码beforeSingletonCreation(beanName);boolean newSingleton = false;// ... 此处省略部分代码try {// 调用 createBean singletonObject = singletonFactory.getObject();newSingleton = true;} catch (IllegalStateException ex) {// ... 此处省略部分代码} finally {// ... 此处省略部分代码afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}}return singletonObject;}
}

调用 createBean 的前后分别调用了 beforeSingletonCreationafterSingletonCreation,这两个方法分别用于标记一个 beanName 是否在创建中以及清除标记

protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}
}protected void afterSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");}
}

最后调用了 addSingleton 方法,此方法的内容就是将 createBean 创建的对象放入 singletonObjects 这个缓存中,并且从 singletonFactories 和 earlySingletonObjects 这两个缓存中移除当前创建的这个 beanName

protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}

从这我们就知道一个获取单例的一个流程:

  1. 先从单例池中获取一次,如果没有,则进行创建
  2. 创建前,标记当前 beanName 为创建中
  3. 创建 beanName 对应的对象
  4. 创建后,清除标记
  5. 将创建的对象添加到 signletonObjects 缓存中

在这里插入图片描述

createBean

createBean 这个方法是在 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 中实现的

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {try {// 可以通过 BeanPostProcessors 返回一个代理对象Object bean = resolveBeforeInstantiation(beanName, mbdToUse);if (bean != null) {return bean;}} catch (Throwable ex) {// ... 省略部分代码}try {Object beanInstance = doCreateBean(beanName, mbdToUse, args);// ... 省略部分日志代码return beanInstance;} catch (XxxException... ex) {// ... 省略部分异常处理代码throw ex;}
}

resolveBeforeInstantiation 这部分先不看,这是关于代理对象的逻辑,doCreateBean 是实际创建对象的逻辑。现在我们只需要知道如果 resolveBeforeInstantiation 方法返回了一个对象,那么 createBean 方法根本不会走 doCreateBean ,也就是不回去创建当前这个 beanName 的对象。

doCreateBean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {// 实例化 BeanBeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {// 先从 factoryBeanInstanceCache 这个缓存中获取instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {// 创建对象instanceWrapper = createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// 执行 MergedBeanDefinitionPostProcessorsynchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);// ...mbd.postProcessed = true;}}// Eagerly cache singletons to be able to resolve circular references// even when triggered by lifecycle interfaces like BeanFactoryAware.boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {// ... 省略部分日志记录代码addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.Object exposedObject = bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);} catch (Throwable ex) {// ...}if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {// ... throw}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);} catch (BeanDefinitionValidationException ex) {// ...}return exposedObject;
}

在这里插入图片描述

addSingletonFactory 的目的就是将 ObjectFactory 添加到 singletonFactories 这个缓存中

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {// ...synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

执行 addSingletonFactory 之前有个前提条件,就是 (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
前面已经说过,createBean 之前,会标记当前 beanName 在创建中,所以 isSingletonCurrentlyInCreation(beanName)); 是为 true 的,
在这里插入图片描述

getEarlyBeanReference 这个方法是调用 SmartInstantiationAwareBeanPostProcessor 这个扩展点的,它是在刚创建好像时进行调用,此时还未执行 populateBean 操作。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);}}return exposedObject;
}

createBeanInstance

这里要重点了解一下 createBeanInstance 方法,这个方法是创建一个对象的逻辑

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// Make sure bean class is actually resolved at this point.Class<?> beanClass = resolveBeanClass(mbd, beanName);// ...Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}// Shortcut when re-creating the same bean...boolean resolved = false;boolean autowireNecessary = false;if (args == null) {synchronized (mbd.constructorArgumentLock) {if (mbd.resolvedConstructorOrFactoryMethod != null) {resolved = true;autowireNecessary = mbd.constructorArgumentsResolved;}}}if (resolved) {if (autowireNecessary) {return autowireConstructor(beanName, mbd, null, null);} else {return instantiateBean(beanName, mbd);}}// Candidate constructors for autowiring?Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);}// Preferred constructors for default construction?ctors = mbd.getPreferredConstructors();if (ctors != null) {return autowireConstructor(beanName, mbd, ctors, null);}// No special handling: simply use no-arg constructor.return instantiateBean(beanName, mbd);
}

循环依赖分析

为什么都使用构造函数无法解决?

因为创建 A 时, createBeanInstance 里如果使用构造函数进行对象的创建,那么会进行依赖注入,如果发现构造函数中依赖了 B,那么会通过 getBean 方法去获取 B 这个对象,因此会再次走创建单例的逻辑,于是又会碰到 B 的构造方法依赖 A 的情况,发生了循环依赖,因此会抛出下面的异常信息:Error creating bean with name ‘xxx’: Requested bean is currently in creation: Is there an unresolvable circular reference?

这个异常就是在 beforeSingletonCreation 方法抛出的
在这里插入图片描述

整个过程如下:

  1. 第一次标记了 A,还未清除 A,就创建 B
  2. 于是标记 B ,由于B依赖A,所以会再次标记 A 在创建中
  3. 由于 A 已被标记,所以直接抛异常

其实本质原因就是构造函数必须参数要有值才能创建这个对象

为什么使用@Autowired可以解决?

使用 @Autowired 时属性注入其实不是在 populateBean 方法中进行的,而是在 initializeBean 的 BeanPostProcessor 中进行的。
没有构造函数依赖的情况下,会默认通过无参构造创建,先创建这个对象,然后进行相关依赖属性的填充。所以区别就在于使用@Autowire可以创建对象,虽然此时这个对象还未进行属性填充,但是使用构造函数依赖连对象都不能创建。

因为 B 需要自动注入 A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了

在这里插入图片描述
但是不同的是,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,所以此时getBean(A)可以直接从缓存中获取

在这里插入图片描述

为什么要添加到 earlySingletonObjects 缓存中?

个人认为是为了性能考虑。举个例子:目前是只有 A 和 B 之间存在循环依赖,但是如果存在多层循环依赖的的话,如下所示。那么执行 doGetBean 方法时直接从而及缓存中就可以获取到,直接就返回了,就没有后续的那么多操作了

class A {B b;
}class B {A a;C c;
}class C {A a;B b;
}

allowCircularReferences 的作用

allowCircularReferences,当出现循环依赖时是否自动尝试解决,默认为 true。

在这里插入图片描述

直观点儿的解释就是控制是否将创建的 bean 加入 singletonFactories 缓存

在这里插入图片描述

如果是普通的 spring 项目,可以以下面的方式进行设置

@ComponentScan
public class Main {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();beanFactory.setAllowCircularReferences(false);AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);context.register(Main.class);context.refresh();}
}

如果是springboot环境下则需要配置spring.main.allow-circular-references=true

allowCircularReferences 为 false 的情况下即使都用@Autowire注入还是会报错。例如默认情况下 springboot 项目就会报错,因为 allowCircularReferences 这个配置默认是 false

Description:The dependencies of some of the beans in the application context form a cycle:┌─────┐
|  a
↑     ↓
|  b (field org.example.springboot.config.A org.example.springboot.config.B.a)
└─────┘Action:Relying upon circular references is discouraged and they are prohibited by 
default. Update your application to remove the dependency cycle between 
beans. As a last resort, it may be possible to break the cycle automatically 
by setting spring.main.allow-circular-references to true.

因为 allowCircularReferences 是 false

在这里插入图片描述

allowCircularReferences 是 false 的情况下是根本不会调用 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 这句代码的。也就是

在这里插入图片描述
所以这里是一定要求 singletonObjects 或者 earlySingletonObjects 是有所需的对象的
在这里插入图片描述

只用二级缓存能解决循环依赖吗?

能解决非构造函数循环依赖的根本条件是:A 依赖 B 时,要去创建 B ,此时 A 已经创建。我们需要将 A 或者获取 A 的逻辑保存在一个位置,后面 B 依赖 A 时能够直接获取到。只使用一个缓存也可以实现这个目的。那为什么要设计这 3 个缓存呢?

个人理解:Spring 容器本质就是一个Map,其key为beanName,value为对象。而我们通过BeanFactory#getBean获取的Bean 需要是可以使用的。由于循环依赖的存在,一个对象的依赖是需要在该对象之前先创建好的,所以这个时候不可避免的出现了这种虽然实例化了,但是属性还未填充的Bean,这种半成品Bean也是不可使用的。

如果一个不可使用的 Bean 还未创建好,就通过 getBean 去获取,就可能会出错。并且循环依赖场景如果想要靠 Spring 自身解决循环依赖是一定会存在需要早期暴露的对象的。比如 A 依赖 B,那么创建 B 时不可能拿到已经创建好的 A 对象,这两种对象一定要分开存放。所以这个时候需要两个 Map 来存放,一个 singletonObjects 放已经创建好的,另外一个 earlySingletonObjects 放需要早期暴露的对象。

那么为什么需要 singletonFactories 这个ObjectFactory工厂的缓存呢?

个人认为和 AOP 应该没有关系,AOP 只是利用这几个缓存实现,而非为了 实现 AOP 才使用 singletonFactories 这个缓存。使用 singletonFactories 这个就是为了不在对象未完全创建时进行暴露。

因为创建 Bean 之后紧接着又要调用其他 API ,比如填充属性 populateBean、初始化 initializeBean 等,在这些 API 中可以通过 ApplicationContextAware、BeanFactoryAware 等方式拿到 BeanFactory 并调用 getBean 方法。ObjectFactory 的作用只是将这个对象暴露的时间延迟到属性填充和初始化完毕之后了。直到 ObjectFactory 调用之前, earlySingletonObjects 都是没有值的,而 ObjectFactory 只会调用一次,调用后就直接删除了。

在这里插入图片描述

举个例子,现在 A 与 B、C 分别形成循环依赖。当 A 对象创建好,但是还未填充属性时,这个时候发现 A 依赖 B,于是创建 B。这个时候 A 应该放在哪呢?首先singletonObjects 肯定是不能放的,那么是放 earlySingletonObjects 吗?也不行,因为 getBean 只能从某一个地方取,不管从哪儿取拿到的都是不能用的早期对象。所以这个时候不能直接放对象,而是放一个 ObjectFactory,此时该对象还没暴露出去的,不能通过 API 拿到。真正想要获取的时候再通过 ObjectFactory 进行获取。为什么是从 singletonFactories 中获取放入 earlySingletonObjects 呢?因为现在不只 B 需要 A,C 也需要 A,但是 B 获取 A 时是不知道 C 的存在的,此时 A 仍是早期对象。

二级缓存什么时候放入一级缓存?

放入一级缓存只有一个方式,调用 addSingleton 方法,这个是内部 API
在这里插入图片描述

而调用此方法只有 2 个地方

  1. 一是Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)在依赖注入完成时

在这里插入图片描述

  1. 通过SingletonBeanRegistry#registerSingleton这个public API手动注册单例对象

在这里插入图片描述

依赖注入完成时放入

存在循环依赖的情况下 getBean 是一个递归的过程。递归开始的地方有 2 个,都在 doCreateBean 方法里,一个是根据构造函数创建对象时,

在这里插入图片描述

另外一个是对象创建好之后进行初始化的时候

在这里插入图片描述
在 createBean 方法里,如果允许暴露早期对象,会自动调用 getSingleton ,此时 allowEarlyReference 为 false,因为此时 earlySingletonObjects 这个缓存里已经有了(退出递归的结果就是报错或者完成依赖注入被放入 earlySingletonObjects 缓存)
在这里插入图片描述

这个调用的目的就是将 earlySingletonObjects 缓存再放入 singletonObjects 缓存

Object earlySingletonReference = getSingleton(beanName, false);

如下图所示

在这里插入图片描述

循环依赖的暴露顺序

A 依赖 B ,B 先暴露,最后才是 A,因为是个递归过程

参考资料

  1. https://developer.aliyun.com/article/766880

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

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

相关文章

计算机毕业设计 基于Python音乐平台的设计与实现 Python毕业设计 Python毕业设计选题 Vue 前后端分离【附源码+安装调试】

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

windows C++-移除界面工作线程(一)

本文档演示了如何使用并发运行时将 Microsoft 基础类 (MFC) 应用程序中由用户界面 (UI) 线程执行的工作移动到工作线程。 本文档还演示了如何提高冗长绘制操作的性能。 通过将阻塞性操作&#xff08;例如&#xff0c;绘制&#xff09;卸载到工作线程来从 UI 线程中移除工作&am…

浙大数据结构:08-图8 How Long Does It Take

这道题算是较为简单的拓扑排序题&#xff0c;难度不大 机翻 1、条件准备 n,m为n个结点&#xff0c;m条边。 tim数组存到该结点完成的最早时间&#xff0c;会一点点更新 graph存有向边的时间 indegree数组存每个结点的入度 #include <iostream> #include <vector&g…

采用反相正基准电压电路的反相运算放大器

1 简介 本设计使用采用反相正基准电压的反相放大器将 –5V 至 –1V 的输入信号转换为 3.3V 至 0.05V 的输出电压。该电路可用于将传感器负输出电压转换为可用的 ADC 输入电压范围。 2 设计目标 2.1 输入 2.2 输出 2.3 电源 3 电路设计 根据设计目标&#xff0c;最终设计的电…

Python 与 Pycharm 的简易安装教程,包含Pycharm的修改

一. 官方网站 Python网址&#xff1a;python唯一的官方网址。 Pycharm网址&#xff1a;Pycharm的官方网址。 二. python安装步骤 滑动到红色框内 Downloads 导航栏。 红色框是选择适合自己电脑系统和版本的部分&#xff0c;蓝色框是选择系统的部分&#xff0c;黄色框是版本号。…

【Golang】Go语言中缓冲bufio的原理解读与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Word中如何删除表格下一页的空白页

Reference&#xff1a; [1] Word空白页怎么都删除不掉&#xff1f;用这6个方法随便删&#xff01; - 知乎 (zhihu.com)

实现TCP Connect的断线重连机制:策略与实践

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 断线重连机制&#xff0c;它成为确保应用在网络不稳定情况下仍能持续提供服务的关键技术之一。本文旨在深入探讨TCP&#xff08;传输控制协…

使用Materialize制作unity的贴图,Materialize的简单教程,Materialize学习日志

Materialize 官网下载地址&#xff1a;http://boundingboxsoftware.com/materialize/ github源码地址&#xff1a;https://github.com/BoundingBoxSoftware/Materialize 下载地址&#xff1a;http://boundingboxsoftware.com/materialize/getkey.php 下载后解压运行exe即可 …

YoloV8改进策略:BackBone改进|CAFormer在YoloV8中的创新应用,显著提升目标检测性能

摘要 在目标检测领域,模型性能的提升一直是研究者和开发者们关注的重点。近期,我们尝试将CAFormer模块引入YoloV8模型中,以替换其原有的主干网络,这一创新性的改进带来了显著的性能提升。 CAFormer,作为MetaFormer框架下的一个变体,结合了深度可分离卷积和普通自注意力…

线性回归逻辑回归-笔记

一、线性回归&#xff08;Linear Regression&#xff09; 1. 定义 线性回归是一种用于回归问题的算法&#xff0c;旨在找到输入特征与输出值之间的线性关系。它试图通过拟合一条直线来最小化预测值与真实值之间的误差。 2. 模型表示 线性回归模型假设目标变量&#xff08;输…

深度学习基础—卷积神经网络示例

1.卷积神经网络的结构 在之前的博客《深度学习—简单的卷积神经网络》&#xff0c;仅由卷积层构成网络的全部&#xff0c;这还不是标准的网络结构&#xff0c;本文将继续介绍标准的卷积神经网络结构有哪些&#xff1f; 深度学习基础—简单的卷积神经网络https://blog.csdn.net…

[C++]使用纯opencv部署yolov11-seg实例分割onnx模型

【算法介绍】 在C中使用纯OpenCV部署YOLOv11-seg进行实例分割是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTorch模型。然而&#xff0c;可以通过一些间接的方法来实现这一目标&#x…

opencvjs 在前端的使用

一、opencv 官网 https://opencv.org/ 二、opencv是什么 三、opencvjs前端使用网站 https://docs.opencv.org/4.x/d0/d84/tutorial_js_usage.html https://docs.opencv.org/4.x/d5/d10/tutorial_js_root.html 四、opencvjs demo 举例 <!DOCTYPE html> <html>…

Ubuntu22.04之mpv播放器高频快捷键(二百七十)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

ML 系列:机器学习和深度学习的深层次总结( 15) — KNN — 第 1 部分

一、说明 K-最近邻 (KNN) 算法是一种流行的监督机器学习算法&#xff0c;用于分类和回归任务。它是非参数惰性学习算法的一个典型例子。KNN 被认为是一种惰性学习算法&#xff0c;因为它在训练阶段不对底层数据分布做出任何假设&#xff0c;也不从训练数据中学习特定模型。相反…

Golang | Leetcode Golang题解之第466题统计重复个数

题目&#xff1a; 题解&#xff1a; func getMaxRepetitions(s1 string, n1 int, s2 string, n2 int) int {n : len(s2)cnt : make([]int, n)for i : 0; i < n; i {// 如果重新给一个s1 并且s2是从第i位开始匹配 那么s2可以走多少位&#xff08;走完了就从头开始走p1, p2 :…

开发环境搭建之JAVA多个JDK版本安装

由于项目需要安装多个版本JDK、所以在此记录一下安装过程&#xff1a; 下载JDK1.8 11 17 等多个版本 简单粗暴一看就会、直接从官网下载exe安装包、然后配置环境变量即可 JDK1.8 JDK11 JDK17 安装完成之后如下图&#xff1a; 环境变量配置 右击“我的电脑”属性、找到…

Kron Reduction消去法如何操作,矩阵推导过程

三阶矩阵消去单节点 在电力系统中,母线上的电流注入始终为0,这样的节点可以通过一定的方法消除。以三节点为例,假设注入节点3的电流为0,则: [ I 1 I 2 I 3 ] = [ I 1 I 2 0 ] = [ Y 11 Y 12 Y 13 Y 21 Y 22 Y 23 Y 31 Y 32 Y 33 ] [ V 1 V 2 V 3 ] \left[\begin{array}{…

计算机网络:数据链路层 —— 数据链路层概述

文章目录 数据链路层主要功能 基本概念链路数据链路帧 数据链路层 在计算机网络中&#xff0c;链路层&#xff08;Data Link Layer&#xff09;是网络协议栈中的一层&#xff0c;负责管理和控制链路的建立、维护和释放&#xff0c;以及处理链路层的数据帧传输和错误控制等功能…