Spring高手之路12——BeanDefinitionRegistry与BeanDefinition合并解析

news2024/11/24 2:29:57

文章目录

  • 1. 什么是BeanDefinitionRegistry?
  • 2. BeanDefinitionRegistry 的使用
    • 2.1 BeanDefinitionRegistry 简单例子
    • 2.2 有关ImportBeanDefinitionRegistrar的实现类的例子
  • 3. BeanDefinition的合并
    • 3.1 调试验证BeanDefinition的合并
    • 3.2 BeanDefinition合并的目的
  • 4. BeanDefinition的合并的源码分析
    • 4.1 BeanDefinition合并过程时序图
    • 4.2 BeanDefinition合并过程源码解读

1. 什么是BeanDefinitionRegistry?

  BeanDefinitionRegistry 是一个非常重要的接口,存在于 Springorg.springframework.beans.factory.support 包中,它是 Spring 中注册和管理 BeanDefinition 的核心组件。

  让我们回顾一下上一篇说的 BeanDefinition。在 Spring 中,一个 Bean 就是一个被 Spring 管理的对象,而一个 BeanDefinition 则是一个 Bean 的配置描述,它描述了一个 Bean 的数据。它包含了 Bean 的类名、是否为抽象类、构造函数和属性值等信息。这些元数据将指导 Spring 如何创建和初始化 Bean

  再来看一下 BeanDefinitionRegistry 的作用,BeanDefinitionRegistry 的主要职责就是注册和管理这些 BeanDefinition。我们可以把它看作是一个存放 BeanDefinition 的注册表,向其中注册新的 BeanDefinition,或者检索和删除现有的 BeanDefinition。它提供了一些方法,如 registerBeanDefinition(String, BeanDefinition)removeBeanDefinition(String),和 getBeanDefinition(String),用于执行这些操作。

  在 Spring 的内部,BeanDefinitionRegistry 通常由 BeanFactory 实现,特别是 DefaultListableBeanFactoryGenericApplicationContext,它们都实现了这个接口。

2. BeanDefinitionRegistry 的使用

2.1 BeanDefinitionRegistry 简单例子

  在这个例子中,我们将创建一个简单的 Bean,注册到 DefaultListableBeanFactory(它实现了 BeanDefinitionRegistry 接口),然后从工厂中获取并使用这个 Bean

全部代码如下:

首先,我们需要一个 Bean 类,这是一个简单的 POJO 类:

package com.example.demo.bean;

public class MyBean {
    private String message;

    public void doSomething() {
        System.out.println("Hello, world!");
    }

    public void setMessage(String message){
        this.message  = message;
    }

    public void getMessage(){
        System.out.println("Your Message : " + message);
    }
}

然后,我们可以使用 DefaultListableBeanFactoryRootBeanDefinition 来创建并注册这个 Bean

package com.example.demo;

import com.example.demo.bean.MyBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;

public class DemoApplication {

    public static void main(String[] args) {
        // 创建 BeanDefinitionRegistry
        DefaultListableBeanFactory registry = new DefaultListableBeanFactory();

        // 创建一个 BeanDefinition
        BeanDefinition beanDefinition = new RootBeanDefinition(MyBean.class);

        // 注册 BeanDefinition
        registry.registerBeanDefinition("myBean", beanDefinition);

        // 从 BeanFactory 中获取 Bean
        MyBean myBean = registry.getBean("myBean", MyBean.class);

        // 使用 Bean
        myBean.doSomething();  // 输出:Hello, world!
    }
}

  这个程序会创建一个名为 "myBean"Bean,这个 BeanMyBean 类的一个实例。然后,我们从 BeanFactory 中获取这个 Bean,并调用其 doSomething 方法,打印 "Hello, world!"

在这里插入图片描述

2.2 有关ImportBeanDefinitionRegistrar的实现类的例子

这个在第8篇(Spring高手之路8——Spring Bean模块装配的艺术:@Import详解)提到过,是3.5节,大家可以回头看,这里不重复粘贴代码。

3. BeanDefinition的合并

