当前位置: 首页 > news >正文

【Spring从成神到升仙系列 五】从根上剖析 Spring 循环依赖

  • 👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主
  • 📕系列专栏:Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙、Spring从成神到升仙系列
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人
  • 📝联系方式:hls1793929520,加我进群,大家一起学习,一起进步👀

在这里插入图片描述

文章目录

  • Spring 循环依赖源码解析
    • 一、引言
    • 二、循环依赖场景
      • 1、有参构造引起的循环依赖
      • 2、属性注入引起的循环依赖
    • 三、循环依赖的原因
      • 1、有参构造失败的原因
      • 2、属性注入成功的原因
        • 2.1 AOP导致的循环依赖
    • 四、循环依赖 Spring 源码剖析
      • 步骤一:查询 MyDemo1 是否存在
      • 步骤二:将 MyDemo1 半实例化放至缓存中
      • 步骤三、四:查询 MyDemo2 的缓存是否存在
      • 步骤五:将 MyDemo2 半实例化放至缓存中
      • 步骤六:从缓存中获取 MyDemo1
      • 步骤七:将 MyDemo2 生成的实例化放至 singletonObject 中
      • 步骤八:将 MyDemo1 生成的实例化放至 singletonObject 中
    • 五、总结

Spring 循环依赖源码解析

一、引言

对于Java开发者而言,关于 Spring ,我们一般当做黑盒来进行使用,不需要去打开这个黑盒。

但随着目前程序员行业的发展,我们有必要打开这个黑盒,去探索其中的奥妙。

本期 Spring 源码解析系列文章,将带你领略 Spring 源码的奥秘

本期源码文章吸收了之前 Kafka 源码文章的错误,将不再一行一行的带大家分析源码,我们将一些不重要的部分当做黑盒处理,以便我们更快、更有效的阅读源码。

废话不多说,发车!
在这里插入图片描述

本文流程图可关注公众号:爱敲代码的小黄,回复:循环依赖 获取
贴心的小黄为大家准备的文件格式为 POS文件,方便大家直接导入 ProcessOn 修改使用

二、循环依赖场景

我们上几篇文章讲解了 IOC、AOP的源码实现,如果没有看过的同学可以去看一下:

  • Spring IOC 源码剖析
  • Spring AOP 源码剖析

如果上面的文章你已经熟悉了,那么对于循环依赖的理解就会变得很简单,甚至你自己都能够想明白整个运行原理

我们首先介绍一下循环依赖的场景

我们在委托 Spring 进行对象的创建时,会遇到下面的情况:

1、有参构造引起的循环依赖

MyDemo1:

public class MyDemo1 {public MyDemo2 myDemo2;public MyDemo1(MyDemo2 myDemo2) {this.myDemo2 = myDemo2;}
}

MyDemo2:

public class MyDemo2 {public MyDemo1 myDemo1;public MyDemo2(MyDemo1 myDemo1) {this.myDemo1 = myDemo1;}
}

xml文件配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="myDemo1" class="cn.hls.demo1.MyDemo1"><constructor-arg value="myDemo2"/></bean><bean id="myDemo2" class="cn.hls.demo1.MyDemo2"><constructor-arg value="myDemo1"/></bean></beans>

测试用例:

public class TestMain {public static void main(String[] args) {ApplicationContext context = new GenericXmlApplicationContext("application.xml");MyDemo1 myDemo1 = (MyDemo1) context.getBean("myDemo1");myDemo1.show();}
}

运行,不出所料,我们会报错:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'myDemo1': Requested bean is currently in creation: Is there an unresolvable circular reference?

2、属性注入引起的循环依赖

MyDemo1:

public class MyDemo1 {public MyDemo2 myDemo2;public void show() {System.out.println("我是" + MyDemo1.class.getName());}public void setMyDemo2(MyDemo2 myDemo2) {this.myDemo2 = myDemo2;}public MyDemo2 getMyDemo2() {return myDemo2;}
}

MyDemo2:

public class MyDemo2 {public MyDemo1 myDemo1;public void show() {System.out.println("我是" + MyDemo2.class.getName());}public MyDemo1 getMyDemo1() {return myDemo1;}public void setMyDemo1(MyDemo1 myDemo1) {this.myDemo1 = myDemo1;}
}

xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="myDemo1" class="cn.hls.demo1.MyDemo1"><property name="myDemo2" ref="myDemo2"/></bean><bean id="myDemo2" class="cn.hls.demo1.MyDemo2"><property name="myDemo1" ref="myDemo1"/></bean></beans>

测试用例:

public class TestMain {public static void main(String[] args) {ApplicationContext context = new GenericXmlApplicationContext("application.xml");MyDemo1 myDemo1 = (MyDemo1) context.getBean("myDemo1");MyDemo2 myDemo2 = (MyDemo2) context.getBean("myDemo2");myDemo1.show();myDemo2.show();}
}

运行,我们竟然发现,这种是可以正常执行的

我是cn.hls.demo1.MyDemo1
我是cn.hls.demo1.MyDemo2

到这里,有没有一点点惊讶、一点点懵逼、一点点卧槽

如果有的话,那这篇文章将带你解析为什么两种方式不同的注入方式

一种可能正常运行,一种不能正常运行

三、循环依赖的原因

这里我们搬出 IOC 源码中的流程图:
在这里插入图片描述

我们分别聊一下有参构造场景下和有参注入场景下的不同

1、有参构造失败的原因

我们通过上图看到,如果一个类需要通过有参构造创建实例化,那么需要得到其构造方法的入参:

在这里插入图片描述

整体情况如上所示,我们总是重复性的循环,MyDemo1 的实例化创建依赖 MyDemo2,而 MyDemo2 的实例化创建又需要依赖 MyDemo1,这样就导致了死循环并无法解决。

所以,当我们的 Spring 察觉到有参构造导致的循环依赖时,会进行报错,这种的循环依赖也是没有办法解决的。

2、属性注入成功的原因

在这里插入图片描述

大家看这张图,可能会疑惑,这不也造成了循环依赖嘛,怎么这种方式没报错

我们想想这种属性注入导致的循环依赖能不能靠其他的方式去解决,加缓存可不可以

在这里插入图片描述
我们来看这种解决方式:

  • 我们 MyDemo1 调用无参构造生成实例(不是完全的实例)时,将其放至我们的缓存池中
  • MyDemo1 调用属性注入时,会去缓存池中寻找 MyDemo2 的实例,若找不到的话,则调用 CreateBean 方法创建 MyDemo2 的实例
  • MyDemo2 调用无参构造生成实例(不是完全的实例)时,将其放至我们的缓存池中
  • MyDemo2 调用属性注入时,会去缓存池中寻找 MyDemo1 的实例,找到之后之前,执行后续的方法生成对应的实例化
  • 这个时候我们的 MyDemo1 已经得到了 MyDemo2 的实例化数据了,直接执行初始方法创建实例即可

通过上述这种方式,我们已经将 属性注入 的循环依赖问题用加一层缓存的方式解决掉了

而这个缓存也被我们称作 提前暴露(earlySingletonObjects) 的缓存

2.1 AOP导致的循环依赖

我们上面可以看到,我们用一层 提前暴露(earlySingletonObjects) 的缓存解决了属性注入导致的循环依赖问题

这时候你可能会说:小黄,小黄,不是三级缓存嘛,你这咋就讲了一个 提前暴露(earlySingletonObjects) 缓存

不要着急,我们继续往下讲

假如我们现在 MyDemo1AOP 动态代理,如果我们再按照上面的方式去进行缓存,会造成什么结果?

我们 MyDemo2 中的成员变量 MyDemo1 是未经动态代理的,这样使用 MyDemo1 时,实际上也是非动态代理的对象,这样是不被允许的!

为什么会有上面的问题呢?
在这里插入图片描述
根本原因在于:我们的属性注入的阶段在我们的执行初始方法(AOP)之前,缓存池中的半实例化对象不是我们代理对象

那怎么解决这个问题呢——没错,还是加缓存

我们再加一层缓存,该缓存的作用:如果我们半实例化的对象是代理对象,那么我们得到其代理对象

在这里插入图片描述

如上所示,整体的业务如上,我们详细的聊一聊 Spring 源码对于循环依赖的处理

