Spring系列 循环依赖

news2024/10/10 5:39:26

文章目录

  • 注入方式
  • 循环依赖的场景
  • 单例创建流程
    • getSingleton
    • createBean
      • doCreateBean
      • createBeanInstance
  • 循环依赖分析
    • 为什么都使用构造函数无法解决?
    • 为什么使用@Autowired可以解决?
    • 为什么要添加到 earlySingletonObjects 缓存中?
    • allowCircularReferences 的作用
    • 只用二级缓存能解决循环依赖吗?
    • 二级缓存什么时候放入一级缓存?
    • 循环依赖的暴露顺序
  • 参考资料

注入方式

一般注入方式可以分为自动注入和手动注入
自动注入我分为以下几类

  1. 构造函数参数
  2. setter 方法 + @Autowire,字段 + @Autowire/@Resource

除了自动注入,在 Bean 的生命周期回调中通过 ApplicationContext 进行手动赋值,我将这种方式称为手动注入

循环依赖的场景

下面是不同场景下循环依赖的解决情况的测试结果

依赖情况依赖注入方式循环依赖是否被解决
AB相互依赖均采用setter方法注入
AB相互依赖
allowCircularReferences = false
均采用setter方法注入
AB相互依赖均采用构造器注入
AB相互依赖A中注入B的方式为setter方法,
B中注入A的方式为构造器
AB相互依赖
allowCircularReferences = false
A中注入B的方式为setter方法,
B中注入A的方式为构造器
AB相互依赖B中注入A的方式为setter方法,
A中注入B的方式为构造器

单例创建流程

getSingleton

首先调用 getBean 时会先到单例缓存中去获取一次
在这里插入图片描述

getSingleton(beanName, true)这个方法实际上就是到缓存中尝试去获取Bean,在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry这个类中有三个Map,对应的就是这些缓存

  1. singletonObjects,存储的是所有创建好了的单例Bean
  2. earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
  3. singletonFactories,提前暴露的一个单例工厂

如下图所示:

在这里插入图片描述

下面我简化了一下 doGetBean 的部分,保留核心流程

protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {
	// 最终要返回的对象
	Object beanInstance;
	// Eagerly check singleton cache for manually registered singletons.
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		// ... 此处简化了部分代码
		beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	} else {
		// ... 此处简化了部分代码
		if (mbd.isSingleton()) {
			sharedInstance = getSingleton(beanName, () -> {
				try {
					return createBean(beanName, mbd, args);
				} catch (BeansException ex) {
					// ... 此处简化了部分代码
					destroySingleton(beanName);
					throw ex;
				}
			});
			beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
		}
		// ... 此处简化了部分代码
	}
	return beanInstance;
}

当获取一个 Bean 时会先去缓存中查一次,如果没有则走 getSingleton 带 ObjectFactory 参数的实现

Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)

这里传入的 ObjectFactory 是一个 lambda 表达式,就简单调用了 createBean 方法并返回,这个方法就是用来创建Bean的。

同样,简化后的方法内容如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	
	synchronized (this.singletonObjects) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null) {
			// ... 此处省略部分代码
			beforeSingletonCreation(beanName);
			boolean newSingleton = false;
			// ... 此处省略部分代码
			try {
				// 调用 createBean 
				singletonObject = singletonFactory.getObject();
				newSingleton = true;
			} catch (IllegalStateException ex) {
				// ... 此处省略部分代码
			} finally {
				// ... 此处省略部分代码
				afterSingletonCreation(beanName);
			}
			if (newSingleton) {
				addSingleton(beanName, singletonObject);
			}
		}
		return singletonObject;
	}
}

调用 createBean 的前后分别调用了 beforeSingletonCreationafterSingletonCreation,这两个方法分别用于标记一个 beanName 是否在创建中以及清除标记

protected void beforeSingletonCreation(String beanName) {
	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
		throw new BeanCurrentlyInCreationException(beanName);
	}
}

protected void afterSingletonCreation(String beanName) {
	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
		throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
	}
}

