spring 源码分析

1 IOC 源码解析

BeanDefinition: bean的定义。里面会有beanClass、beanName、scope等属性

  • beanClass:通过Class.forName生成的Class 对象
  • beanName:context.getBean(“account”),acount就是beanName
  • scope: 作用区分单例bean、原型bean

BeanFactory: 生产bean的工厂

可以把IOC过程分成四个状态:概念态、定义态、纯净态、成熟态。
概念态:只是通过注解或配置文件声明的bean。
定义态:spring 容器通过读取配置文件和注解,把类的定义信息装载到BeanDefinition中。
纯净态:beanFactory通过反射和BeanDefinition生成对象,还没有进行依赖注入。
成熟态:对象在进行依赖注入以及后置处理器处理后就是成熟态。

1.1 通过配置文件进行加载

public class DemoApplication {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");System.out.println(context.getBean("account"));}}

1.2 进入AnnotationConfigApplicationContext

    public AnnotationConfigApplicationContext(String... basePackages) {this();//包扫描this.scan(basePackages);//容器刷新this.refresh();}

1.3 进入核心方法refresh的源码

refresh() 方法是 Spring 上下文启动的核心方法。它包含了多个步骤,这些步骤确保了 Spring 容器被正确初始化和配置。

    public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符。this.prepareRefresh();//生成beanFactory//把通过注解或配置文件声明的bean,解析成BeanDefinition,注册到BeanFactory中。ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();//设置bean的类加载器,添加几个BeanPostProcessor,手动注册几个特殊的 beanthis.prepareBeanFactory(beanFactory);try {//具体的子类可以在这步的时候添加一些特殊的BeanFactoryPostProcessor的实现类//可以对BeanFactory进行一些处理和BeanPostProcessor相似this.postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");//调用BeanFactoryPostProcessor各个实现类的postProcessBeanFactory(factory)方法this.invokeBeanFactoryPostProcessors(beanFactory);//注册BeanPostProcessor的实现类//此接口两个方法:postProcessBeforeInitialization和postProcessAfterInitialization//两个方法分别在Bean初始化之前和初始化之后得到执行,这里仅仅是注册。this.registerBeanPostProcessors(beanFactory);beanPostProcess.end();//初始化 MessageSource,用于国际化支持。this.initMessageSource();//初始化事件发布器,用于事件发布和监听。this.initApplicationEventMulticaster();//模板方法,允许子类在容器刷新时进行特定的初始化操作。this.onRefresh();//注册事件监听器。this.registerListeners();//重点!!!//完成BeanFactory的初始化,包括实例化所有的非懒加载的单例bean。this.finishBeanFactoryInitialization(beanFactory);//完成刷新的最后步骤,比如发布上下文刷新事件。this.finishRefresh();} catch (BeansException var10) {BeansException ex = var10;if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + ex);}this.destroyBeans();this.cancelRefresh(ex);throw ex;} finally {this.resetCommonCaches();contextRefresh.end();}}}

(1)【1.准备刷新 】prepareRefresh();
主要是一些准备工作设置其启动日期和活动标志以及执行一些属性的初始化。(不是很重要的方法)

(2)【2.初始化 BeanFactory 】obtainFreshBeanFactory();(重点方法)

    <1>如果有旧的BeanFactory就删除并创建新的BeanFactory<2>解析所有的Spring配置文件,将配置文件中定义的bean封装成BeanDefinition,加载到BeanFactory中(这里只注册,不会进行Bean的实例化)

(3)【3.bean工厂前置操作】prepareBeanFactory(beanFactory);

    配置 beanFactory 的标准上下文特征,例如上下文的 ClassLoader、后置处理器等。

(4)【4.bean工厂后置操作】postProcessBeanFactory(beanFactory);

    空方法,如果子类需要,自己去实现 

(5)【5.调用bean工厂后置处理器】invokeBeanFactoryPostProcessors(beanFactory);

    实例化和调用所有BeanFactoryPostProcessor,完成类的扫描、解析和注册BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,Spring IoC 容器允许 BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它。

(6)【6.注册bean后置处理器】registerBeanPostProcessors(beanFactory);

    将所有实现了 BeanPostProcessor 接口的类注册到 BeanFactory 中。

(7)【7.初始化消息源】initMessageSource();

    初始化MessageSource组件(做国际化功能;消息绑定,消息解析)

(8)【8.初始化事件广播器】initApplicationEventMulticaster();

    初始化应用的事件广播器 ApplicationEventMulticaster。

(9)【9.刷新:拓展方法】onRefresh();

    空方法,模板设计模式;子类重写该方法并在容器刷新的时候自定义逻辑;例:springBoot在onRefresh() 完成内置Tomcat的创建及启动

(10)【10.注册监听器】registerListeners();

    向事件分发器注册硬编码设置的applicationListener,向事件分发器注册一个IOC中的事件监听器(并不实例化)

(11)【11.实例化所有非懒加载的单例bean】finishBeanFactoryInitialization(beanFactory);

    是整个Spring IoC核心中的核心

(12)【12.结束refresh操作】finishRefresh();

    发布事件,ApplicationContext 初始化完成

1.4 核心方法源码解析

1.4.1 obtainFreshBeanFactory

这一步上面简单介绍过了,作用是把配置文件解析成一个个BeanBeanDefinition,并且注册到BeanFactory中,源码:

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {this.refreshBeanFactory();return this.getBeanFactory();}

我们进入refreshBeanFactory()方法

@Override
protected final void refreshBeanFactory() throws BeansException {//如果存在旧的BeanFactory就删除if (hasBeanFactory()) {destroyBeans();closeBeanFactory();}try {//创建新的BeanFactoryDefaultListableBeanFactory beanFactory = createBeanFactory();//序列化beanFactory.setSerializationId(getId());//是否允许 Bean 覆盖、是否允许循环引用customizeBeanFactory(beanFactory);//根据配置,加载各个Bean,然后放到 BeanFactory 中loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}}catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);}
}

这个方法的作用是关闭旧的 BeanFactory (如果有),创建新的 BeanFactory,加载 Bean 定义、注册 Bean 等。

1.4.2 实例化所有非懒加载的单例bean

finishBeanFactoryInitialization(beanFactory)
在这个方法中,我们主要做了以下操作:
(1)将之前解析的 BeanDefinition 进一步处理,将有父 BeanDefinition 的进行合并,获得MergedBeanDefinition
(2)尝试从缓存获取 bean 实例
(3)处理特殊的 bean —— FactoryBean 的创建
(4)创建 bean 实例
(5)循环引用的处理
(6)bean 实例属性填充
(7)bean 实例的初始化
(8)BeanPostProcessor 的各种扩展应用
这个方法解析的结束,也标志着Spring IoC重要内容基本都已经解析完毕

finishBeanFactoryInitialization(beanFactory)点进去看源码

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {// 5.实例化所有剩余(非懒加载)单例对象beanFactory.preInstantiateSingletons();	
}

reInstantiateSingletons();点进去看源码

@Override
public void preInstantiateSingletons() throws BeansException {List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {//判断beanName对应的bean是否为FactoryBeanif (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) {//如果希望急切的初始化,则通过beanName获取bean实例getBean(beanName);}}}else {//如果beanName对应的bean不是FactoryBean,只是普通Bean,通过beanName获取bean实例getBean(beanName);}}}//遍历beanNames,触发所有SmartInitializingSingleton的后初始化回调for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());}else {smartSingleton.afterSingletonsInstantiated();}}}
}

getBean();点进去看源码

@Override
public Object getBean(String name) throws BeansException {// 获取name对应的bean实例,如果不存在,则创建一个return doGetBean(name, null, null, false);
}

doGetBean();点进去看源码

            protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {// 1.解析beanName,主要是解析别名、去掉FactoryBean的前缀“&”		final String beanName = transformedBeanName(name);Object bean;// 2.尝试从缓存中获取beanName对应的实例Object sharedInstance = getSingleton(beanName);// 3.如果beanName的实例存在于缓存中if (sharedInstance != null && args == null) {if (logger.isDebugEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.debug("Returning cached instance of singleton bean '" + beanName + "'");}}// 3.1 返回beanName对应的实例对象(主要用于FactoryBean的特殊处理,普通Bean会直接返回sharedInstance本身)bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {// 4.scope为prototype的循环依赖校验:如果beanName已经正在创建Bean实例中,而此时我们又要再一次创建beanName的实例,则代表出现了循环依赖,需要抛出异常。if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// 5.获取parentBeanFactoryBeanFactory parentBeanFactory = getParentBeanFactory();// 5.1 如果parentBeanFactory存在,并且beanName在当前BeanFactory不存在Bean定义,则尝试从parentBeanFactory中获取bean实例if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// 5.2 将别名解析成真正的beanNameString nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}// 5.3 尝试在parentBeanFactory中获取bean对象实例else if (args != null) {// Delegation to parent with explicit args.return (T) parentBeanFactory.getBean(nameToLookup, args);}else {// No args -> delegate to standard getBean method.return parentBeanFactory.getBean(nameToLookup, requiredType);}}// 6.如果不是仅仅做类型检测,而是创建bean实例,这里要将beanName放到alreadyCreated缓存if (!typeCheckOnly) {markBeanAsCreated(beanName);}try {// 7.根据beanName重新获取MergedBeanDefinition(步骤6将MergedBeanDefinition删除了,这边获取一个新的)final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// 8.拿到当前bean依赖的bean名称集合,在实例化自己之前,需要先实例化自己依赖的beanString[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {// 8.2 检查dep是否依赖于beanName,即检查是否存在循环依赖if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}// 8.4 将dep和beanName的依赖关系注册到缓存中registerDependentBean(dep, beanName);try {// 8.5 获取dep对应的bean实例,如果dep还没有创建bean实例,则创建dep的bean实例getBean(dep);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}}}// 9.针对不同的scope进行bean的创建if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {// 9.1.1 创建Bean实例return createBean(beanName, mbd, args);}catch (BeansException ex) {destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}else if (mbd.isPrototype()) {// 9.2 scope为prototype的bean创建Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}// 10.返回创建出来的bean实例对象return (T) bean;
}

getSingleton(beanName); 点进去看源码

protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1.从单例对象缓存中获取beanName对应的单例对象Object singletonObject = this.singletonObjects.get(beanName);// 2.如果单例对象缓存中没有,并且该beanName对应的单例bean正在创建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 3.加锁进行操作synchronized (this.singletonObjects) {// 4.从早期单例对象缓存中获取单例对象(之所称成为早期单例对象,是因为earlySingletonObjects里// 的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作)singletonObject = this.earlySingletonObjects.get(beanName);// 5.如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用if (singletonObject == null && allowEarlyReference) {// 6.从单例工厂缓存中获取beanName的单例工厂ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 7.如果存在单例对象工厂,则通过工厂创建一个单例对象singletonObject = singletonFactory.getObject();// 8.将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中this.earlySingletonObjects.put(beanName, singletonObject);// 9.移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存了,// 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects缓存拿到,不需要在用到该单例工厂this.singletonFactories.remove(beanName);}}}}// 10.返回单例对象return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
public boolean isSingletonCurrentlyInCreation(String beanName) {return this.singletonsCurrentlyInCreation.contains(beanName);
}

这段代码很重要,在正常情况下,该代码很普通,只是正常的检查下我们要拿的 bean 实例是否存在于缓存中,如果有就返回缓存中的 bean 实例,否则就返回 null。
这段代码之所以重要,是因为该段代码是 Spring 解决循环引用的核心代码。解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该bean 实例还未初始化),并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将
ObjectFactory 放到 singletonFactories 缓存),通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,从而避免出现循环引用导致的死循环。这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的bean 实例,但是由于是单例,所以后续初始化完成后,该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。

另外这个代码块中引进了4个重要缓存:
(1)singletonObjects 缓存:beanName -> 单例 bean 对象。
(2)earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。
(3)singletonFactories 缓存:beanName -> ObjectFactory。
【singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “一、二、三级缓存” 的概念】
(4)singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。

createBean(beanName, mbd, args);点进去看源码

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {...try {//创建Bean实例(真正创建Bean的方法)Object beanInstance = doCreateBean(beanName, mbdToUse, args);if (logger.isDebugEnabled()) {logger.debug("Finished creating instance of bean '" + beanName + "'");}return beanInstance;}...
}

doCreateBean(beanName, mbdToUse, args);点进去看源码

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {......Object exposedObject = bean;try {//对bean进行属性填充populateBean(beanName, mbd, instanceWrapper);//对bean进行初始化,方法中激活了AwareexposedObject = initializeBean(beanName, exposedObject, mbd);}......//完成创建并返回return exposedObject;
}

1.5 IOC流程图

在这里插入图片描述

2 Spring的循环依赖

2.1 三级缓存

三级缓存:

singletonObjects:一级缓存

主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例 Bean 实例,这样的 Bean 能够直接提供给用户使用,我们称之为终态 Bean 或叫成熟 Bean。

earlySingletonObjects:二级缓存

主要存放的已经完成初始化,但属性还没自动赋值的 Bean,这些 Bean 还不能提供用户使用,只是用于提前暴露的 Bean 实例,我们把这样的 Bean 称之为临时 Bean 或早期的 Bean(半成品 Bean)。

singletonFactories:三级缓存

存放的是 ObjectFactory 的匿名内部类实例,调用 ObjectFactory.getObject() 最终会调用 getEarlyBeanReference 方法,该方法可以获取提前暴露的单例 Bean 引用。

2.1 流程分析

Spring 通过三级缓存解决了循环依赖,其中,一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象 earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当 A、B 两个类发生循环引用时,在 A 完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果 A 被 AOP 代理,那么,通过这个工厂获取到的就是 A 代理后的对象,如果 A 没有被 AOP 代理,那么,这个工厂获取到的就是 A 实例化的对象。

当 A 进行属性注入时,会去创建 B,同时,B 又依赖了 A,所以,创建 B 的同时又会去调用 getBean(a) 来获取需要的依赖,此时的 getBean(a) 会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到 B 中。紧接着 B 会走完它的生命周期流程,包括初始化、后置处理器等。当 B 创建完后,会将 B 再注入到 A 中,此时, A 再完成它的整个生命周期。

通过上述流程,可以看出 bean 都是需要先可以被实例化才可以的,所以,这也就是为什么构造器依赖可能会失败的原因。

例如,Bean A 的构造器依赖 B,而实例化 A 需要先调用 A 的构造函数,发现依赖 B,那么,需要再去初始化 B,但是,B 也依赖 A,不管 B 是通过构造器注入还是 setter 注入,此时,由于 A 没有被实例化,没有放入三级缓存,所以, B 无法被初始化,所以,spring 会直接报错。反之,如果 A 通过 setter 注入的话,那么,则可以通过构造函数先实例化,放入缓存,然后再填充属性,这样的话不管 B 是通过 setter 还是构造器注入 A,都能在缓存中获取到,于是可以初始化。

2.3 循环依赖的经典面试题

(1)【Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?】

    答:只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题。

(2)【三级缓存中为什么要添加 ObjectFactory 对象,而不是直接保存实例对象?】

    答:假如想对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的。

(3)【构造器注入注入的循环依赖为什么无法解决?】

    答:因为我们要先用构造函数创建一个 “不完整” 的 bean 实例,如果构造器出现循环依赖,我们连不完整的 bean 实例都构建不出来。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

快速搭建SpringBoot3+Vue3+ElementPlus管理系统

快速搭建SpringBoot3Vue3管理系统 前端项目搭建&#xff08;默认开发环境&#xff1a;node20,Jdk17&#xff09;创建项目并下载依赖--执行以下命令 前端项目搭建&#xff08;默认开发环境&#xff1a;node20,Jdk17&#xff09; 创建项目并下载依赖–执行以下命令 创建项目 y…

基于Hadoop大数据音乐推荐系统的设计与实现

摘 要 各种主流的音乐平台都为用户提供了的大量的音乐&#xff0c;让他们时刻都能沉浸在音乐的海洋之中。然而&#xff0c;过多的音乐往往使用户眼花缭乱&#xff0c;很难发现他们真正所需要的。一套优秀的推荐系统&#xff0c;可以很好地解决这个问题&#xff0c;既能帮助用户…

IDEA遇到EasyConnect中的网络资源无法访问的问题

IDEA遇到EasyConnect中的网络资源无法访问的问题 摘要由CSDN通过智能技术生成 点击编辑IDEA的 启动配置&#xff0c;然后在启动器下面的新增一个请求参数然后重新启动项目&#xff0c; java.net.preferIPv4Stack true IDEA就能连接到EasyConnect代理的网络服务 wanshanyu_ 关…

IP研究 | 大数据洞察黄油小熊的爆火之路

一只来自泰国的小熊在国内红成了顶流。 今年&#xff0c;黄油小熊以烘焙店“打工人”的超萌形象迅速走红&#xff0c;2个月内火遍中国的社交媒体&#xff0c;泰国门店挤满飘洋过海求合影的中国粉丝&#xff0c;根据数说故事全网大数据洞察&#xff0c;黄油小熊2024年度的线上声…

分数求和ᅟᅠ        ‌‍‎‏

分数求和 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入n个分数并对他们求和&#xff0c;并用最简形式表示。所谓最简形式是指&#xff1a;分子分母的最大公约数为1&#xff1b;若最终结果的分母为…

5G中的随机接入过程可以不用收RAR?

有朋友提到了一种不用接收RAR的RA过程&#xff0c;问这个是怎么回事。其实在刚刚写过的LTM cell switch篇章中就有提到&#xff0c;这里把所有相关的内容整理如下。 在RACH-less LTM场景&#xff0c;在进行LTM cell switch之前就要先知道target cell的TA信息&#xff0c;进而才…

Ubuntu安装grafana

需求背景&#xff1a;管理服务器&#xff0c;并在线预警&#xff0c;通知 需求目的&#xff1a; 及时获取服务器状态 技能要求&#xff1a; 1、ubuntu 2、grafana 3、prometheus 4、https://img-home.csdnimg.cn/images/20230724024159.png?origin_urlhttps%3A%2F%2Fimg…

vue3获取、设置元素高度

前言 在web端常见的需求场景中&#xff0c;会经常遇到table表格需要根据页面可视区域使高度自适应的情况。 傻喵(作者本人)昨天在尝试使用vue3实现这个需求时&#xff0c;看了几篇网上写的回答&#xff0c;都不太全面&#xff0c;所以干脆自己写个总结吧.(第一次写&#xff0c…

美畅物联丨观看实时视频对服务器带宽有什么要求?

​随着互联网的迅猛发展&#xff0c;实时视频观看已然成为人们日常生活中不可或缺的一部分。不管是视频会议、在线教育&#xff0c;还是在线娱乐&#xff0c;实时视频都起到了极为重要的作用。不过&#xff0c;实时视频的流畅播放对服务器的带宽有着极高的要求。本文将深入探究…

MongoDB-固定集合(Capped Collection)

在 MongoDB 中&#xff0c;固定集合&#xff08;Capped Collection&#xff09;是一种具有特殊属性的集合。固定集合具有一个固定的最大大小&#xff0c;并且一旦达到该大小时&#xff0c;最早插入的文档将会被自动删除&#xff0c;以便为新的文档腾出空间。固定集合的这种特性…

EasyExcel注解使用

上接《Springboot下导入导出excel》&#xff0c;本篇详细介绍 EasyExcel 注解使用。 1. ExcelProperty value&#xff1a;指定写入的列头&#xff0c;如果不指定则使用成员变量的名字作为列头&#xff1b;如果要设置复杂的头&#xff0c;可以为value指定多个值order&#xff…

yolo-V3

1、研究背景及意义 1&#xff09;对yolo进行创新&#xff0c;准确度更高。 2、创新点 1&#xff09;主要是更换了主干网络&#xff0c;使用了多尺度特征融合。 3、网络结构 yolo-V3以Darket-Net-53为主干网络。网络输入一张尺寸为416416的图片&#xff0c;经过多层卷积分别…

零基础如何使用ChatGPT快速学习Python

引言 AI编程时代来临&#xff0c;没有编程基础可以快速上车享受时代的红利吗&#xff1f;答案是肯定的。本文旨在介绍零基础如何利用ChatGPT快速学习Python编程语言&#xff0c;开启AI编程之路。解决的问题包括&#xff1a;传统学习方式效率低、缺乏互动性以及学习资源质量参差…

重生之我在异世界学编程之C语言:枚举联合篇

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文枚举&#xff08;Enum&#xff0…

MYSQL索引的分类和创建

目录 1、聚簇索引和非聚簇索引 tips&#xff1a; 小问题&#xff1a;主键为什么建议使用自增id? 2、普通索引 &#xff08;常规索引&#xff09;(normal) 3、唯一索引&#xff08;UNIQUE &#xff09; 唯一索引和主键的区别&#xff1a; 唯一约束和唯一索引的区别&#…

Robust Depth Enhancement via Polarization Prompt Fusion Tuning

paper&#xff1a;论文地址 code&#xff1a;github项目地址 今天给大家分享一篇2024CVPR上的文章&#xff0c;文章是用偏振做提示学习&#xff0c;做深度估计的。模型架构图如下 这篇博客不是讲这篇论文的内容&#xff0c;感兴趣的自己去看paper&#xff0c;主要是分享环境&…

.NET 一款获取主机远程桌面端口的工具

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

【开源】A066—基于JavaWeb的农产品直卖平台的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

vue3+vite+ts 使用webrtc-streamer播放海康rtsp监控视频

了解webrtc-streamer webrtc-streamer 是一个使用简单机制通过 WebRTC 流式传输视频捕获设备和 RTSP 源的项目&#xff0c;它内置了一个小型的 HTTP server 来对 WebRTC需要的相关接口提供支持。相对于ffmpegflv.js的方案&#xff0c;延迟降低到了0.4秒左右&#xff0c;画面的…

C语言基础六:循环结构及面试上机题

Day06&#xff1a;循环结构 定义 代码的重复执行&#xff0c;就叫做循环 循环的分类 无限循环:其实就是死循环&#xff0c;程序设计中尽量避免无限循环。程序中的无限循环必须可控。有限循环:循环限定循环次数或者循环的条件。 循环的构成 循环条件循环体 当型循环的实现…