关于Spring解决循环依赖的一些思考

news2025/4/16 14:18:20

Spring采用三级缓存解决循环依赖问题,当你尝试写一个简单的ioc容器时发现根本不需要三级缓存。那Spring为什么采用三级缓存呢?

文章目录

  • 什么是循环依赖?
  • 手写一个简单的ioc容器
  • Bean的创建流程
  • Spring如何解决循环依赖?
  • 三级缓存
  • Spring为什么不两阶段初始化?
  • Spring能解决哪些循环依赖问题?
  • 提前AOP时对象还没初始化会出问题吗?
  • 怎么避免重复AOP?
  • 只有一级缓存可以吗?
  • 只有二级缓存可以吗?
  • 三级缓存为什么使用工厂?

什么是循环依赖?

在Spring中因为有iocdi的特性,假如有以下代码:

@Component
class A {
    @Autowired
    B b;
}
@Component
class B {
    @Autowired
    A a;
}

两个组件,你依赖我,我依赖你。

假如先创建A对象,然后进行属性赋值,发现依赖B,转头去发现B没创建,但是它是用户需要创建的,因为标记了Component注解,所以我又去创建B,发现依赖A,此时A还没创建完呢,然后创建A,发现需要B,创建B,发现需要A…

一般来说,如果我们的代码中出现了循环依赖,则说明我们的代码在设计的过程中可能存在问题,我们应该尽量避免循环依赖的发生。不过一旦发生了循环依赖,Spring 默认也帮我们处理好了,当然这并不能说明循环依赖这种代码就没问题。

实际上在目前最新版的 Spring 中,循环依赖是要额外开启的,如果不额外配置,发生了循环依赖就直接报错了。

手写一个简单的ioc容器

下面代码,用来模拟循环依赖。

通过开关控制是否提前放入一级缓存,如果允许则可解决循环依赖,如果不允许就会递归爆栈。