四、循环依赖 Spring 源码剖析

我们以属性注入的例子来进行源码解析:

在我们讲解之前,我介绍一下三级缓存各自的功能:

  • 一级缓存(singletonObject):存储的是所有创建好了的单例Bean
  • 二级缓存(earlySingletonObjects):完成实例化,但是还未进行属性注入及初始化的对象
  • 三级缓存(singletonFactories):提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

这三个缓存非常重要,必须要记住。

当我们使用 ApplicationContext context = new GenericXmlApplicationContext("application.xml"); 启动时,会进行我们 Bean 的创建

这里只说最关键的步骤,整体的步骤可见:Spring IOC 源码剖析

整体流程如下:
在这里插入图片描述

步骤一:查询 MyDemo1 是否存在

此时的缓存:
在这里插入图片描述

我们直接跳到这里:AbstractBeanFactory246

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){// Step1:查询MyDemo1缓存是否存在Object sharedInstance = getSingleton(beanName);// 如果是单例的beanif (mbd.isSingleton()) {// 直接创建bean即可,注意 getSingleton 方法sharedInstance = getSingleton(beanName, () -> {return createBean(beanName, mbd, args);});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}
}// Step1:从三级缓存中查询 MyDemo1 是否被缓存
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 一级缓存查询Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 二级缓存查询singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {// 三级缓存查询ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}// 这里记住一个操作:在我们创建bean结束之后,会调用 addSingleton 该方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {finally {if (recordSuppressedExceptions) {this.suppressedExceptions = null;}afterSingletonCreation(beanName);}if (newSingleton) {addSingleton(beanName, singletonObject);}return singletonObject;
}

步骤二:将 MyDemo1 半实例化放至缓存中

我们直接跳到 AbstractAutowireCapableBeanFactory580

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){// 是否需要提前暴露boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));// 如果需要提前暴露,则放入到我们的三级缓存里面if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}
}// 将未完全实例化的 MyDemo1 放至缓存中
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);}}
}// 这个是重点:生成动态代理对象的地方,我们后面会讲
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;
}

此时的缓存:
在这里插入图片描述

步骤三、四:查询 MyDemo2 的缓存是否存在

我们直接跳到这里:AbstractBeanFactory246

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){// Step4:查询MyDemo2缓存是否存在Object sharedInstance = getSingleton(beanName);// 如果是单例的beanif (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {return createBean(beanName, mbd, args);});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}
}

步骤五:将 MyDemo2 半实例化放至缓存中

我们直接跳到 AbstractAutowireCapableBeanFactory580

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){// 是否需要提前暴露boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));// 如果需要提前暴露,则放入到我们的三级缓存里面if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}
}

此时的缓存:
在这里插入图片描述

步骤六:从缓存中获取 MyDemo1

我们直接跳到这里:AbstractBeanFactory246

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){// Step6:从缓存中获取 MyDemo1 Object sharedInstance = getSingleton(beanName);// 如果是单例的beanif (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {return createBean(beanName, mbd, args);});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}
}
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) {// 这里获取的是 MyDemo1 的缓存,我们之前已经放入过Object sharedInstance = getSingleton(beanName);
}protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {// 【重点】从三级缓存中取到ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 调用 getEarlyBeanReference 的方法生成对象singletonObject = singletonFactory.getObject();// 将生成的半实例对象放至二级缓存中this.earlySingletonObjects.put(beanName, singletonObject);// 删除掉三级缓存的信息this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}

我们来看一下 getEarlyBeanReference 做了什么、

  • 如果是普通的类,没有被动态代理的,直接返回 bean 即可
  • 如果是动态代理的类,需要进行动态代理类的生成并返回
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;// 【重点】exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;
}public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);// 这里会生成动态代理类【AOP文章讲过】return wrapIfNecessary(bean, beanName, cacheKey);
}

到这里,我们的缓存的状态如下:
在这里插入图片描述

步骤七:将 MyDemo2 生成的实例化放至 singletonObject 中

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {if (newSingleton) {addSingleton(beanName, singletonObject);}return singletonObject;
}// 当bean初始化完成之后
// 删除二级缓存、三级缓存,将其放入一级缓存中
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);}
}

