【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);
    
    // 如果是单例的bean
    if (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);
    
    // 如果是单例的bean
    if (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);
    
    // 如果是单例的bean
    if (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领域新星创作者,喜欢后端架构和中间件源码。

我们下期再见。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

经典七大比较排序算法 ·上

经典七大比较排序算法 上1 选择排序1.1 算法思想1.2 代码实现1.3 选择排序特性2 冒泡排序2.1 算法思想2.2 代码实现2.3 冒泡排序特性3 堆排序3.1 堆排序特性&#xff1a;4 快速排序4.1 算法思想4.2 代码实现4.3 快速排序特性5 归并排序5.1 算法思想5.2 代码实现5.3 归并排序特性…

QT的使用3:鼠标事件

鼠标事件0 事件1 需求2 查看控件的事件处理函数3 UI设计4 新建一个类&#xff0c;继承QLabel5 对已有对象进行类型提升6 重写事件处理函数7 项目进一步拓展&#xff08;1&#xff09;获取鼠标按键&#xff08;2&#xff09;鼠标移动&#xff08;3&#xff09;显示多个按键&…

【数据结构】Java实现栈

目录 1. 概念 2. 栈的使用 3. 自己动手实现栈&#xff08;使用动态数组实现栈&#xff09; 1. 创建一个MyStack类 2. push入栈 3. pop出栈 4. 查看栈顶元素 5. 判断栈是否为空与获取栈长 6. toString方法 4. 整体实现 4.1 MyStack类 4.2 Test类 4.3 测试结果 1.…

计算机网络笔记——物理层

计算机网络笔记——物理层2. 物理层2.1 通信基础2.1.1 信号2.1.2 信源、信道及信宿2.1.3 速率、波特及码元2.1.4 带宽2.1.5 奈奎斯特定理采样定理奈奎斯特定理2.1.6 香农定理2.1.7 编码与调制调制数字信号调制为模拟信号模拟数据调制为模拟信号编码数字数据编码为数字信号模拟数…

C#中WPF实现依赖注入和MVVM,以及服务定位ServiceLocator

最近在想重写架构于是就研究了一套WPF的相关内容&#xff0c;WPF不像MAUI内置了容器&#xff0c;需要我们自己手动添加&#xff0c;于是就有了今天的内容。 首先&#xff0c;我们新建一个.net6.0的WPF项目 由于WPF没有内置容器,我们先安装一下依赖注入的nuget包 Microsoft.Ex…

网络技术与应用概论(上)——“计算机网络”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰的内容依旧是计算机网络的一些知识点噢&#xff0c;下面&#xff0c;让我们进入计算机网络的世界吧 网络内涵 网络特征 网络定义 互联网发展过程 从ARPA网络到Internet 从低速互联网到高速互联网 从数据结构到统一网…

【C语言】通讯录的实现(静态版)

【C语言】通讯录的实现(静态版一.前言1.前期准备a.菜单实现b.联系人结构体的构建c.菜单选项的功能d.#define 的定义2.功能的实现a.初始化通讯录b.增加联系人c.显示通讯录d.查找联系人e.修改联系人d.删除联系人3. 总代码test.ccontact.ccontact.h一.前言 本文将会用c语言实现一…

Golang每日一练(leetDay0013)

目录 37. 解数独 Sudoku Solver &#x1f31f;&#x1f31f;&#x1f31f; 38. 外观数列 Count and Say &#x1f31f;&#x1f31f; 39. 组合总和 Combination Sum &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Py…

大数据技术之Hive

第1章Hive基本概念1.1 Hive1.1.1 Hive的产生背景在那一年的大数据开源社区&#xff0c;我们有了HDFS来存储海量数据、MapReduce来对海量数据进行分布式并行计算、Yarn来实现资源管理和作业调度。但是面对海量数据和负责的业务逻辑&#xff0c;开发人员要编写MR来对数据进行统计…

【Go】K8s 管理系统项目[Jenkins Pipeline K8s环境–应用部署]

K8s 管理系统项目[Jenkins Pipeline K8s环境–应用部署] 1. k8s-plantform-api-Pipeline 考虑到实际工作中前后端可能是不同的同学完成,一般Api部分完成后改动会比较小,web部分改动会比较频繁.于是将api和web分了2个pipeline实现 1.1 GIt仓库 docker目录存放镜像构建相关文件…

简介虚拟地址空间:保障进程间独立性的机制

我们知道&#xff0c;进程之间是相互独立的&#xff0c;在操作系统级别中&#xff0c;一个进程所执行的程序无法直接访问另一个进程所执行的内存区域&#xff08;即实现进程间通信比较困难&#xff09;&#xff1b;一个进程运行的失败也不会影响其它进程的运行。这使我们的操作…

vue编程方法

1&#xff0c;app.vue 其中的moundted只是被执行一次。 系统中所有的组件都放到app。vue文件中。放到根组件中的只是被执行一次的代码可以放到main.js中码&#xff1f; 不可以&#xff0c;因为main文件只是一个js文件不是一个组件。组件中的一些属性不能被使用。比如&#xff…

VS Code上搭建Vue开发环境超详细教程

这篇关于在Visual Studio Code上搭建vue开发环境的超详细教程手把手教会你! 首先在Visual Studio Code上搭建vue开发环境有几个步骤&#xff1a; 1、下载安装node.js 2、安装npm 3、安装cnpm 4、安装vue/cli脚手架 5、创建vue项目 6、运行vue项目 1.下载安装node.js 地址&…

鸟哥的Linux私房菜 正则表示法与文件格式化处理

第十一章、正则表示法与文件格式化处理 https://linux.vbird.org/linux_basic/centos7/0330regularex.php 简体版 http://cn.linux.vbird.org/linux_basic/0330regularex.php 11.2.2 grep的一些高级选项 例题一、搜索特定字符串 例题二、利用中括号 [] 来搜寻集合字符 例题四…

8个python自动化脚本提高打工人幸福感~比心~

人生苦短&#xff0c;我用Python 最近有许多打工人都找我说打工好难 每天都是执行许多重复的任务&#xff0c; 例如阅读新闻、发邮件、查看天气、打开书签、清理文件夹等等&#xff0c; 使用自动化脚本&#xff0c;就无需手动一次又一次地完成这些任务&#xff0c; 非常方便…

蓝桥杯嵌入式RTC实时时钟

文章目录 前言一、RTC是什么二、cubemx的配置三、函数的使用总结前言 本篇文章将给大家介绍RTC实时时钟。 一、RTC是什么 STM32的实时时钟RTC是一个独立的定时器,RTC时钟内部依靠BCD码计数。RTC实时时钟提高时钟、闹钟、日历功能。RTC功耗较低,可以使用在低功耗设备上。 …

Redis为什么选择单线程?Redis为什么这么快?

目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程&#xff1f;三、Redis6.0引入多线程四、Redis主线程和IO线程是如何完成请求的&#xff1f;1、服务端和客户端建立socket连接2、IO线程读取并解析请求3、主线程执行请求命令4、IO线程会写回socket和主线程清…

DM8:LINUX环境安装DM8数据库安装条件--GLIBC版本要求

DM8&#xff1a;LINUX环境安装DM8数据库安装条件--GLIBC版本要求环境介绍1 检查 GLIBC 版本号2 /tmp 临时目录空间要等于或大于2GB3 报错截图3.1 导入授权报错3.2 设置时区报错3.3 DmAPService启动失败3.4 初始化实例报错4 更多达梦数据库使用经验环境介绍 在LINUX环境安装达梦…

一线大厂软件测试常见面试题1500问,背完直接拿捏面试官,

三、测试理论 3.1 你们原来项目的测试流程是怎么样的? 我们的测试流程主要有三个阶段&#xff1a;需求了解分析、测试准备、测试执行。 1、需求了解分析阶段 我们的SE会把需求文档给我们自己先去了解一到两天这样&#xff0c;之后我们会有一个需求澄清会议&#xff0c; 我…

基于Springboot实现口腔牙诊所网站平台【源码+论文】

基于Springboot实现口腔牙诊所网站平台【源码论文】开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea M…