Spring循环依赖问题,Spring是如何解决循环依赖的?

news2024/9/20 18:37:23

文章目录

  • 一、什么是循环依赖
    • 1、代码实例
    • 2、重要信息
  • 二、源码分析
    • 1、初始化Student
      • 对Student中的ClassRoom进行Autowire操作
    • 2、Student的自动注入ClassRoom时,又对ClassRoom的初始化
    • 3、ClassRoom的初始化,又执行自动注入Student的逻辑
    • 4、Student注入ClassRoom
    • 5、初始化完成的Bean放入一级缓存
  • 三、总结
    • 1、二级缓存的用处
    • 2、汇总流程图!
  • 参考资料

一、什么是循环依赖

多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于C、C依赖于A。
在这里插入图片描述
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

1、代码实例

public class ClassRoom {
    private String name;
    
    @Autowired
    private Collection<Student> students;
	// get set
}

public class Student {
    private Long id;
    private String name;

    @Autowired
    private ClassRoom classRoom;
	// get set
}
public class CircularReferencesDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册 Configuration Class
        context.register(CircularReferencesDemo.class);

        // 如果设置为 false,则抛出异常信息如:currently in creation: Is there an unresolvable circular reference?
        // 默认值为 true
        context.setAllowCircularReferences(true);

        // 启动 Spring 应用上下文
        context.refresh();

        System.out.println("Student : " + context.getBean(Student.class));
        System.out.println("ClassRoom : " + context.getBean(ClassRoom.class));

        // 关闭 Spring 应用上下文
        context.close();
    }

    @Bean
    public static Student student() {
        Student student = new Student();
        student.setId(1L);
        student.setName("张三");
        return student;
    }

    @Bean
    public static ClassRoom classRoom() {
        ClassRoom classRoom = new ClassRoom();
        classRoom.setName("C122");
        return classRoom;
    }
}

我们看到以上实例ClassRoom和Student是互相依赖的。

Spring默认是打开处理循环依赖的开关的,我们可以使用setAllowCircularReferences方法手动设置关闭循环依赖。

开启解决循环依赖之后,以上代码是可以执行没有问题的。关闭循环依赖,以上就会出现循环依赖的异常,如果确认项目中没有循环依赖的问题可以关闭,以提高性能。

2、重要信息

本文的源码都是基于Spring5.2.2版本进行分析的,其他版本或许有些许出入,但是差别不大。

本文所说的循环引用,都是指的是单例Bean,原型Bean不在此分析中。

阅读本文需要明白一些前置知识点:
(1)单例的Bean初始化时机-finishBeanFactoryInitialization方法执行时:
Spring应用上下文生命周期详解及源码分析,Spring IOC容器启动及关闭过程超详细解释(上)
(2)Bean的生命周期需要熟悉:
Spring Bean生命周期——从源码角度详解Spring Bean的生命周期(上)
Spring Bean生命周期——从源码角度详解Spring Bean的生命周期(下)
(3)@Autowire注入流程
@Autowire源码分析,@Autowire是怎么实现依赖注入的

二、源码分析

我们先从IOC容器启动时执行的finishBeanFactoryInitialization方法开始入口,此方法是单例Bean的创建入口,然后执行preInstantiateSingletons方法正是开始创建单例Bean。

// org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
	// 省略上面代码

	// Instantiate all remaining (non-lazy-init) singletons.
	// 关键方法!完成BeanFactory的初始化
	finishBeanFactoryInitialization(beanFactory); 

	// 省略下面代码
// org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
	// 省略上面代码

	// Instantiate all remaining (non-lazy-init) singletons.
	// 关键方法!初始化单例的Bean
	beanFactory.preInstantiateSingletons();
}

preInstantiateSingletons方法在DefaultListableBeanFactory中进行了实现。

// org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
@Override
public void preInstantiateSingletons() throws BeansException {

	// Trigger initialization of all non-lazy singleton beans...
	for (String beanName : beanNames) {
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
			if (isFactoryBean(beanName)) {
				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
				if (bean instanceof FactoryBean) {
					final FactoryBean<?> factory = (FactoryBean<?>) bean;
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
										((SmartFactoryBean<?>) factory)::isEagerInit,
								getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
			}
			else { // 关键方法!获取Bean的入口
				getBean(beanName);
			}
		}
	}
	// 省略下面代码

1、初始化Student

第一个首先是初始化我们的Student类,getBean会调用doGetBean,然后调用getSingleton方法(重点!解决循环依赖最重要的三级缓存):

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName); // 一级缓存
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName); // 二级缓存
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); // 三级缓存
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