我们前一篇讲解BeanDefinition的时候没有讲解BeanDefinition的合并,这里补充说明。

  1. BeanDefinition

  在 Spring 中,BeanDefinition 是一个接口,它定义了 Bean 的配置信息,例如 Bean 的类名,是否是单例,依赖关系等。在 Spring 中,每一个 Bean 都对应一个 BeanDefinition 对象。

  1. 合并的意义

  在 Spring 中,有一种特殊的 BeanDefinition,叫做子 BeanDefinition,也就是我们在 XML 配置文件中通过 parent 属性指定的那种。这种子 BeanDefinition 可以继承父 BeanDefinition 的配置信息。

  合并的过程,就是把子 BeanDefinition 的配置信息和父 BeanDefinition 的配置信息合并起来,形成一个完整的配置信息。合并后的 BeanDefinition 对象包含了 Bean 创建所需要的所有信息,Spring 将使用这个完整的 BeanDefinition 来创建 Bean 实例。

  1. 合并的过程

  Spring 在需要创建 Bean 实例的时候,会先获取对应的 BeanDefinition 对象。如果这个 BeanDefinition 是一个子 BeanDefinitionSpring 就会找到它的父 BeanDefinition,然后把两者的配置信息合并起来,形成一个完整的 BeanDefinition

  这个过程是在 DefaultListableBeanFactorygetMergedBeanDefinition 方法中进行的,如果大家有兴趣,可以在这个方法中设置断点,看一看具体的合并过程。

  1. 合并过程的流程图

在这里插入图片描述

我们可以通过父子Bean的方式使用这个特性,下面是一个XML配置的例子:

<bean id="parentBean" class="com.example.ParentClass" abstract="true">
    <property name="commonProperty" value="commonValue" />
</bean>

<bean id="childBean" parent="parentBean">
    <property name="specificProperty" value="specificValue" />
</bean>

  在这个例子中,我们定义了两个bean,一个是 parentBean,另一个是 childBeanparentBeanabstract 的,表示它不会被实例化,只作为模板使用。childBeanparent 属性指向 parentBean,表示它继承了 parentBean 的配置。

  parentBean 有一个属性 commonProperty,值为 commonValuechildBean 有一个属性 specificProperty,值为 specificValue。在Spring解析这个配置文件,生成BeanDefinition的时候,childBeanBeanDefinition会包含两个属性:commonPropertyspecificProperty,这就是 BeanDefinition 的合并过程。

  Java配置中,我们无法直接模拟XML配置的BeanDefinition合并过程,因为这是Spring XML配置的一项特性,配置类通常会采用Java代码的继承或组合来重用bean定义,不会涉及到配置元数据层面的BeanDefinition合并。XML配置中的BeanDefinition合并特性允许我们定义一个父Bean,然后定义一些子Bean,子Bean可以继承父Bean的一些属性。

  这个特性在Java配置中并没有直接的替代品,因为Java配置通常更加依赖实例化过程中的逻辑,而不是元数据(即BeanDefinition)。在Java配置中,我们可以使用继承和组合等普通的Java特性来实现类似的结果,但这不是真正的BeanDefinition合并。因此,当我们从XML配置转换为Java配置时,通常需要手动将共享的属性复制到每个Bean的定义中。

3.1 调试验证BeanDefinition的合并

全部代码如下:

首先,创建 XML 配置文件 applicationContext.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="parentBean" class="com.example.demo.bean.ParentClass" abstract="true">
        <property name="commonProperty" value="commonValue" />
    </bean>

    <bean id="childBean" parent="parentBean" class="com.example.demo.bean.ChildClass">
        <property name="specificProperty" value="specificValue" />
    </bean>
</beans>

然后,创建 ParentClassChildClass,如下:

package com.example.demo.bean;

public abstract class ParentClass {

    private String commonProperty;

    public String getCommonProperty() {
        return commonProperty;
    }

    public void setCommonProperty(String commonProperty) {
        this.commonProperty = commonProperty;
    }
}
package com.example.demo.bean;