此时各缓存情况:
在这里插入图片描述

步骤八:将 MyDemo1 生成的实例化放至 singletonObject 中

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {if (newSingleton) {addSingleton(beanName, singletonObject);}return singletonObject;
}// 当bean初始化完成之后
// 删除二级缓存、三级缓存,将其放入一级缓存中
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);}
}

此时各缓存情况:
在这里插入图片描述
到这里,我们的循环依赖的整体流程就被解决了

五、总结

又是一篇大工程的文章结束了

记得校招时候,当时对 Spring 懵懂无知,转眼间也被迫看了源码

更可怕的是,现在面试竟然百分之80都要熟悉IOCAOP的源码,甚至手写 AOP 的实现

但通过这篇文章,我相信,99% 的人应该都可以理解了 Spring 循环依赖 的实现

那么如何证明你真的理解了 Spring 循环依赖 呢,我这里出个经典的题目,大家可以想一下:为什么Spring要用三级缓存,二级不可以嘛?

如果你能看到这,那博主必须要给你一个大大的鼓励,谢谢你的支持!

喜欢的可以点个关注,Spring 系列到此正式结束了~

  • 【Spring从成神到升仙系列 一】2023年再不会动态代理,就要被淘汰了
  • 【Spring从成神到升仙系列 二】2023年再不会 IOC 源码,就要被淘汰了
  • 【Spring从成神到升仙系列 三】2023年再不会 AOP 源码,就要被淘汰了
  • 【Spring从成神到升仙系列 四】从源码分析 Spring 事务的来龙去脉

后续博主应该会更新 dubbo 或者 并发编程 的系列文章,

我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,Java领域新星创作者,喜欢后端架构和中间件源码。

我们下期再见。

相关文章:

【Spring从成神到升仙系列 五】从根上剖析 Spring 循环依赖

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…...

设计模式之代理模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、代理模式是什么&#xff1f; 代理模式是一种结构型的软件设计模式&#xff0c;在不改变原代码前提下&#xff0c;提供一个代理…...

c++11 标准模板(STL)(std::unordered_multimap)(三)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…...

Linux进程控制-2

紧接着上篇博客出发&#xff0c;我们接着来讲述Linux中进程控制的内容。 目录 1.等待 1.1具体操作 1.等待 进程等待主要的作用在于&#xff1a;父进程创建子进程之后&#xff0c;等待子进程退出&#xff0c;获取子进程的退出码&#xff0c;释放子进程的资源&#xff0c;避…...

快速排序算法

一&#xff1a;快速排序思想 假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数&#xff08;不要被这个名词吓到了&#xff0c;就是一个用来参照的数&#xff0c;待会你就知道它用来做啥的了&#xff09;。为了方便&#xff…...

中华好诗词大学季第二季(四)

第七期 1,二十四友一朝尽&#xff0c;爱妾坠楼何足言出自许浑的《金谷园》&#xff0c;“爱妾”指的是谁 2,李白在《九月十日即事》借菊花表达自己的惋惜之情&#xff0c;请问九月十日是什么节日 A 后登高 B 菊花节 C 小重阳 3,贾宝玉在大观园里面题了“曲径通幽”&#xf…...

分布式系统容灾部署方案

本文主要以OceanBase部署来说明分布式系统容灾部署方案 分布式系统提供持续可用的服务尤为重要。 好的分布式系统根据需求提供不同等级的的高可用与容灾级别。 而在分布式系统中&#xff0c;数据库系统又是最核心最关键的系统。 我们以数据库分布式系统为主&#xff0c;考虑…...

Python 爬虫性能相关总结

这里我们通过请求网页例子来一步步理解爬虫性能 当我们有一个列表存放了一些url需要我们获取相关数据&#xff0c;我们首先想到的是循环 简单的循环串行 这一种方法相对来说是最慢的&#xff0c;因为一个一个循环&#xff0c;耗时是最长的&#xff0c;是所有的时间总和 代码…...

Baumer工业相机堡盟工业相机如何设置网口的IP地址(工业相机连接的网口设置IP地址步骤)