最后调用了 addSingleton 方法,此方法的内容就是将 createBean 创建的对象放入 singletonObjects 这个缓存中,并且从 singletonFactories 和 earlySingletonObjects 这两个缓存中移除当前创建的这个 beanName

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);
	}
}

从这我们就知道一个获取单例的一个流程:

  1. 先从单例池中获取一次,如果没有,则进行创建
  2. 创建前,标记当前 beanName 为创建中
  3. 创建 beanName 对应的对象
  4. 创建后,清除标记
  5. 将创建的对象添加到 signletonObjects 缓存中

在这里插入图片描述

createBean

createBean 这个方法是在 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 中实现的

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	try {
		// 可以通过 BeanPostProcessors 返回一个代理对象
		Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
		if (bean != null) {
			return bean;
		}
	} catch (Throwable ex) {
		// ... 省略部分代码
	}
	try {
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		// ... 省略部分日志代码
		return beanInstance;
	} catch (XxxException... ex) {
		// ... 省略部分异常处理代码
		throw ex;
	}
}

resolveBeforeInstantiation 这部分先不看,这是关于代理对象的逻辑,doCreateBean 是实际创建对象的逻辑。现在我们只需要知道如果 resolveBeforeInstantiation 方法返回了一个对象,那么 createBean 方法根本不会走 doCreateBean ,也就是不回去创建当前这个 beanName 的对象。

doCreateBean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
	// 实例化 Bean
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		// 先从 factoryBeanInstanceCache 这个缓存中获取
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	if (instanceWrapper == null) {
		// 创建对象
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	Object bean = instanceWrapper.getWrappedInstance();
	Class<?> beanType = instanceWrapper.getWrappedClass();
	if (beanType != NullBean.class) {
		mbd.resolvedTargetType = beanType;
	}

	// 执行 MergedBeanDefinitionPostProcessor
	synchronized (mbd.postProcessingLock) {
		if (!mbd.postProcessed) {
			applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
			// ...
			mbd.postProcessed = true;
		}
	}

	// 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) {
		// ... 省略部分日志记录代码
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

	// Initialize the bean instance.
	Object exposedObject = bean;
	try {
		populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	} catch (Throwable ex) {
		// ...
	}

	if (earlySingletonExposure) {
		Object earlySingletonReference = getSingleton(beanName, false);
		if (earlySingletonReference != null) {
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				String[] dependentBeans = getDependentBeans(beanName);
				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
				for (String dependentBean : dependentBeans) {
					if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
						actualDependentBeans.add(dependentBean);
					}
				}
				if (!actualDependentBeans.isEmpty()) {
					// ... throw
				}
			}
		}
	}

	// Register bean as disposable.
	try {
		registerDisposableBeanIfNecessary(beanName, bean, mbd);
	} catch (BeanDefinitionValidationException ex) {
		// ...
	}
	return exposedObject;
}

在这里插入图片描述

addSingletonFactory 的目的就是将 ObjectFactory 添加到 singletonFactories 这个缓存中

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);
		}
	}
}

执行 addSingletonFactory 之前有个前提条件,就是 (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
前面已经说过,createBean 之前,会标记当前 beanName 在创建中,所以 isSingletonCurrentlyInCreation(beanName)); 是为 true 的,
在这里插入图片描述

getEarlyBeanReference 这个方法是调用 SmartInstantiationAwareBeanPostProcessor 这个扩展点的,它是在刚创建好像时进行调用,此时还未执行 populateBean 操作。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
			exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		}
	}
	return exposedObject;
}

createBeanInstance