第一次调用getSingleton方法不会获取到Bean,获取的是null,走下面的单例bean创建逻辑:

// org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

// 省略上面代码
// Create bean instance.
if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, () -> {
		try {
			return createBean(beanName, mbd, args); // 关键方法!创建Bean,开始正式进入Bena的生命周期
		}
		catch (BeansException ex) {
			// Explicitly remove instance from singleton cache: It might have been put there
			// eagerly by the creation process, to allow for circular reference resolution.
			// Also remove any beans that received a temporary reference to the bean.
			destroySingleton(beanName);
			throw ex;
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

// 省略下面代码

createBean方法会调用doCreateBean方法,doCreateBean方法会创建bean的实例,然后完成属性的初始化(@Autowire还未开始),然后会判断如果该Bean正在创建,就会调用addSingletonFactory方法:

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// 省略上面创建bean的实例,然后完成属性的初始化(@Autowire还未开始)的代码
// 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) {
	if (logger.isTraceEnabled()) {
		logger.trace("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// 省略下面@Autowire的代码

我们会发现,此处对allowCircularReferences 进行了判断,假如说我们打开了循环依赖的开关,会走这个逻辑。

addSingletonFactory方法,通过传入的单例对象工厂,

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存如果不存在
			this.singletonFactories.put(beanName, singletonFactory); // 放入三级缓存
			this.earlySingletonObjects.remove(beanName); // 从二级缓存删掉(其实并没有)
			this.registeredSingletons.add(beanName); // 放入正在注册的单例Bean列表
		}
	}
}

我们注意到此时Student中的classRoom并没有被注入,但是id和name属性已经被初始化了。
在这里插入图片描述

对Student中的ClassRoom进行Autowire操作

我们回到doCreateBean方法,接下来会执行populateBean初始化属性值,会调用@Autowire的处理类AutowiredAnnotationBeanPostProcessor的postProcessProperties方法进行处理@Autowire注入。

// populateBean方法,此处省略部分代码

PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {
	if (pvs == null) {
		pvs = mbd.getPropertyValues();
	}
	for (BeanPostProcessor bp : getBeanPostProcessors()) {
		if (bp instanceof InstantiationAwareBeanPostProcessor) {
			InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
			// 关键方法!最终调用AutowiredAnnotationBeanPostProcessor的postProcessProperties
			PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
			if (pvsToUse == null) {
				if (filteredPds == null) {
					filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
				}
				pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
				if (pvsToUse == null) {
					return;
				}
			}
			pvs = pvsToUse;
		}
	}
}
// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {
		metadata.inject(bean, beanName, pvs); // 关键方法
	}
	catch (BeanCreationException ex) {
		throw ex;
	}
	catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
	}
	return pvs;
}
// org.springframework.beans.factory.annotation.InjectionMetadata#inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
		for (InjectedElement element : elementsToIterate) {
			if (logger.isTraceEnabled()) {
				logger.trace("Processing injected element of bean '" + beanName + "': " + element);
			}
			element.inject(target, beanName, pvs); // 关键方法
		}
	}
}

inject方法此处需要自动注入的是属性,需要获取该属性的value值也就是ClassRoom(此时ClassRoom尚未初始化)。

// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
// 此处省略部分源码
try {
	// value就是需要获取的ClassRoom
	value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
// org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
		@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

	descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
	if (Optional.class == descriptor.getDependencyType()) {
		return createOptionalDependency(descriptor, requestingBeanName);
	}
	else if (ObjectFactory.class == descriptor.getDependencyType() ||
			ObjectProvider.class == descriptor.getDependencyType()) {
		return new DependencyObjectProvider(descriptor, requestingBeanName);
	}
	else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
		return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
	}
	else {
		Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
				descriptor, requestingBeanName);
		if (result == null) {
			// 关键代码!会走该逻辑
			result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
		}
		return result;
	}
}
// doResolveDependency方法,省略部分源码,会调用resolveCandidate
if (instanceCandidate instanceof Class) {
	instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}

resolveCandidate会执行beanFactory.getBean方法,又开始了对ClassRoom的getBean操作,开始处理ClassRoom。

// 
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
		throws BeansException {

	return beanFactory.getBean(beanName);
}

2、Student的自动注入ClassRoom时,又对ClassRoom的初始化

我们跳过上面的分析,来到addSingletonFactory方法:
在这里插入图片描述
我们发现,又将ClassRoom放入了三级缓存,此时三级缓存中有了Student和ClassRoom,但是都没有完成对ClassRoom和Student的自动注入工作。

3、ClassRoom的初始化,又执行自动注入Student的逻辑