Baumer工业相机堡盟工业相机如何设置网口的IP地址&#xff08;工业相机连接的网口设置IP地址步骤&#xff09;Baumer工业相机Baumer工业相机设置网络端口IP地址匹配设置网络端口IP地址和工业相机IP地址匹配第一次打开CameraExplorer软件确认问题为IP地址不匹配问题打开网络连接…...

Android MediaCodec设置H264 Profile到High

H264 High Profile压缩率高&#xff0c;能降低码率&#xff0c;这里记录下MediaCodec Profile设置到High遇到的一些问题。 Android 4.1 就引入了MediaCodecInfo.CodecProfileLevel类&#xff0c;下面截取H264(AVC)的Profile和Level定义: /** Copyright (C) 2012 The Android O…...

QT之QSysInfo(查看电脑信息)

文章目录前言一、API使用总结前言 QSysInfo是Qt中用于获取有关运行应用程序的系统信息的类。 我们可以获取以下信息&#xff1a; 返回系统产品类型&#xff0c;如ios&#xff0c;windows&#xff0c;Linux等 返回当前系统的产品版本。 返回当前系统的内核类型。 返回当前系统的…...

中国塑料编织袋产业竞争状况及投资前景预测报告2023-2029年

中国塑料编织袋产业竞争状况及投资前景预测报告2023-2029年 KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK 《报告编号》: BG451639 《出版时间》: 2023年4月 《出版机构》: 中智正业研究院 免费售后 服务一年&#xff0c;具体内容及订购流程欢迎咨询客服人员 内容简介&…...

从头用脚分析FFmpeg源码 - av_read_frame

av_read_frame作用 /*** Return the next frame of a stream.* This function returns what is stored in the file, and does not validate* that what is there are valid frames for the decoder. It will split what is* stored in the file into frames and return one f…...

第17章_触发器

第17章_触发器 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目前在某公…...

3956. 截断数组

3956. 截断数组 - AcWing题库 3956. 截断数组 【题目描述】 给定一个长度为 nn 的数组 a1,a2,…,ana1,a2,…,an。 现在&#xff0c;要将该数组从中间截断&#xff0c;得到三个非空子数组。 要求&#xff0c;三个子数组内各元素之和都相等。 请问&#xff0c;共有多少种不同…...

React Labs: 我们最近在做什么——2023 年 3 月

原文&#xff1a;https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023 React Server Components React Server Components(下文简称 RSC) 是由 React 团队设计的新应用程序架构。 我们首先在一个介绍性演讲和一个RFC中分享了我们对 RSC 的…...

文件系统设计详解

抽象的文件系统以目录的形式来组织文件&#xff0c;我们可以利用该文件系统来读取某个文件的内容&#xff0c;也可以对目录或者文件实施监控并及时获取变化的通知。 IChangeToken IChangeToken对象就是一个与某组监控数据相关联的“令牌”&#xff08;Token&#xff09;&#x…...

好看~立马启动python实现美女通通下

人生苦短&#xff0c;我用python一、环境版本使用二、代码实现思路三、代码展示&#xff1a;导入模块伪装(请求头)四、部分好看截图&#xff0c;更多的就自己去采集噜~吃饭放松的时候哇一不小心看见了很多好看的东西 哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 独乐乐不如众乐乐&#xf…...

Git 安装设置

1、安装 安装以下三个软件&#xff1a; Git-2.13.3-64-bit.exe TortoiseGit-2.4.0.2-64bit.msi TortoiseGit-LanguagePack-2.4.0.0-64bit-zh_CN.msi 安装过程中不用填写、不用选择&#xff0c;全部点"下一步"&#xff0c;完成后需要重启机器。 2、基本设…...

Python-闭包

介绍 Python的闭包是一种高级的编程技巧&#xff0c;它可以在函数内部定义另一个函数&#xff0c;并返回该函数的引用。这个内部函数可以访问外部函数的变量和参数&#xff0c;即使外部函数已经执行完毕 好处 1&#xff09;闭包可以避免全局变量的污染&#xff0c;使得代码更…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战&#xff0c;克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

Mysql8 忘记密码重置,以及问题解决

1.使用免密登录 找到配置MySQL文件&#xff0c;我的文件路径是/etc/mysql/my.cnf&#xff0c;有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...