这里要重点了解一下 createBeanInstance 方法,这个方法是创建一个对象的逻辑

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
	// Make sure bean class is actually resolved at this point.
	Class<?> beanClass = resolveBeanClass(mbd, beanName);
	// ...
	Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
	if (instanceSupplier != null) {
		return obtainFromSupplier(instanceSupplier, beanName);
	}

	if (mbd.getFactoryMethodName() != null) {
		return instantiateUsingFactoryMethod(beanName, mbd, args);
	}
	// Shortcut when re-creating the same bean...
	boolean resolved = false;
	boolean autowireNecessary = false;
	if (args == null) {
		synchronized (mbd.constructorArgumentLock) {
			if (mbd.resolvedConstructorOrFactoryMethod != null) {
				resolved = true;
				autowireNecessary = mbd.constructorArgumentsResolved;
			}
		}
	}
	if (resolved) {
		if (autowireNecessary) {
			return autowireConstructor(beanName, mbd, null, null);
		} else {
			return instantiateBean(beanName, mbd);
		}
	}

	// Candidate constructors for autowiring?
	Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
	if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
			mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
		return autowireConstructor(beanName, mbd, ctors, args);
	}
	// Preferred constructors for default construction?
	ctors = mbd.getPreferredConstructors();
	if (ctors != null) {
		return autowireConstructor(beanName, mbd, ctors, null);
	}
	// No special handling: simply use no-arg constructor.
	return instantiateBean(beanName, mbd);
}

循环依赖分析

为什么都使用构造函数无法解决?

因为创建 A 时, createBeanInstance 里如果使用构造函数进行对象的创建,那么会进行依赖注入,如果发现构造函数中依赖了 B,那么会通过 getBean 方法去获取 B 这个对象,因此会再次走创建单例的逻辑,于是又会碰到 B 的构造方法依赖 A 的情况,发生了循环依赖,因此会抛出下面的异常信息:Error creating bean with name ‘xxx’: Requested bean is currently in creation: Is there an unresolvable circular reference?

这个异常就是在 beforeSingletonCreation 方法抛出的
在这里插入图片描述

整个过程如下:

  1. 第一次标记了 A,还未清除 A,就创建 B
  2. 于是标记 B ,由于B依赖A,所以会再次标记 A 在创建中
  3. 由于 A 已被标记,所以直接抛异常

其实本质原因就是构造函数必须参数要有值才能创建这个对象

为什么使用@Autowired可以解决?

使用 @Autowired 时属性注入其实不是在 populateBean 方法中进行的,而是在 initializeBean 的 BeanPostProcessor 中进行的。
没有构造函数依赖的情况下,会默认通过无参构造创建,先创建这个对象,然后进行相关依赖属性的填充。所以区别就在于使用@Autowire可以创建对象,虽然此时这个对象还未进行属性填充,但是使用构造函数依赖连对象都不能创建。

因为 B 需要自动注入 A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了

在这里插入图片描述
但是不同的是,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,所以此时getBean(A)可以直接从缓存中获取

在这里插入图片描述

为什么要添加到 earlySingletonObjects 缓存中?

个人认为是为了性能考虑。举个例子:目前是只有 A 和 B 之间存在循环依赖,但是如果存在多层循环依赖的的话,如下所示。那么执行 doGetBean 方法时直接从而及缓存中就可以获取到,直接就返回了,就没有后续的那么多操作了

class A {
    B b;
}

class B {
    A a;
    C c;
}

class C {
    A a;
    B b;
}

allowCircularReferences 的作用

allowCircularReferences,当出现循环依赖时是否自动尝试解决,默认为 true。

在这里插入图片描述

直观点儿的解释就是控制是否将创建的 bean 加入 singletonFactories 缓存

在这里插入图片描述

如果是普通的 spring 项目,可以以下面的方式进行设置

@ComponentScan
public class Main {
    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.setAllowCircularReferences(false);
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);
        context.register(Main.class);
        context.refresh();
    }
}

如果是springboot环境下则需要配置spring.main.allow-circular-references=true

allowCircularReferences 为 false 的情况下即使都用@Autowire注入还是会报错。例如默认情况下 springboot 项目就会报错,因为 allowCircularReferences 这个配置默认是 false

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  a
↑     ↓
|  b (field org.example.springboot.config.A org.example.springboot.config.B.a)
└─────┘

Action:

Relying upon circular references is discouraged and they are prohibited by 
default. Update your application to remove the dependency cycle between 
beans. As a last resort, it may be possible to break the cycle automatically 
by setting spring.main.allow-circular-references to true.

因为 allowCircularReferences 是 false

在这里插入图片描述

allowCircularReferences 是 false 的情况下是根本不会调用 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 这句代码的。也就是