package com.zhouql.spring;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApplicationContext {
    Map<String, Class<?>> beanDefinitionMap = new HashMap<>();
    Map<String, Object> singletonMap = new HashMap<>();
    boolean putInAdvance;

    public ApplicationContext(boolean putInAdvance) {
        // 是否提前放入singletonMap
        this.putInAdvance = putInAdvance;
        // 模拟扫描到的bean组件
        beanDefinitionMap.put("a", A.class);
        beanDefinitionMap.put("b", B.class);
        // 对扫描到的组件初始化
        beanDefinitionMap.forEach(this::createBean);
    }

    // 创建后需要注入
    public void createBean(String name, Class<?> type) {
        if (singletonMap.containsKey(name)) return;
        Object bean = this.getBean(name);
        if (putInAdvance) this.singletonMap.put(name, bean);
        // 属性注入
        List<Field> autowiredFields = getAutowiredFields(type);
        if (!autowiredFields.isEmpty()) {
            autowiredFields.forEach(field -> {
                this.createBean(field.getName(), field.getType());
                field.setAccessible(true);
                try {
                    field.set(bean, singletonMap.get(field.getName()));
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            });
        }
        if (!putInAdvance) this.singletonMap.put(name, bean);
    }

    public List<Field> getAutowiredFields(Class<?> type) {
        return Arrays.stream(type.getDeclaredFields())
        .filter(field -> field.isAnnotationPresent(Autowired.class))
        .toList();
    }

    // 用户通过此API获取bean
    public Object getBean(String name) {
        if (!singletonMap.containsKey(name)) {
            boolean requireCreate = beanDefinitionMap.containsKey(name);
            if (!requireCreate) throw new RuntimeException("bean name is not support.");
            try {
                return beanDefinitionMap.get(name).getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return singletonMap.get(name);
    }

    public static void main(String[] args) {
        // 通过开关控制是否提前放入一级缓存,如果允许则可解决循环依赖,如果不允许就会递归爆栈
        ApplicationContext ioc = new ApplicationContext(true);
        A bean = (A) ioc.getBean("a");
        System.out.println(bean.b);
    }
}

class A {
    @Autowired
    B b;
}

class B {
    @Autowired
    A a;
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Autowired {
}

上面代码是想说,一级缓存就可以避免这个问题了。

因为对象实例化就可以被观察了,依赖注入发生在实例化以后,流程大概如下。

A实例化(放入map)
A需要B
B实例化
B需要A(从map中可以拿到,B完成)
回到A需要B(从map中拿到B,A完成)

Bean的创建流程

创建实例(createInstance)->填充属性(populateBean)->初始化(initializeBean)

& 创建实例

  • 这一阶段的目标是创建 Bean 对象的实例(即调用构造函数)。
  • Spring 使用反射来完成实例化。
  • 已经初始化好的 Bean :是指已经完成创建 Bean 需要经历的三个阶段。
  • 未初始化好的 Bean :指只经过了 Bean 创建的第一个阶段,只将 bean 实例创建出来了。

& 填充属性

  • 在实例化之后,Spring 会将 Bean 所依赖的其他 Bean 注入进来,即进行依赖注入。
  • 处理 @Autowired、@Value、XML 或 Java 配置中定义的依赖。
  • 将其它 Bean 注入到字段、setter 或构造器中。
  • 类型转换(Type Converter)也会在此阶段发生,例如字符串转枚举、字符串转 Date 等。
  • 循环依赖处理:对于单例的情况,此时可以提前暴露引用,解决循环依赖。

& 初始化

  • 属性填充完毕后,会调用初始化逻辑。
  • 调用 Aware 接口方法(如果实现了):BeanNameAwareBeanClassLoaderAwareBeanFactoryAwareApplicationContextAware 等。
  • 执行 BeanPostProcessor 前置处理(PostProcessBeforeInitialization)。
  • 执行初始化方法,如果实现了 InitializingBean 接口,会调用 afterPropertiesSet() 方法。如果在配置中指定了 init-method,也会调用这个方法。如果使用了 @PostConstruct,也会在这里执行。
  • 执行 BeanPostProcessor 后置处理(PostProcessAfterInitialization),比如 AOP 动态代理就是在这里生成的代理对象。

Spring如何解决循环依赖?

Spring容器的循环依赖包括构造器循环依赖和setter循环依赖。

构造器循环依赖

是不好解决的,会抛出 BeanCurrentlylnCreationException 异常。

理解这点很容易,比如A对象构造时依赖B,创建B,此时A还没创建完成,发现B依赖A,拿A,发现A没有,创建A,依次循环没有出口。

setter循环依赖

在创建单例 Bean 时,先实例化原始Bean,然后会把该 Bean 的工厂函数的匿名类对象放入三级缓存中的 singletonFactories 中。

然后在填充属性时,如果出现循环依赖依赖本 Bean,必然执行之前放入的工厂函数的匿名实现,如果该 Bean 无需 AOP 的话,工厂函数返回的就是原 Bean 对象;如果该 Bean 有 AOP 的话,也有可能是被某些 BBP 处理 AOP 之后的代理对象,会放入二级缓存中的 earlySingletonObjects 中。

接着 Bean 开始初始化,如果该 Bean 无需 AOP 的话,结果返回的原来创建的 Bean 对象;如果该 Bean 有 AOP 的话,检查 AOP 织入逻辑是否已经在提前曝光时已经执行了,如果已经执行 AOP 则返回提前曝光的代理 Bean 对象;如果 AOP 织入逻辑未执行过,则进行后续的 BeanPostProcessor 后置处理器进行 AOP 织入,生成 AOP 代理 Bean 对象,并返回。

最后对于提前曝光的单例,就会去检查初始化后的 Bean 对象与二级缓存中提前曝光的 Bean 是不是同一个对象,只有不是的情况下才可能抛出异常。

项目中出现 Bean 的循环依赖,本质原因是代码架构设计不合理,某些 facade 类实现了本应在 serivce 层的业务逻辑,导致其他业务依赖地方反复应用 facade 层对象。SpringBoot 2.6.x 以上的版本官方已经不推荐使用循环依赖,说不定今后某个最新版本的 Spring 会强制不能出现 Bean 循环依赖,因此需要我们开发者在平时编码时要重视代码架构设计。

三级缓存

  • 一级缓存 singletonObjects:主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例 Bean 实例,这样的 Bean 能够直接提供给用户使用,我们称之为终态 Bean 或叫成熟 Bean。
  • 二级缓存 earlySingletonObjects:创建bean过程中用于处理循环依赖的临时缓存,搭配第三层缓存,用于其ObjectFactory返回对象的缓存,保证多个关联对象对当前bean的引用为同一个
  • 三级缓存 singletonFactories:创建bean过程中用于处理循环依赖的临时缓存,由于只有在初始化时才知道有没有循环依赖,所以通过ObjectFactory临时“存储”刚创建完的bean,并延迟触发循环依赖时被引用存放的是 ObjectFactory 的匿名内部类实例,调用 ObjectFactory.getObject() 最终会调用 getEarlyBeanReference 方法,该方法可以获取提前暴露的单例 Bean 引用。

Spring为什么不两阶段初始化?

什么是两阶段初始化?我们可能觉得这样不就避免了循环依赖了吗?

  • 第一遍:遍历所有 BeanDefinition,new 出实例但不做任何依赖注入,也不执行初始化逻辑,把这些对象放入容器(不可用状态)。
  • 第二遍:统一给每个 Bean 执行依赖注入和初始化逻辑,从“仓库”里 get 依赖即可。

Spring 为什么不这么做?这个问题的关键点在于:

一、Spring 的 Bean 初始化逻辑是用户可扩展的

比如你自己实现了:InitializingBean@PostConstruct自定义的 BeanPostProcessor、InstantiationAwareBeanPostProcessor 等。这些钩子可以在 Bean 还没完全创建好时就操作其他 Bean。比如你可能在 afterPropertiesSet() 里就调用了另一个 Mapper 查询数据库,而那个 Mapper 的 Bean 还没被初始化呢。

如果 Spring 用“两阶段”方式,到你真正调用别的 Bean 的时候,它可能还是“半成品”状态。

二、依赖注入不是简单的 set/get,而是需要构造时就准备好

特别是你用了构造器注入(@Autowired 构造方法),那你根本连 Bean 对象都没办法创建成功,因为构造参数里的依赖都还没准备好,连对象都 new 不出来,更别说放进“仓库”了。

三、循环依赖只能解决“字段注入”

Spring 本身也不能解决所有的循环依赖——只能解决“单例 + 字段注入”的情况。构造器注入 + 循环依赖,依旧会抛异常。

所以 Spring 的做法是:

  • 在第一遍创建 Bean 时,如果遇到循环依赖,就在“三级缓存”里提前暴露早期 Bean(early reference),只注入必要的引用,保证可以互相注入完成。
  • 最后再做完整初始化。

Spring能解决哪些循环依赖问题?

Spring 只能解决“单例 + 非构造器注入”的循环依赖。

提前AOP时对象还没初始化会出问题吗?

不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,后续目标初始化相当于代理对象自身也完成了初始化。

怎么避免重复AOP?

Spring 用一个名为 earlyProxyReferences 的 Map,记录 提前被代理过的 Bean,在常规初始化时检查这个 Map,避免重复创建代理对象。

Spring 初始化 Bean 时,会两次调用 BeanPostProcessor:

第一次:是在解决循环依赖时,调用的是 getEarlyBeanReference()

  • 来自 SmartInstantiationAwareBeanPostProcessor(AOP 的 AbstractAutoProxyCreator 实现了这个接口)
  • 内部也会调用 wrapIfNecessary() 尝试创建代理
  • 同时将 bean 放入 earlyProxyReferences 中 ✅

第二次:是 bean 正常初始化完成后,Spring 统一调用所有 BeanPostProcessor 的 postProcessAfterInitialization()

  • AOP 的逻辑也在这里再次执行,但会判断如果已经在 earlyProxyReferences 中,就不重复代理
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);

    // 👇 就是这里!
    this.earlyProxyReferences.put(cacheKey, bean);

    // 再次尝试包装成代理对象(如果需要)
    return wrapIfNecessary(bean, beanName, cacheKey);
}
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);

        // 👇 核心判断(如果之前代理过,这里相等,不再代理,如果没有,这里是null尝试代理)
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 创建代理
            return this.wrapIfNecessary(bean, beanName, cacheKey);
        }
    }

    return bean;
}
/**
 * 判断当前 bean 是否需要被代理(应用 AOP),如果需要,则创建代理对象
 */
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {

    // ✅ 1. 如果当前 beanName 是通过 TargetSource 特殊处理的(比如通过 lookup-method 或 method-injection 方式生成的)
    //    那么不在这里创建代理,直接返回原始 bean
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }

    // ✅ 2. 如果之前已经判断过这个 bean 不需要被代理,就直接返回原始 bean(缓存命中)
    else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }

    // ✅ 3. 判断是否是基础设施类(比如 Advisor、Advice、AopInfrastructureBean 之类的),或者被 shouldSkip 筛选排除
    //    如果不是这些,就尝试生成代理
    else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {

        // ✅ 4. 获取与当前 bean 匹配的拦截器(Advisor/Advice)
        //    如果返回的是 DO_NOT_PROXY,说明不需要代理
        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource) null);

        // ✅ 5. 如果返回的是可用的切面/通知(非 DO_NOT_PROXY),说明需要创建代理
        if (specificInterceptors != DO_NOT_PROXY) {
            // 标记当前 bean 已经被代理过
            this.advisedBeans.put(cacheKey, Boolean.TRUE);

            // 创建 AOP 代理对象
            Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

            // 记录代理类的类型,用于后续类型匹配等逻辑
            this.proxyTypes.put(cacheKey, proxy.getClass());

            // 返回代理对象
            return proxy;
        } else {
            // 如果不需要代理,缓存结果避免下次重复判断
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }

    // ✅ 6. 如果是基础设施类 或 shouldSkip 返回 true,记录缓存并返回原始 bean
    else {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}