public class ChildClass extends ParentClass {

    private String specificProperty;

    public String getSpecificProperty() {
        return specificProperty;
    }

    public void setSpecificProperty(String specificProperty) {
        this.specificProperty = specificProperty;
    }
}

主程序如下:

package com.example.demo;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DemoApplication {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();

        // 获取childBean的原始BeanDefinition
        BeanDefinition childBeanDefinition = factory.getBeanDefinition("childBean");
        System.out.println("Child bean definition before merge: " + childBeanDefinition);

        // 获取合并后的BeanDefinition
        BeanDefinition mergedBeanDefinition = factory.getMergedBeanDefinition("childBean");
        System.out.println("Merged bean definition: " + mergedBeanDefinition);
    }
}

  在这个示例中,我们首先加载了 applicationContext.xml 配置文件,然后获取了 childBean 的原始 BeanDefinition。然后,我们调用 getMergedBeanDefinition 方法获取了合并后的 BeanDefinition,可以在这个过程中设置断点来查看合并过程的详细情况。

运行结果:

在这里插入图片描述

  大家可以从运行结果看到打印了Generic beanRoot bean,代表了GenericBeanDefinitionRootBeanDefinition

为什么有两个不同的 BeanDefinition 类型( GenericBeanDefinition 和 RootBeanDefinition)

GenericBeanDefinition:

  • 这是一个通用的BeanDefinition实现类,可以配置任何类型的bean
  • 它通常用于读取XML、注解或其他形式的配置。
  • 与其它特定的BeanDefinition相比,它是比较简单和轻量级的。
  • 当使用<bean>元素在XML中定义bean时,通常会为该bean创建一个GenericBeanDefinition实例。

RootBeanDefinition:

  • 这是一个完整的bean定义,包含了bean的所有配置信息,如构造函数参数、属性值、方法覆盖等。
  • 它通常用于合并父子bean定义。也就是说,当一个bean定义继承另一个bean定义时,RootBeanDefinition负责持有合并后的最终配置。
  • 除了GenericBeanDefinition之外,它还包含许多与bean的实例化、依赖解析和初始化相关的内部细节。
  • Spring的内部工作流中,尽管开始时可以有各种BeanDefinition实现,但在容器的后期处理阶段,它们通常都会转化为RootBeanDefinition,因为在这个阶段需要一个完整和固定的bean定义来进行bean的创建。

调试点1:我们从BeanFactory中获取了子bean的原始BeanDefinition。这个BeanDefinition只表示了XML中为子 bean配置的元数据,没有与父Bean的合并,只能看到specificProperty属性。

在这里插入图片描述

调试点2:用getMergedBeanDefinition之后,控制台打印的 BeanDefinition 的类型变为了 RootBeanDefinition ,此时,我们从BeanFactory中获取了合并后的子BeanBeanDefinition。由于子BeanBeanDefinition与父BeanBeanDefinition已合并,所以能看到一个完整的属性集,这里在propertyValues中看到两个属性键值对:commonPropertyspecificProperty,这表明子Bean继承了父Bean的属性值。

在这里插入图片描述

注意,这个示例的目的是展示 BeanDefinition 的合并过程,因此我们直接操作了 BeanFactory。在实际的应用开发中,我们一般不会直接操作 BeanFactory

3.2 BeanDefinition合并的目的

  1. 提供完整的BeanDefinition信息:在配置中,我们经常会使用父子BeanDefinition(如通过<bean>标签的parent属性)。子BeanDefinition可能只会定义需要改变或增加的bean属性,而父BeanDefinition则提供共享的默认定义。在这种情况下,合并操作会将父子BeanDefinition的信息合并为一个完整的BeanDefinition,用于接下来的bean创建。

  2. 优化性能:合并操作的结果通常会被缓存起来,因此在下次获取同样的bean时,可以直接从缓存中获取合并后的BeanDefinition,避免了重复的合并操作,从而提高了性能。

  3. 解决循环依赖:在处理bean之间的循环依赖时,需要尽早抛出已经处理(例如实例化和属性填充)的bean,这时就需要一个完整的BeanDefinition信息。因此,BeanDefinition的合并在解决循环依赖问题上也有重要作用。