在这里插入图片描述
所以这里是一定要求 singletonObjects 或者 earlySingletonObjects 是有所需的对象的
在这里插入图片描述

只用二级缓存能解决循环依赖吗?

能解决非构造函数循环依赖的根本条件是:A 依赖 B 时,要去创建 B ,此时 A 已经创建。我们需要将 A 或者获取 A 的逻辑保存在一个位置,后面 B 依赖 A 时能够直接获取到。只使用一个缓存也可以实现这个目的。那为什么要设计这 3 个缓存呢?

个人理解:Spring 容器本质就是一个Map,其key为beanName,value为对象。而我们通过BeanFactory#getBean获取的Bean 需要是可以使用的。由于循环依赖的存在,一个对象的依赖是需要在该对象之前先创建好的,所以这个时候不可避免的出现了这种虽然实例化了,但是属性还未填充的Bean,这种半成品Bean也是不可使用的。

如果一个不可使用的 Bean 还未创建好,就通过 getBean 去获取,就可能会出错。并且循环依赖场景如果想要靠 Spring 自身解决循环依赖是一定会存在需要早期暴露的对象的。比如 A 依赖 B,那么创建 B 时不可能拿到已经创建好的 A 对象,这两种对象一定要分开存放。所以这个时候需要两个 Map 来存放,一个 singletonObjects 放已经创建好的,另外一个 earlySingletonObjects 放需要早期暴露的对象。

那么为什么需要 singletonFactories 这个ObjectFactory工厂的缓存呢?

个人认为和 AOP 应该没有关系,AOP 只是利用这几个缓存实现,而非为了 实现 AOP 才使用 singletonFactories 这个缓存。使用 singletonFactories 这个就是为了不在对象未完全创建时进行暴露。

因为创建 Bean 之后紧接着又要调用其他 API ,比如填充属性 populateBean、初始化 initializeBean 等,在这些 API 中可以通过 ApplicationContextAware、BeanFactoryAware 等方式拿到 BeanFactory 并调用 getBean 方法。ObjectFactory 的作用只是将这个对象暴露的时间延迟到属性填充和初始化完毕之后了。直到 ObjectFactory 调用之前, earlySingletonObjects 都是没有值的,而 ObjectFactory 只会调用一次,调用后就直接删除了。

在这里插入图片描述

举个例子,现在 A 与 B、C 分别形成循环依赖。当 A 对象创建好,但是还未填充属性时,这个时候发现 A 依赖 B,于是创建 B。这个时候 A 应该放在哪呢?首先singletonObjects 肯定是不能放的,那么是放 earlySingletonObjects 吗?也不行,因为 getBean 只能从某一个地方取,不管从哪儿取拿到的都是不能用的早期对象。所以这个时候不能直接放对象,而是放一个 ObjectFactory,此时该对象还没暴露出去的,不能通过 API 拿到。真正想要获取的时候再通过 ObjectFactory 进行获取。为什么是从 singletonFactories 中获取放入 earlySingletonObjects 呢?因为现在不只 B 需要 A,C 也需要 A,但是 B 获取 A 时是不知道 C 的存在的,此时 A 仍是早期对象。

二级缓存什么时候放入一级缓存?

放入一级缓存只有一个方式,调用 addSingleton 方法,这个是内部 API
在这里插入图片描述

而调用此方法只有 2 个地方

  1. 一是Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)在依赖注入完成时

在这里插入图片描述

  1. 通过SingletonBeanRegistry#registerSingleton这个public API手动注册单例对象

在这里插入图片描述

依赖注入完成时放入

存在循环依赖的情况下 getBean 是一个递归的过程。递归开始的地方有 2 个,都在 doCreateBean 方法里,一个是根据构造函数创建对象时,

在这里插入图片描述

另外一个是对象创建好之后进行初始化的时候

在这里插入图片描述
在 createBean 方法里,如果允许暴露早期对象,会自动调用 getSingleton ,此时 allowEarlyReference 为 false,因为此时 earlySingletonObjects 这个缓存里已经有了(退出递归的结果就是报错或者完成依赖注入被放入 earlySingletonObjects 缓存)
在这里插入图片描述