此时,我们来到ClassRoom的populateBean属性初始化方法,仍然调用AutowiredAnnotationBeanPostProcessor的postProcessProperties方法,来完成对Student的自动注入。

省略我们分析Student的自动注入步骤,我们继续分析,自动注入的过程仍然会调用resolveDependency方法,然后调用doResolveDependency方法,然后调用resolveCandidate方法,又通过beanFactory.getBean来获取Student。

还记得我们上面分析的,doGetBean方法中调用的getSingleton方法的逻辑,此时从三级缓存中已经能够获取到Student了,但是该Student的ClassRoom仍然是null的,但是能够获取到Studen实例,并不影响我们ClassRoom对Student的注入!

// org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
		@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

	final String beanName = transformedBeanName(name);
	Object bean;

	// Eagerly check singleton cache for manually registered singletons.
	// 关键方法!
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		if (logger.isTraceEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}

从三级缓存中获取到Student后,将Student从三级缓存移出,放入二级缓存。
在这里插入图片描述
在这里插入图片描述
此时,在ClassRoom的初始化自动注入Student的过程,获取到了我们的Student(还未注入CLassRoom),在Inject方法中,继续执行会发现通过反射,将Student写入了ClassRoom的属性。

// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Field field = (Field) this.member;
	Object value;
	if (this.cached) {
		value = resolvedCachedArgument(beanName, this.cachedFieldValue);
	}
	else {
		DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
		desc.setContainingClass(bean.getClass());
		Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
		Assert.state(beanFactory != null, "No BeanFactory available");
		TypeConverter typeConverter = beanFactory.getTypeConverter();
		try {
			value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
		}
		catch (BeansException ex) {
			throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
		}
		synchronized (this) {
			if (!this.cached) {
				if (value != null || this.required) {
					this.cachedFieldValue = desc;
					registerDependentBeans(beanName, autowiredBeanNames);
					if (autowiredBeanNames.size() == 1) {
						String autowiredBeanName = autowiredBeanNames.iterator().next();
						if (beanFactory.containsBean(autowiredBeanName) &&
								beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
							this.cachedFieldValue = new ShortcutDependencyDescriptor(
									desc, autowiredBeanName, field.getType());
						}
					}
				}
				else {
					this.cachedFieldValue = null;
				}
				this.cached = true;
			}
		}
	}
	if (value != null) {
		ReflectionUtils.makeAccessible(field);
		field.set(bean, value); // 通过反射写入属性
	}
}

此时完成了ClassRoom的初始化。

4、Student注入ClassRoom

此时ClassRoom在Student初始化过程,完成了初始化并注入Student实例,此时的Student还没有完成对ClassRoom的注入呢!

我们又回到Student的inject方法了(此时相当于是一个递归的过程)。

对ClassRoom的create的过程,会返回创建好的ClassRoom实例(初始化完成也注入完成),正好将ClassRoom注入到Student中。

此时完成对Student和ClassRoom的初始化过程。

也正式解决了Student和ClassRoom的循环依赖问题。

5、初始化完成的Bean放入一级缓存

我们再次回到doGetBean方法,这里调用了getSingleton方法,getSingleton方法最后面的finally中会调用addSingleton方法。

// Create bean instance.
if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, () -> {
		try {
			return createBean(beanName, mbd, args);
		}
		catch (BeansException ex) {
			// Explicitly remove instance from singleton cache: It might have been put there
			// eagerly by the creation process, to allow for circular reference resolution.
			// Also remove any beans that received a temporary reference to the bean.
			destroySingleton(beanName);
			throw ex;
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

addSingleton方法会将Bean放入一级缓存,同时删除二级缓存和三级缓存中的Bean,此时Bean初始化完成,同时也缓存完成,下次获取Bean直接从一级缓存获取即可,提高性能。

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
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);
	}
}

关于Bean的缓存请移步:
Spring中Bean会被缓存吗?Spring的Bean是如何被缓存的?

三、总结

Spring解决循环依赖,正式靠着这三级缓存完成的,相当于一个递归初始化的过程:
在这里插入图片描述

在 Spring 底层 IoC 容器 BeanFactory 中处理循环依赖的方法主要借助于以下 3 个 Map 集合:

  1. singletonObjects(一级 Map),里面保存了所有已经初始化好的单例 Bean
  2. earlySingletonObjects(二级 Map),里面会保存从 三级 Map 获取到的正在初始化的 Bean,保存的同时会移除 三级 Map 中对应的 ObjectFactory 实现类,在完全初始化好某个 Bean 时会移除 二级 Map中对应的早期对象
  3. singletonFactories(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好)