只有一级缓存可以吗?

实例化以后直接放入一级缓存,可解决部分循环引用。

但是实例化后没经历属性填充,初始化等生命周期,该bean是一个不完成的bean,可能导致业务代码执行不一致,因此需要保证一级缓存中放的bean是经历了完成的生命周期的可用对象。

只有二级缓存可以吗?

Map<String, Object> cache1; // 用于存储成熟的bean
Map<String, Object> cache2; // 用于存储实例化后的对象,提前暴露

可以的,需要调整获取bean的逻辑,先读一级缓存,没有,再读二级缓存,同时实例化后将bean放入二级缓存,初始化完成后放入一级缓存,移除二级缓存对应key。

那关于aop问题呢?其实需要在实例化时判断是否需要aop,如果需要,创建aop代理对象,如果不需要创建普通对象即可。

但是在这里直接使用二级缓存的话,那么意味着所有的Bean在实例化后,属性填充、初始化前要完成AOP代理。

违背了Spring在结合AOP跟Bean的生命周期的设计!Spring结合AOP跟Bean的生命周期本身就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。

如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

三级缓存为什么使用工厂?

三级缓存存储的是BeanFactory接口,一个函数式接口。

使用工厂的目的在于延迟对实例化阶段生成的对象的代理。

只有真正发生循环依赖的时候,才去提前生成代理对象(提前aop)。