简而言之,BeanDefinition的合并是为了得到一个完整、准确的BeanDefinition,以供Spring IoC容器后续的bean创建和依赖解析使用。

4. BeanDefinition的合并的源码分析

4.1 BeanDefinition合并过程时序图

在这里插入图片描述

4.2 BeanDefinition合并过程源码解读

这里讲一下前一篇没提到的BeanDefinition的合并,我们针对Spring 5.3.7的源码分析一下,先展示图,后面分析。

在这里插入图片描述

我们分析一下AbstractBeanFactory类的几个方法。

// 获取本地合并后的 BeanDefinition
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
    // 从缓存中获取合并后的 BeanDefinition
    RootBeanDefinition mbd = (RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);
    // 如果缓存中的 BeanDefinition 不为空并且未过时,则直接返回
    // 否则,对 BeanDefinition 进行合并
    return mbd != null && !mbd.stale ? mbd : this.getMergedBeanDefinition(beanName, this.getBeanDefinition(beanName));
}

// 获取合并后的 BeanDefinition
protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) throws BeanDefinitionStoreException {
    // 直接调用 getMergedBeanDefinition 方法,将 containingBd 设为 null
    return this.getMergedBeanDefinition(beanName, bd, (BeanDefinition)null);
}

// 获取合并后的 BeanDefinition
protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException {
    synchronized(this.mergedBeanDefinitions) {
        RootBeanDefinition mbd = null;
        RootBeanDefinition previous = null;

        // 如果没有包含的 BeanDefinition,那么从缓存中获取合并后的 BeanDefinition
        if (containingBd == null) {
            mbd = (RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);
        }

        // 如果缓存中的 BeanDefinition 为空或者过时,那么创建新的 BeanDefinition 进行合并
        if (mbd == null || mbd.stale) {
            previous = mbd;

            // 如果 bd 没有父名称,即没有继承其他的 bean
            // 那么就直接 clone 这个 bd,生成一个 RootBeanDefinition
            if (bd.getParentName() == null) {
                if (bd instanceof RootBeanDefinition) {
                    mbd = ((RootBeanDefinition)bd).cloneBeanDefinition();
                } else {
                    mbd = new RootBeanDefinition(bd);
                }
            } else { // 如果 bd 是一个子 BeanDefinition(即有父 BeanDefinition)
                // 首先获取父 BeanDefinition
                BeanDefinition pbd;
                try {
                    String parentBeanName = this.transformedBeanName(bd.getParentName());
                    if (!beanName.equals(parentBeanName)) {
                        pbd = this.getMergedBeanDefinition(parentBeanName);
                    } else {
                        BeanFactory parent = this.getParentBeanFactory();
                        if (!(parent instanceof ConfigurableBeanFactory)) {
                            throw new NoSuchBeanDefinitionException(parentBeanName, "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + "': cannot be resolved without a ConfigurableBeanFactory parent");
                        }

                        pbd = ((ConfigurableBeanFactory)parent).getMergedBeanDefinition(parentBeanName);
                    }
                } catch (NoSuchBeanDefinitionException var11) {
                    throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, "Could not resolve parent bean definition '" + bd.getParentName() + "'", var11);
                }

                // 创建一个新的 RootBeanDefinition 并覆盖 bd 中的属性
                // 这就完成了父子 BeanDefinition 的合并
                mbd = new RootBeanDefinition(pbd);
                mbd.overrideFrom(bd);
            }

            // 如果合并后的 BeanDefinition 没有指定作用域
            // 则默认设置为 singleton
            if (!StringUtils.hasLength(mbd.getScope())) {
                mbd.setScope("singleton");
            }

            // 如果定义了父 BeanDefinition 且父 BeanDefinition 的作用域不是 singleton 但子 BeanDefinition 的作用域是 singleton
            // 则将子 BeanDefinition 的作用域设置为父 BeanDefinition 的作用域
            if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) {
                mbd.setScope(containingBd.getScope());
            }

            // 如果不存在包含的 BeanDefinition 并且需要缓存 BeanMetadata
            // 那么就将这个新创建并合并的 BeanDefinition 放入 mergedBeanDefinitions 缓存中
            if (containingBd == null && this.isCacheBeanMetadata()) {
                this.mergedBeanDefinitions.put(beanName, mbd);
            }
        }

        // 如果之前存在过期的 BeanDefinition
        // 那么从过期的 BeanDefinition 中拷贝相关的缓存到新的 BeanDefinition 中
        if (previous != null) {
            this.copyRelevantMergedBeanDefinitionCaches(previous, mbd);
        }

        // 返回合并后的 BeanDefinition
        return mbd;
    }
}

  这段代码主要完成了 BeanDefinition 的合并工作。当一个 BeanDefinition 有父 BeanDefinition 时,Spring 会将子 BeanDefinition 的定义与父 BeanDefinition 的定义进行合并,生成一个新的完整的 BeanDefinition,这个过程就是 BeanDefinition 的合并。该合并的 BeanDefinition 会被缓存起来,以便下次使用。如果一个 BeanDefinition 没有父 BeanDefinition,则直接 clone 一份作为合并后的 BeanDefinition。在 Spring 的整个生命周期中,BeanDefinition 的合并可能会发生多次,每次获取 Bean 时,都会先进行 BeanDefinition 的合并。


欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------

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

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

相关文章

怎么在JMeter中的实现关联

我们一直用的phpwind这个系统做为演示系统, 如果没有配置好的同学, 请快速配置之后接着往下看哦. phpwind发贴时由于随着登陆用户的改变, verifycode是动态变化的, 因此需要用到关联. LoadRunner的关联函数是reg_save_param, Jmeter的关联则是利用后置处理器来完成. 在需要查…

jpa Page 1 of 0 containing UNKNOWN instances错误关于like问题的解决记录

导致这个问题的原因很多&#xff0c;这里记录一下我碰到的问题和解决方法。 网上有说时 pageNo要从0开始&#xff0c;我的不是这个问题。 在使用springboot jpa时&#xff0c;发现使用 t.ip like %?5% 语句&#xff0c;如果数据库记录的ip is null时&#xff0c;将查询不到该…

【网络】应用层——HTTPS协议

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; HTTPS协议 &#x1f349;HTTP的不安全性&#x1f349;认识HTTPS协议&#x1f353;加密解密&#x1f35…

露营,「迷失」在春风里

【潮汐商业评论/原创】 “周末一起去露营吧&#xff1f;”Susan向闺蜜发起邀请。 Susan闺蜜看到后连忙说“去多了感觉没意思&#xff0c;还不如去隔壁城市走走呢&#xff1f;”两人一拍即合&#xff0c;便研究起了攻略。 Susan疑惑&#xff0c;好像露营也没火多久&#xff0…

【Linux】-- 进程间通信

目录 一、进程间通信介绍 二、管道 1.什么是管道&#xff08;pipe&#xff09; 2.重定向和管道 &#xff08;1&#xff09;为什么要有管道的存在 &#xff08;2&#xff09;重定向和管道的区别 3.匿名管道 &#xff08;1&#xff09;匿名管道原理 &#xff08;2&…

springAOP的实例

文章目录 前言一.用户登录权限校验1.1 spring 拦截器1.2 传统的用户登录权限验证1.3 使用拦截器的方式1.4 案例1.5 拦截器实现原理 三.统一异常处理3.1 什么是统一异常处理3.2 具体步骤 四.统⼀数据返回格式4.1 为什么需要统一的数据返回4.2 统一返回数据的格式4.3 统一移除处理…

瞅一眼nginx

目录 &#x1f9ac;什么是nginx? &#x1f9ac;nginx配置官方yum源&#xff1a; &#x1f9ac;nginx优点 &#x1f9ac;nginx 缺点 &#x1f9ac;查看nginx默认模块 &#x1f40c;nginx新版本的配置文件&#xff1a; &#x1f40c;nginx目录索引 &#x1f40c;nginx状态…