而这三个缓存,其实也就是人们称的三级缓存,其实严格意义上并不能称为“缓存”,这三个缓存其实都是Map:
在这里插入图片描述

1、二级缓存的用处

此处解决循环依赖似乎并没有用到二级缓存,那么二级缓存是哪里用到的呢?

因为通过 三级 Map获取 Bean 会有相关 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 的处理,避免重复处理。
例如在循环依赖中一个 Bean 可能被多个 Bean 依赖, A -> B(也依赖 A) -> C -> A,当你获取 A 这个 Bean 时,后续 B 和 C 都要注入 A,没有上面的 二级 Map的话,三级 Map 保存的 ObjectFactory 实现类会被调用两次,会重复处理,可能出现问题。这就是为什么需要 3 个 Map,另外这样做在性能上也有所提升 。

也是为了处理AOP动态代理的问题,也是一个对象被多个对象重复依赖,导致重复创建的问题:
假如 A 需要进行 AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:
B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。
看到问题没?你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。

2、汇总流程图!

在这里插入图片描述

参考资料

spring如何解决循环依赖
源码深度解析,Spring 如何解决循环依赖?
极客时间《小马哥讲 Spring 核心编程思想》

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

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

相关文章

8. QT_OpenGL--1. 在QtWidget中搭建OpenGL加载框架

1. 说明&#xff1a; 在 Qt 中使用 OpenGL&#xff0c;实际上时严格遵循一种代码开发框架的&#xff0c;在 QtWidget 中&#xff0c;需要使用 openGlWidget 控件&#xff0c;并自定义类&#xff0c;类中还需继承 QOpenGLWidget,QOpenGLFunctions_3_3_Core 两个类&#xff0c;并…

Word控件Spire.Doc 【Table】教程(14): 如何在C#中为word表格设置AutoFit选项

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

【沁恒WCH CH32V307V-R1的单线半双工模式串口通讯】

【沁恒WCH CH32V307V-R1的单线半双工模式串口通讯】1. 前言2. 软件配置2.1 安装MounRiver Studio3. UASRT项目测试3.1 打开UASRT工程3.2 编译项目4. 下载验证4.1 接线4.2 演示效果5. 小结1. 前言 该模块包含 3 个通用同步异步收发器&#xff08;USART1/2/3&#xff09;和 5 个通…

vivo 自研Jenkins资源调度系统设计与实践

作者&#xff1a;vivo 互联网服务器团队- Wu Qinghua 本文从目前业界实现Jenkins的高可用的实现方案&#xff0c;分析各方案的优缺点&#xff0c;引入vivo目前使用的Jenkins高可用方案&#xff0c;以及目前Jenkins资源的调度方案的设计实践和目前的落地运行效果。 一、前言 现…

微服务实战--高级篇:分布式事务seata

分布式事务 1.分布式事务问题 1.1.本地事务 本地事务&#xff0c;也就是传统的单机事务。在传统数据库事务中&#xff0c;必须要满足四个原则&#xff1a; 1.2.分布式事务 分布式事务&#xff0c;就是指不是在单个服务或单个数据库架构下&#xff0c;产生的事务&#xff0c…

PPP协议

PPP协议PPP协议概述PPP链路建立过程PPP链路接口状态机LCP报文格式LCP协商过程—正常协商&#xff08;链路层协商过程&#xff09;LCP协商过程—参数不匹配&#xff08;链路层协商过程&#xff09;LCP协商过程—参数不识别&#xff08;链路层协商过程&#xff09;PPP认证模式PAP…

vTESTstudio - VT System CAPL Functions - General/Trigger Function

前面文章中我们已经介绍了常用的几种板卡的基本信息&#xff0c;那这些板卡该如何去通过软件调用呢&#xff1f;带着这个问题我们开始新的一块内容 - VT系统相关的自动化控制函数介绍&#xff0c;我会按照不同的板卡来分类&#xff0c;对其可控制的函数进行介绍&#xff0c;方便…

快速理解 JVM 原理 - 【基础概念篇】

快速理解 JVM 原理 - 【基础概念篇】 Java虚拟机 是什么&#xff1f; Java 虚拟机 本质就是一台”虚拟的“计算机 &#xff0c;大家通俗的理解是一款 “软件”安装在电脑上 虚拟机通常分为两类&#xff1a; 系统虚拟机【可运行完整操 作系统的软件平台】程序虚拟机 【为执行…

AA-PEG-AA,Acetic Acid-PEG-Acetic Acid,羧酸-聚乙二醇-羧酸供应