否则该三级缓存直接返回原始对象了。

即使没有循环依赖,也会将其添加到三级缓存中,而且是不得不添加到三级缓存中(解决循环依赖的关键,这也就是为什么说使用三级缓存,不是指三个map,而是第三级最重要),因为到目前为止Spring也不能确定这个Bean有没有跟别的Bean出现循环依赖。

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

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

相关文章

github配置ssh,全程CV

1)随便找一个文件夹右键进入git bash 2)验证是否已有公私钥文件 cd ~/.ssh ls如果不存在则生成然后获取 生成时一直回车 ssh-keygen -t rsa -C "xxxxxx.com" cd ~/.ssh cat id_rsa.pub如果存在则直接获取 cd ~/.ssh cat id_rsa.pub3&#xff09;复制 4&#xf…

Dify简介:从架构到部署与应用解析

Dify 是一个开源的生成式 AI 应用开发平台&#xff0c;融合了后端即服务&#xff08;Backend as a Service, BaaS&#xff09;和 LLMOps 的理念&#xff0c;旨在帮助开发者快速搭建生产级的生成式 AI 应用。本文将详细解析 Dify 的技术架构、部署流程以及实际应用场景&#xff…

碳化硅(SiC)功率模块方案对工商业储能变流器PCS市场格局的重构