Attacks in NLP

一、 Introduction NLP对抗攻击是人工智能对抗攻击的一个重要的组成部分&#xff0c;但是最近几年才逐渐开始兴起&#xff0c;究其原因在于NLP对抗攻击与传统computer vision或者audio对抗攻击有很大的不同&#xff0c;主要在于值空间的连续性&#xff08;CV、audio&#xff0…

基于Selenium技术方案的爬虫入门实践

通过爬虫技术抓取网页&#xff0c;动态加载的数据或包含 JavaScript 的页面&#xff0c;需要使用一些特殊的技术和工具。以下是一些常用的技术方法&#xff1a; 使用浏览器模拟器&#xff1a;使用像 Selenium、PhantomJS 或其他类似工具可以模拟一个完整的浏览器环境&#xff0…

Mysql的instr()函数用法详解

最近接手了一个大型老项目&#xff0c;用到的jfinal技术&#xff0c;后端大部分都是拼写的sql&#xff0c;对一些sql函数不太理解的我算是一个挑战&#xff0c;也是一个进步的很大空间。 今天来说下instr这个函数 首先看下我们的表数据 我们先执行&#xff1a; SELECT * fro…

Axwing.878 线性同余方程

题目 给定n组数据ai, bi , mi&#xff0c;对于每组数求出一个xi&#xff0c;使其满足ai * xibi (mod mi)&#xff0c;如果无解则输出impossible。 输入格式 第一行包含整数n。 接下来n行&#xff0c;每行包含一组数据ai , bi , mi。 输出格式 输出共n行&#xff0c;每组数…

【C++】常用到的“using namespace std;”到底是什么?

一、引言 在初学C时&#xff0c;在包含完头文件之后&#xff0c;我们常常会看到这么一句话&#xff1a;using namespace std; 比如&#xff1a; #include<iostream> using namespace std; int main() {cout << "hello world" << endl;return 0…

Java实现neo4j数据库连接及增删改查

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

LeetCode96. 不同的二叉搜索树

96. 不同的二叉搜索树 文章目录 [96. 不同的二叉搜索树](https://leetcode.cn/problems/unique-binary-search-trees/)一、题目二、题解 一、题目 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的…

《剑指offer》(5)搜索算法、位运算、模拟

方法一&#xff1a; class Solution: def GetNumberOfK(self , nums: List[int], k: int) -> int: #从两边开始找&#xff0c;找到之后记录当前位置 left 0 right len(nums) - 1 if k not in nums: return 0 start len(nums) - 1 end 0 while left < right: if nums…

C++ 多态深入解析

文章目录 前言一、什么是多态二、如何实现多态三、代码讲解四、静态联编&#xff0c;动态联编总结 前言 在C编程中&#xff0c;多态性&#xff08;Polymorphism&#xff09;是一种重要的概念&#xff0c;它允许基于对象的实际类型来调用不同的函数。多态性提供了灵活性和可扩展…

高绩效项目管理助力企业数字化变革︱海克斯康数字智能大中华区PMO经理周游

海克斯康数字智能大中华区PMO经理周游先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;高绩效项目管理助力企业数字化变革。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; 在当今项目驱动的…

vue2 封装 webSocket 开箱即用

第一步&#xff1a; 下载 webSocket npm install vue-native-websocket --save 第二步&#xff1a; 需要在 main.js 中 引入 import websocket from vue-native-websocket; Vue.use(websocket, , {connectManually: true, // 手动连接format: json, // json格式reconnection:…

口-肠-脑轴与精神健康的关系

谷禾健康 在个体中&#xff0c;每个微生物栖息地都表现出独特的微生物种群模式。迄今为止&#xff0c;关于微生物组相关疾病的研究主要集中在器官特异性微生物组上。然而&#xff0c;器官间的微生物网络正逐渐成为生理功能和病理过程中的重要调节因子和治疗机会。 在正常情况下…