英文名称&#xff1a;Acetic Acid-PEG-Acetic Acid&#xff0c;AA-PEG-AA 中文名称&#xff1a;羧酸-聚乙二醇-羧酸 AA-PEG-AA是一种线性双功能PEG羧酸试剂。PEG和COOH基团之间存在亚甲基&#xff08;CH2&#xff09;键。AA-PEG-AA也称为CM-PEG-CM&#xff0c;CM&#xff1a;…

【GD32F427开发板试用】使用TinyMaix进行手写数字识别

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;xusiwei1236 一、TinyMaix简介 TinyMaix是国内sipeed团队开发一个轻量级AI推理框架&#xff0c;官方介绍如下&#xff1a; TinyMaix 是面向单…

django+mysql实现一个简单的web登录页面

目录 一、使用pyacharm创建一个django项目 二、启动django项目验证 三、配置mysql数据库 1、本地安装mysql数据库 1&#xff09;安装mysql数据库 2&#xff09;自己创建一个数据库 2、安装 pymysql 3、配置mysql数据库 1&#xff09;在项目同名包下的_init_.py里面添加…

OpenGL渲染管线介绍

一、概述 OpenGL是跨平台计算机图形应用程序的应用规范&#xff0c;广泛应用于仿真、游戏、GIS系统等领域&#xff0c;实现二三维图形的渲染。OpenGL渲染过程需要经历CPU、GPU两个阶段&#xff0c;CPU中进行图形计算&#xff0c;完成之后调用OpenGL开发接口在GPU中创建缓存区缓…

前后端一些下载与配置(第二篇 第10天过后)nuxt banner redis

NUXT 应该是不用怎么装&#xff1f; 有现成的 axios 还需要在npm吗 好像已经有现成的了 banner banner 笔记汇总P396 Redis Linux安装redis tar -xzvf redis-6.2.6.tar.gz cd redis-6.2.6 照着他做 然后 cd /usr/local/redis/bin ./redis-server /usr/local/redis…

图数据建模基础

Neo4j 图的组件 节点&#xff08;Nodes&#xff09;标签&#xff08;Labels&#xff09;关系&#xff08;Relationships&#xff09;属性&#xff08;Properties&#xff09;建模过程 了解领域并为应用程序定义特定用例&#xff08;问题&#xff09;。开发初始图形数据模型。 对…

ansible的剧本(playbook)

一、playbooks 概述以及实例操作 1、playbooks 的组成 playbooks 本身由以下各部分组成 &#xff08;1&#xff09;Tasks&#xff1a;任务&#xff0c;即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行 &#xff08;2&#xff09;Variables&#xff1…

多模式支持无线监控技术:主动式定位、被动式定位

物联网空间信息与数字技术发展至今&#xff0c;已经催生了一大批优秀的践行者。在日常与商业应用中&#xff0c;室内外定位领域依托于这一技术的发展&#xff0c;更是在近几年风光无限。但是并不是说室内定位与室外定位都已经相当成熟&#xff0c;相对来说&#xff0c;室内定位…

简单实用的内网穿透实现教程

内网穿透&#xff0c;字面理解就是网络地址穿透&#xff0c;是一种比较常用的将内网地址转换成公网地址的方式。通过内网穿透&#xff0c;可以将本地内网局域网提供给外网公网上访问&#xff0c;在外网也能连接访问内网主机和应用&#xff0c;当用户有日常远程和异地外网访问的…

Zabbix的自定义监控

Zabbix的自定义监控 zabbix自动可以提供很多监控项&#xff1b;但是往往不能满足需求&#xff1b;尝尝需要我们自己创建一系列的监控项&#xff0c;这就是自定义监控&#xff1b; 监控项&#xff1a;zabbix进行监控的一个指标&#xff0c;zabbix成为item&#xff1b; 它的值…

C++7:STL-模拟实现vector

目录 vector的成员变量 构造函数 reserve size() capacity() push_back 一些小BUG 赋值操作符重载 析构函数 【】操作符重载 resize pop_back Insert 迭代器失效 erase 二维数组问题 总结一下 vector&#xff0c;翻译软件会告诉你它的意思是向量&#xff0c;但其…

面试腾讯测开岗,结束后被面试官吐槽“什么阿猫阿狗都敢来面试大厂了吗?”.....

前一阵子有个小徒弟向我诉苦&#xff0c;说自己在参加某大厂测试面试的时候被面试官怼得哑口无言&#xff0c;场面让他一度十分尴尬 印象最深的就是下面几个问题&#xff1a; 根据你以前的工作经验和学习到的测试技术&#xff0c;说说你对质量保证的理解&#xff1f; 非关系型…