碳化硅&#xff08;SiC&#xff09;模块方案&#xff08;如BMF240R12E2G3&#xff09;对工商业储能变流器PCS市场格局产生颠覆性的重构&#xff1a; 2025年&#xff0c;SiC模块方案&#xff08;如BMF240R12E2G3&#xff09;凭借效率、成本和政策支持的三重优势&#xff0c;将重…

Redis入门(Java中操作Redis)

目录 一 基础概念 1. Redis 核心特点 2. Redis 与 MySQL 的对比 3. Redis的开启与使用 二 Redis的常用数据类型 1 基础概念 2 数据结构的特点 三 Redis基础操作命令 1 字符串操作命令 2 哈希操作命令 3 列表操作命令 4 集合操作命令 5 有序集合操作命令 6 通用命令…

算法思想之位运算(一)

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;算法思想之位运算(一) 发布时间&#xff1a;2025.4.12 隶属专栏&#xff1a;算法 目录 算法介绍六大基础位运算符常用模板总结 例题位1的个数题目链接题目描述算法思路代码实现 比特位计数题目链接题目描述算法思路…

【基于Servlet技术处理表单】

文章目录 一、实验背景与目的二、实验设计与实现思路1. 功能架构2. 核心代码实现3. 测试用例 总结 一、实验背景与目的 本次实验旨在深入理解Servlet工作原理&#xff0c;掌握JSP与Servlet的协同开发&#xff0c;实现前端表单与后端数据处理的交互。具体目标包括&#xff1a;设…

[OS] mmap | fd是什么 | inode机制 | vfs封装

Linux 下一切皆文件 * 统统抽象为文件&#xff0c;系统封装一层结构体之后&#xff0c;通过指针来访问 * 文章后面的 几个思考题都挺好的 * 后面涉及到的inode 机制&#xff0c;去年暑假的这篇文章&#xff0c;有详细的记录到过 【Linux】(26) 详解磁盘与文件系统&#xff1a;从…

STL详解 - vector的模拟实现

目录 一、整体设计 1.1 核心结构 1.2 迭代器实现 二、核心接口实现 2.1 构造函数系列 &#x1f334;默认构造 &#x1f334;迭代器范围构造 &#x1f334;元素填充构造 2.2 拷贝控制 &#x1f335;拷贝构造函数 &#x1f335;赋值运算符&#xff08;现代写法&#xf…

C++第三方库【JSON】nlohman/json

文章目录 优势使用API从文件中读取json从json文本创建json对象直接创建并操作json对象字符串 <> json对象文件流 <> json对象从迭代器读取像使用STL一样的访问STL容器转化为 json数组STL容器 转 json对象自定义类型转化为 json对象 限制 优势 直观的语法&#xff…

超细的ollama下载以及本地部署deepseek项目

Ollama 是一个开源的本地化大语言模型&#xff08;LLM&#xff09;运行和部署工具&#xff0c;专注于让开发者能够快速、高效地在本地运行和管理各种开源大语言模型&#xff08;如 LLaMA、Mistral、GPT 系列等&#xff09;。它提供了一个统一的接口&#xff0c;简化了模型下载、…

【Sequelize】关联模型和孤儿记录