这个调用的目的就是将 earlySingletonObjects 缓存再放入 singletonObjects 缓存

Object earlySingletonReference = getSingleton(beanName, false);

如下图所示

在这里插入图片描述

循环依赖的暴露顺序

A 依赖 B ,B 先暴露,最后才是 A,因为是个递归过程

参考资料

  1. https://developer.aliyun.com/article/766880

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

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

相关文章

基于Kafka2.1解读Producer原理

文章目录 前言一、Kafka Producer是什么&#xff1f;二、主要组件1.Kafka Producer1.1 partitioner1.2 keySerializer1.3 valueSerializer1.4 accumulator1.5 sender 2.Sender2.1 acks2.2 clientinFlightBatches 3. Selector3.1 nioSelector3.2 channels 4. 全局总览 总结 前言…

【hot100-java】N 皇后

回溯篇 视频题解 真的裂开了&#xff0c;多看视频题解。 class Solution {public List<List<String>> solveNQueens(int n) {List<List<String>>retnew ArrayList<>();int []colnew int[n];boolean[] onPathnew boolean[n];boolean[] diag1ne…

(Linux和数据库)1.Linux操作系统和常用命令

了解Linux操作系统介绍 除了办公和玩游戏之外不用Linux&#xff0c;其他地方都要使用Linux&#xff08;it相关&#xff09; iOS的本质是unix&#xff08;unix是付费版本的操作系统&#xff09; unix和Linux之间很相似 Linux文件系统和目录 bin目录--放工具使用的 操作Linux远程…

双光吊舱图像采集详解!

一、图像采集 可见光图像采集&#xff1a; 使用高性能的可见光相机&#xff0c;通过镜头捕捉自然光或人工光源照射下的目标图像。 相机内部通常配备有先进的图像传感器&#xff0c;如CMOS或CCD&#xff0c;用于将光信号转换为电信号。 红外图像采集&#xff1a; 利用红外热…

【hot100-java】二叉树的最近公共祖先

二叉树篇 我觉得是比两个节点的深度&#xff0c;取min&#xff08;一种情况&#xff09; DFS解题。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val x; }* }*/ clas…

Unity/VS 消除不想要的黄色警告

方法一&#xff1a;单个消除 在要关闭的代码前一行写上#pragma warning disable 警告代码编码 在要关闭代码行下面一行写上#pragma warning restore 警告代码编码 精准的关闭指定地方引起的代码警告&#xff0c;不会过滤掉无辜的代码 #pragma warning disable 0162,1634HandleL…

JDBC: 连接池

文章目录 没有连接池的现状连接池解决现状问题的原理连接池好处常用连接池的介绍Druid连接池DRUID简介Druid常用的配置参数Druid连接池基本使用API介绍 案例代码 没有连接池的现状 通过下图来分析一下我们目前jdbc程序的结构。 以前使用的jdbc的缺点&#xff1a; 1、操作数据库…

户外打气泵方案软件设计开发

随着户外活动的普及和人们对便捷生活的需求&#xff0c;打气泵成为越来越多有车人士及爱好户外运动的人的装备之一。而打气泵的核心控制是它的芯片和软件方案&#xff0c;今天我们就介绍一下打气泵芯片软件方案的开发过程与技术要点。 打气泵方案的软件设计相较于硬件更具复杂性…

数据库——sql多表查询

当要在两个表&#xff08;或多个表&#xff09;中查找对应数据时&#xff0c;与普通的查询略有不同。本篇博文用于介绍基本的多表查询的方法。 为方便展示&#xff0c;本篇建了两个数据库表“学生表”和“班级表”作为例子 其中&#xff0c;在“学生表”中&#xff0c;cid标签…

RT-DETR改进策略:BackBone改进|CAFormer在RT-DETR中的创新应用,显著提升目标检测性能

摘要 在目标检测领域,模型性能的提升一直是研究者和开发者们关注的重点。近期,我们尝试将CAFormer模块引入RT-DETR模型中,以替换其原有的主干网络,这一创新性的改进带来了显著的性能提升。 CAFormer,作为MetaFormer框架下的一个变体,结合了深度可分离卷积和普通自注意力…

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(动态新增、修改等操作)