一、关联模型的核心机制 1. 关联类型与组合规则 • 基础四类型&#xff1a; • hasOne&#xff1a;外键存储于目标模型&#xff08;如用户档案表存储用户ID&#xff09; • belongsTo&#xff1a;外键存储于源模型&#xff08;如订单表存储用户ID&#xff09; • hasMany&…

Sentinel实战教程:流量控制与Spring Boot集成

Sentinel实战教程:流量控制与Spring Boot集成 1. Sentinel简介与核心概念 1.1 什么是Sentinel? Sentinel是阿里巴巴开源的流量控制组件,主要用于微服务架构中的流量防护。它通过限流、熔断、热点防护等机制,帮助系统在高并发场景下保持稳定运行。 1.2 核心功能与术语 流…

循环神经网络 - 扩展到图结构之递归神经网络

本文我们来学习递归神经网络(Recursive Neural Network&#xff0c;RecNN)&#xff0c;其是循环神经网络在有向无循环图上的扩展 。 递归神经网络是一类专门设计来处理具有层次结构或树形结构的数据的神经网络模型。它与更常见的循环神经网络&#xff08;Recurrent Neural Net…

Maven超级详细安装部署

1.到底什么是Maven&#xff1f;搞清楚这个 Maven 是一个项目管理工具&#xff0c;主要用于 Java 项目的构建、依赖管理和文档生成。 它基于项目对象模型&#xff08;POM&#xff09;&#xff0c;通过 pom.xml 文件定义项目的配置。 &#xff08;简单说破&#xff1a;就是工程…

电机控制-隆博戈观测器(Luenberger state observer)

本文围绕基于无传感器控制策略的状态观测器展开&#xff0c;介绍其在电机领域的应用、原理、性能表现及无传感器驱动的优劣&#xff1a; 应用场景&#xff1a;适用于燃油泵、风扇等大量固定转速和低成本应用场景。工作原理&#xff1a;状态观测器利用完整的电机微分模型&#…

RK3506+net9+VS2022跨平台调试C#程序

下载GetVsDbg.sh &#xff0c;这脚本会下载一个压缩包&#xff0c;然后解压缩&#xff0c;设置x权限等等。但是目标板子连不上&#xff0c;就想办法获取到下载路径&#xff0c;修改这个脚本&#xff0c;显示这个下载链接后&#xff0c;复制一下&#xff0c;用电脑下下来 修改好…

【16】数据结构之基于树的排序算法篇章

目录标题 选择排序简单选择排序树形选择排序 堆排序堆的定义Heap小跟堆大根堆堆的存储堆的代码设计堆排序的代码设计 排序算法综合比较 选择排序 基本思想&#xff1a;从待排序的序列中选出最大值或最小值&#xff0c;交换该元素与待排序序列的头部元素&#xff0c;对剩下的元…

华熙生物亮相消博会,这次又带来了什么样的变化?

首先&#xff0c;从展示层面来看&#xff0c;华熙生物在消博会上构建科技桥梁&#xff0c;展台主视觉展示糖生物学发展历程与自身发展交织历程&#xff0c;这象征着中国生物科技企业从产业突围到定义全球标准的蜕变。这一展示不仅提升了华熙生物的品牌形象&#xff0c;更向外界…

大象机器人推出myCobot 280 RDK X5,携手地瓜机器人共建智能教育机

摘要 大象机器人全新推出轻量级高性能教育机械臂 myCobot 280 RDK X5&#xff0c;该产品集成地瓜机器人 RDK X5 开发者套件&#xff0c;深度整合双方在硬件研发与智能计算领域的技术优势&#xff0c;实现芯片架构、软件算法、硬件结构的全栈自主研发。作为国内教育机器人生态合…

【初阶数据结构】——算法复杂度

一、前言 1、数据结构是什么&#xff1f; 数据结构(Data Structure)是计算机存储、组织数据的⽅式&#xff0c;指相互之间存在⼀种或多种特定关系的数 据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤&#xff0c;所以我们要学各式各样的数据结构&#xff0c; 如&…