SpringBoot教程&#xff08;二十四&#xff09; | SpringBoot实现分布式定时任务之Quartz&#xff08;动态新增、修改等操作&#xff09; 前言数据库脚本创建需要被调度的方法创建相关实体类创建业务层接口创建业务层实现类控制层类测试结果 前言 我这边的SpringBoot的版本为2…

Android 防止截屏和录屏

通过给当前的window对象设置标记WindowManager.LayoutParams.FLAG_SECURE来防止截屏和录屏 protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 防止截屏getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManage…

vscode配置:启用括号对着色

想要的效果&#xff1a;启用括号对着色&#xff0c;在大括号之间用折线表示&#xff0c;看起来会更加直观方便&#xff0c;例如在less中嵌套层级比较多时&#xff0c;大括号的层级不容易看清楚&#xff0c;做了这个配置会更好一些。 vscode安装扩展插件&#xff1a;Bracket P…

Spring Boot学习资源库:Spring生态的精华

摘 要 社会的进步&#xff0c;教育行业发展迅速&#xff0c;人们对教育越来越重视&#xff0c;在当今网络普及的情况下&#xff0c;教学模式也开始逐渐网络化&#xff0c;各大高校开始网络教学模式。 本文研究的教学资源库系统基于Springboot框架&#xff0c;采用Java技术和MYS…

Linux deepin系统通过编辑crontab来设置定时任务---定时关机

在Linux系统中&#xff0c;crontab 是用来设置周期性被执行的指令的守护进程。通过编辑 crontab&#xff0c;您可以安排定时任务&#xff0c;比如定时关机、定时备份文件、定时运行脚本等。以下是如何编辑 crontab 来设置定时任务的步骤&#xff1a; 打开终端&#xff1a;您可以…

AcWing 802. 区间和(离散化算法,python)

本篇博客详细讲解一下离散化知识点&#xff0c;通过讲解和详细列题带大家掌握离散化。 题目&#xff1a; 原题链接&#xff1a;https://www.acwing.com/problem/content/description/804/ 假定有一个无限长的数轴&#xff0c;数轴上每个坐标上的数都是 0。 现在&#xff0c;…

平时使用的正则总结

1、将某一个字符串的后缀名后面加上“!400_500” 使用场景是将minio拿过来的图片压缩尺寸从而压缩其大小&#xff0c;加快渲染的速度。需要在图片的后缀名后面加上尺寸如下&#xff1a; const str //storage-test.test.shiqiao.com/gateway/common/isopen/2024/10/09/e708e9…

科创集团所属园区入驻企业北京铭镓半导体获 “硬科技”潜在独角兽企业认定

近日&#xff0c;科创集团所属工宇园区企业北京铭镓半导体荣获北京市科委、中关村管委会“硬科技”潜在独角兽企业认定。独角兽企业特指具备显著创新力、展现出强劲成长潜力以及获得市场高度认可的企业&#xff0c;是新经济领域发展的标志性存在。 北京铭镓半导体有限公司于202…

如何搭建直播美颜平台?视频美颜SDK的核心技术详解

时下&#xff0c;美颜效果作为提升直播吸引力的重要手段&#xff0c;已经成为主播和观众的共同期待。本篇文章&#xff0c;小编将与大家分享搭建一个高效的直播美颜平台的流程&#xff0c;重点介绍视频美颜SDK的核心技术。 一、直播美颜平台的构建 搭建一个直播美颜平台&#…

vue3:自定义描点定位组件(锚点定位和监听滚动切换)以及遇到的问题

目录 第一章 实现效果 第二章 锚点组件分析 2.1 功能分析 2.2 核心点 第三章 源代码 3.1 数据格式 3.2 代码分析 3.2.1 tab栏以及内容页面 3.2.2 逻辑 第四章 遇到的问题 第一章 实现效果 第二章 锚点组件分析 2.1 功能分析 tab栏以及切换涉及逻辑点击tab切换同时页…