Spring大白话--三级缓存解决循环依赖问题

news2025/1/21 0:45:57

文章目录

  • 前言
  • 一、Spring 循环依赖是什么?
  • 二、Spring 三级缓存解决单例的循环依赖:
    • 2.1 Bean 单例对象生成的过程:
    • 2.2 三级缓存工作过程:
  • 三、Spring 三级缓存无法解决的单例循环依赖情况:
    • 3.1 通过构造方法注入的bean ,出现循环依赖会报错:
    • 3.2 早期暴露的非aop代理对象引用,出现循环依赖会报错:
  • 四、Lazy 注解解决循环依赖问题:
  • 总结:
  • 参考:


前言

在使用Spring 开发过程中,我们需要对定义后的bean 通过构造方法,或者bean 注入的方式注入到某个类中进而使用改bean 对应的方法,在此过程中就会出现一个类中注入了n多个bean,几个类中bean 相互注入,出现 A 依赖B,B依赖C,C又依赖A/B 这种循环情况的出现。


提示:以下是本篇文章正文内容,下面案例可供参考

一、Spring 循环依赖是什么?

循环依赖是指在Spring容器中,存在两个或多个Bean之间的互相依赖关系,形成了一个闭环的依赖链。具体来说,当Bean A依赖Bean B,同时Bean B又依赖Bean A时,就产生了循环依赖。
循环依赖可能会导致以下问题:

  • 死锁:如果循环依赖的解析过程不正确,可能会导致死锁。当容器无法确定如何先实例化哪个Bean时,可能会造成死锁情况,导致应用程序无法继续正常执行。
    在这里插入图片描述

  • 未完全初始化的Bean:在解决循环依赖时,Spring使用了代理对象来解决依赖问题。这意味着当Bean A注入到Bean B中时,A可能是一个未完全初始化的代理对象,而不是完全实例化的对象。这可能导致在早期阶段的Bean存在一些限制和潜在的问题。

为了解决循环依赖的问题,Spring使用了一个两阶段的解析过程:实例化阶段和注入阶段。在实例化阶段,Spring创建对象并将其放入缓存中;在注入阶段,Spring解决依赖关系并完成注入。

SpringBoot 从 2.6 之前默认开启循环依赖,之后 开始默认不允许出现 Bean 循环引用,如果需要则进行手动开启:

spring:
  main:
    allow-circular-references:true

二、Spring 三级缓存解决单例的循环依赖:

Spring 三级缓存 实际上使用了3个Map 来打破单例对象循环依赖的问题,singletonObjects:这是一级缓存,保存已经实例化且完成了所有的依赖注入和初始化的单例bean实例。earlySingletonObjects:这是二级缓存,保存已经实例化但尚未完成所有的依赖注入和初始化的单例bean实例。它主要用于解决属性注入时的循环依赖问题。singletonFactories:这是三级缓存,保存创建单例bean实例的ObjectFactory。当创建bean时,Spring首先会尝试从singletonFactories中获取bean实例的ObjectFactory,用于创建bean实例。一旦bean创建完成,会将其从singletonFactories中移除。

2.1 Bean 单例对象生成的过程:

对象的生成必须先通过其构造方法进行实例化,然后对其属性赋值完成初始化;以下以 Aservice ,Bservice,Cservice 为例进行研究;

public interface Aservice {
}
public interface Bservice {
}
public interface Cservice {
}
@Service
public class AserviceImpl implements Aservice {
    @Autowired
    private Bservice bservice;

}
@Service
public class BserviceImpl implements Bservice {

    @Autowired
    private Aservice aservice;

}
@Service
public class CserviceImpl implements Cservice {
    @Autowired
    private Aservice aservice;
    @Autowired
    private Bservice bservice;
}

其中Aservice 依赖Bservice 的bean ,Bservice 依赖Aservice 的bean,Cservice 依赖Aservice ,Bservice 的bean ;

在这里插入图片描述

  • 首先 A 实例化,通过A 类的构造方法进行 实例的构建并返回; 对bservice 进行 属性值进行设置;
  • 此时需要先从单例池中去获取,Bservice 的bean ,Bservice 的bean 还没有被创建,所以此时需要先创建Bservice 的bean ;
  • 调用Bservice 的构造方法进行实例的创建返回,然后 对Aservice 进行 属性值进行设置;

然后在从单例池中获取Aservice 的bean,发现没有Aservice 的bean ,这个时候如果重复在走Aservice 的bean 创建过程就会陷入死循环,显然我们的项目此时是可以成功启动的,也即没有陷入死循环中,那么Spring 是怎么解决的?

如果说在创建Aservice 的bean时是分为两步:

  • 步骤1: 先通过其构造方法完成实例化;
  • 步骤2: 对其属性进行填充;

那么如果我们在步骤1 之后 ,就将还没有完成初始化的Aservice 的bean 放入到某个地方,然后在初始化其他bean 的时候 如果发现依赖了Aservice 的bean 此时可以直接注入Aservice 的bean完成对其的引用,即使Aservice 的bean还没有进行完整的初始化,我们进行了提前暴露,这样在Aservice 的bean真正完成初始化之后,对Aservice 的bean引用也随即完成;这样就打破了bean 的循环依赖,bean 可以正常初始化了;
现在Bservice,Cservice 中都依赖了Aservice 的bean ,显然无法对Aservice 的单例bean 实例化两次 ,那么就需要有个地方来存放Aservice 的这个还没有完全初始化的bean,这样后续其它的bean 在注入Aservice 的bean 时 会发现 Aservice 的bean 已经有了,所以就可以直接使用,不需要在额外创建,这里spring 使用 map (二级缓存)来存放已经实例化,但是还没有完全初始化的 bean , 以便于在发生循环依赖时,如果从单例池中获取不到对应的bean 就到二级缓存中在获取一次,如果获取到了可以直接使用,如果获取不到则需要去生成这个bean 并将其放入到二级缓存中;
在这里插入图片描述
因为Bservice 中已经将Aservice的bean 放入到了二级缓存中,所以Cservice 可以直接从二级缓存中获取到service 的单例bean ;
到此看起来spring 已经通过二级缓存来提前暴露未初始化完成的bean 而解决了循环依赖,那么为什么还有三级缓存的概念?
在原码中可以看到三级缓存也是一个map ,并且其value 存的是一个对象的工厂

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

我们可能已经听说过三级缓存放入的是Lambda表达式 的匿名函数,这个函数会在使用到的时候被调用,那么spring 为什么选择放一个匿名函数而不是直接放入一个bean 呢;显然如果直接放入一个bean 那么三级缓存的作用就和二级缓存相同了;所以spring 这样做肯定是有一些原因的,先来看下在原码中三级缓存放入的Lambda是什么:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    SmartInstantiationAwareBeanPostProcessor bp;
    if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
        for(Iterator var5 = this.getBeanPostProcessorCache().smartInstantiationAware.iterator(); var5.hasNext(); exposedObject = bp.getEarlyBeanReference(exposedObject, beanName)) {
            bp = (SmartInstantiationAwareBeanPostProcessor)var5.next();
        }
    }

    return exposedObject;
}

从以上代码可以执行先把初始的 bean 对象赋给了 exposedObject ,然后如果发现这个bean 是否满足mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()条件,进而判断当前的bean是否是一个合成的代理对象。在AOP中,合成代理对象是通过特定的机制(如JDK动态代理或CGLIB动态代理)创建的。这个条件可以用来检查当前创建的bean是否是这种合成的代理对象

如果需要合成代理对象则 进入exposedObject = bp.getEarlyBeanReference(exposedObject, beanName) 进行代理对象的创建:
AbstractAutoProxyCreator.getEarlyBeanReference

   // 省略代码
public Object getEarlyBeanReference(Object bean, String beanName) {
  Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
     this.earlyProxyReferences.put(cacheKey, bean);
     return this.wrapIfNecessary(bean, beanName, cacheKey);
 }
Object exposedObject = bean;

 try {
 	// 递归填充改bean 的其他属性
     this.populateBean(beanName, mbd, instanceWrapper);
     exposedObject = this.initializeBean(beanName, exposedObject, mbd);
 } catch (Throwable var18) {
     if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
         throw (BeanCreationException)var18;
     }

     throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
 }
   // 省略代码
 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
         return bean;
     } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
         return bean;
     } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
         Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
         if (specificInterceptors != DO_NOT_PROXY) {
             this.advisedBeans.put(cacheKey, Boolean.TRUE);
             Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
             this.proxyTypes.put(cacheKey, proxy.getClass());
             return proxy;
         } else {
             this.advisedBeans.put(cacheKey, Boolean.FALSE);
             return bean;
         }
     } else {
         this.advisedBeans.put(cacheKey, Boolean.FALSE);
         return bean;
     }
 }

从上面代码可以看到 如果这个对象在缓存中没有而且是不能被跳过的 则使用this.createProxy 为其生产代理对象并进行返回;所以使用了这个Lambda表达式目的就是返回一个普通对象的bean 或者代理对象的bean,那么为什么不直接在bean 实例化之后,就直接调用getEarlyBeanReference 方法,这样将生成的普通对象或者代理对象的bean 直接放入到二级缓存中,这样岂不是更为直接,显然这样做也是可以解决spring 的循环依赖的问题,而且在二级缓存中存放的对象就是普通对象或者代理生成的对象。
虽然可以这样做但是违反了spring 对代理对象生成的原则,Spring 的设计原则是尽可能保证普通对象创建完成之后,再生成其 AOP 代理(尽可能延迟代理对象的生成),因为这样做的话,所有代理都提前到了实例化之后,初始化阶段前,显然与尽可能延迟代理对象的生成 原则是违背的。所以在此使用 Lambda表达式 ,在真正需要创建对象bean 的提前引用时,才通过 Lambda表达式 来进行创建 ,来遵循尽可能延迟代理对象的生成 原则

没有依赖,有AOP 这种情况中,我们知道 AOP 代理对象的生成是在成品对象创建完成之后创建的,这也是 Spring 的设计原则,代理对象尽量推迟创建,循环依赖 + AOP 这种情况中, 代理对象的生成提前了,因为必须要保证其 AOP 功能,那么在bean 初始化完成之后,又到了要对改对象进行代理增强的环节,此时spring 又是怎么判断改bean 已经被增强为代理对象,而不需要重新创建代理对象?

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
     if (bean != null) {
         Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
         if (this.earlyProxyReferences.remove(cacheKey) != bean) {
             return this.wrapIfNecessary(bean, beanName, cacheKey);
         }
     }

     return bean;
 }
 public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return this.wrapIfNecessary(bean, beanName, cacheKey);
    }
 

从以上源码中可以看到 在postProcessAfterInitialization bean 被初始化完成之后执行的方法 从this.getCacheKey 获取到的cacheKey ,最后比较两个bean 是否是一个,如果是则说明bean 已经进行过代理,否则则重新执行wrapIfNecessary 生成代理对象

2.2 三级缓存工作过程:

既然在spring 容器中bean 是单例的,那么就不可能存在改bean 的多个对象,也即对bean 的所有引用都指向同一个对象;此时就有一个问题,当一个bean 被依赖注入时,怎么知道这个单例的bean 是否已经被初始化?
所以就需要将已经完成初始化的bean 放入到一个地方中,这个地方要满足如果这个bean 已经被初始化过了,则不需要进行在进行初始化,而且存和取都比较方便,spring 选用map 来对其进行存储,其中key 为bean的那么,value 为单列bean:

  private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

当Spring创建一个单例bean时,会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回。如果没有找到,则继续执行bean的创建流程,并将工厂方法存储在三级缓存(singletonFactories)中
在创建bean的过程中,如果存在循环依赖问题,Spring会先尝试从二级缓存(earlySingletonObjects)中获取bean实例的早期引用,以解决循环依赖。如果早期引用不存在,就会使用三级缓存(singletonFactories)中的工厂方法创建bean实例
一旦bean创建完成,会将其放入一级缓存(singletonObjects),并从二级缓存(earlySingletonObjects)和三级缓存(singletonFactories)中移除
AbstractBeanFactory,doGetBean 获取对应的bean,此处只列举关键代码

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
/**
** 省略代码
**/ 
  // 会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回,此方法调用DefaultSingletonBeanRegistry 下getSingleton 方法
  Object sharedInstance = this.getSingleton(beanName);
  /**
** 省略代码
**/ 
// 如果没有找到,则继续执行bean的创建流程,并将工厂方法存储在三级缓存(singletonFactories)中
 if (mbd.isSingleton()) {
 	// this.getSingleton 通过改bean 的工厂方法创建出来bean 并放入到单例池中
   sharedInstance = this.getSingleton(beanName, () -> {
        try {
        	// 创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonFactories)中 
        	// 此方法调用  AbstractAutowireCapableBeanFactory 下 createBean 方法然后在调用 doCreateBean 方法
            return this.createBean(beanName, mbd, args);
        } catch (BeansException var5) {
            this.destroySingleton(beanName);
            throw var5;
        }
    });
    beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

}

Object sharedInstance = this.getSingleton(beanName) 方法,DefaultSingletonBeanRegistry 下getSingleton, 会先检查一级缓存(singletonObjects),如果在其中找到bean实例,则直接返回:

@Nullable
public Object getSingleton(String beanName) {
     return this.getSingleton(beanName, true);
 }
 @Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存获取完整的bean
  Object singletonObject = this.singletonObjects.get(beanName);
  // 没有获取到 并且 这个bean 正在初始化中
  if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
  	// 从二级缓存获取这个bean 的早期引用
      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) {
                  	// 从三级缓存 获取bena 的 工厂类
                      ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                      if (singletonFactory != null) {
                          singletonObject = singletonFactory.getObject();
                          //  二级缓存中放入
                          this.earlySingletonObjects.put(beanName, singletonObject);
                          // 三级缓存中移除
                          this.singletonFactories.remove(beanName);
                      }
                  }
              }
          }
      }
  }

  return singletonObject;
}

单例池中没有改bean 则进入 sharedInstance = this.getSingleton(beanName, () -> {}) return this.createBean(beanName, mbd, args) 方法通过AbstractAutowireCapableBeanFactory.doCreateBean 创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonFactories)中:

boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
    }
	//  addSingletonFactory 放入到三级缓存
    this.addSingletonFactory(beanName, () -> {
    	// 获取提前暴露出来的bean 对象
        return this.getEarlyBeanReference(beanName, mbd, bean);
    });
}
try {
	// 继续对改bean 中的属性进行初始化
    this.populateBean(beanName, mbd, instanceWrapper);
     exposedObject = this.initializeBean(beanName, exposedObject, mbd);
 } catch (Throwable var18) {
     if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
         throw (BeanCreationException)var18;
     }

     throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
 }

DefaultSingletonBeanRegistry.addSingletonFactory: bean的工厂放入到三级缓存:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
       Assert.notNull(singletonFactory, "Singleton factory must not be null");
       synchronized(this.singletonObjects) {
           if (!this.singletonObjects.containsKey(beanName)) {
           		// 放入3级缓存map
               this.singletonFactories.put(beanName, singletonFactory);
               this.earlySingletonObjects.remove(beanName);
               // 将改bean 的名称放入到正在创建的bean 的set 集合
               this.registeredSingletons.add(beanName);
           }

       }
   }

在对改bean 的工厂放入到三级缓存之后,继续调用 sharedInstance = this.getSingleton(beanName, () -> {}) , this.getSingleton 方法:

DefaultSingletonBeanRegistry.getSingleton 将对应的bean 通过 Lambda表达式 生成对应的bean ,然后最终将改bean 放入到单例池中;

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     Assert.notNull(beanName, "Bean name must not be null");
     synchronized(this.singletonObjects) {
     	//  获取对象锁,然后从单例池中获取bean
         Object singletonObject = this.singletonObjects.get(beanName);
         if (singletonObject == null) {
            // 省略代码

             try {
             	// 获取bean 的工厂
                 singletonObject = singletonFactory.getObject();
                 newSingleton = true;
             } 
  			// 省略代码
             if (newSingleton) {
             // 放入到单例翅中
                 this.addSingleton(beanName, singletonObject);
             }
         }

         return singletonObject;
     }
 }
protected void addSingleton(String beanName, Object singletonObject) {
     synchronized(this.singletonObjects) {
     	// 单例池中放入改bean
         this.singletonObjects.put(beanName, singletonObject);
         // 从三级缓存和二级缓存中移除
         this.singletonFactories.remove(beanName);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
     }
 }

在A 依赖于B,B 依赖于C,C又依赖A 的场景中,当初始化 A 时,会先实例化 B,然后再实例化 C,然后再实例化 A:

  • 开始初始化 A,创建 A 的实例并完成属性注入(如果有);
  • 当初始化 A 过程中发现 A 依赖于 B,Spring 会尝试创建 B 的实例并完成属性注入;
  • 创建 B 的实例时发现 B 还有其他依赖,其中包 C。此时 Spring 会首先尝试创建 C 的实例并完成属性注;
  • 在初始化 C 过程中,发现 C 依赖于 A,此时 A 实例还未创建完成,因此会暂时将 A 的实例设置为一个 Early Reference(早期引用),并将 C 的依赖关系注册到 A 的 Dependent 对象中;
  • 继续初始化 C,并完成属性注入(如果有);
  • 完成 C 的实例化后,继续初始化 B,并完成属性注入(如果有)
  • 在初始化 B 过程中,发现 B 不再依赖其他对象,完成 B 的初始化;
  • 继续初始化 A,在初始化 A 过程中,发现 A 依赖于 B,B 的实例早已创建完成,因此可以直接获取 B 的实例;
  • 完成 A 的实例化,并完成属性注入(如果有)

Spring 使用了缓存机制,确保在递归调用时能够正确地获取到已经创建的对象实例,避免死循环。同时,Spring 也会处理代理对象的生成和使用,以确保 A、B、C 的代理对象在正确的时间被创建和使用。

三、Spring 三级缓存无法解决的单例循环依赖情况:

3.1 通过构造方法注入的bean ,出现循环依赖会报错:

在A类中通过构造方法的方式注入B的bean,在B类中通过构造方法的方式注入A的bean;在此场景中,

  • 在调用A的构造方法进行实例化时,发现依赖的B的bean,需要对B类进行实例化;
  • 调用B类的构造方法进行实例化时,发现依赖的A的bean,此时出现循环依赖;
  • 然后此时需要对A的bean 提前进行引用的暴露;
  • 然而在对A的bean 提前进行引用的暴露,需要用到A 的实例化对象,此时A的实例化对象还没有被创建,则直接报错;

3.2 早期暴露的非aop代理对象引用,出现循环依赖会报错:

@Service
public class AserviceImpl implements Aservice {
    @Autowired
    private Bservice bservice;

    @Async
    public  void test(){
        System.out.println(bservice);
    }

}
@Service
public class BserviceImpl implements Bservice {

    @Autowired
    private Aservice aservice;

}

当使用 @Async 注解标注一个bean 中的方法为异步方法时,Bservice 中注入的Aservice aservice 的bean 与最终生成的Aservice 的bean 不相同而导致报错

  • 对Aservice 进行实例化后,对其bservice 属性进行初始化;
  • 对Bservice 进行实例化,然后对其aservice 属性进行初始化,此时发现循环依赖;
  • 暴露Aservice 的bean 到二级缓存中,因为Aservice 非aop 代理对象 ,所以此时二级缓存中放入的是Aservice 的普通对象;
  • Bservice 的bean 完成初始化;
  • Aservice 对 bservice 属性初始化完成 ,并将Aservice 的bean 放入到一级缓存,并从二级缓存中删除;
  • 对Aservice 的bean 进行代理对象的包装,包装后的bean 与之前放入到一级缓存的bean 两个不是同一个,程序报错;

四、Lazy 注解解决循环依赖问题:

延迟加载可以通过将 bean 的依赖关系运行时进行注入,而不是在初始化阶段。这样,当遇到循环依赖时,Spring 可以先创建需要的 bean 实例,并将其设置为代理对象,而不需要立即解决依赖关系。

@Service
public class AserviceImpl implements Aservice {
    @Autowired
    @Lazy
    private Bservice bservice;

    @Async
    public  void test(){
    }

}

Lazy 延迟加载打破循环依赖; 通过其它途径生成bservice 的lazy 的代理对象,不会去走创建bservice 的代理 对象 然后注入aservice 这套流程。这样创建aservice 的单例对象并放入到单例池中,Bservice 的bean 在实例化后,注入aservice bean 属性就可以从单例池中加载到aservice 的真正的bean ,而不会出现bean 对象不一致的问题。

总结:

spring 通过三级缓存解决单例的循环依赖问题,singletonObjects 用来存放已经初始化完成的bean,earlySingletonObjects 用来存放早期暴露出来的半成品bean 的引用,singletonFactories 用来存放获取早期引用的 Lambda表达式 工厂。

参考:

Spring的三级缓存整理;
从源码角度,带你研究什么是三级缓存;

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

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

相关文章

如何优化网站排名(百度SEO指南与优化布局方法)

百度SEO指南介绍&#xff1a;蘑菇号-www.mooogu.cn 首先&#xff0c;为了提高网站的搜索引擎优化排名&#xff0c;需要遵循百度SEO指南的规则和标准。这包括使用符合规范的网站结构、页面内容的质量和与目标用户相关的关键词。避免使用非法技术和黑帽SEO的方法。 增加百度SEO…

如何快速重置模型原点

1、什么是模型原点&#xff1f; 模型原点是三维建模中的概念&#xff0c;它是指在一个虚拟三维空间中确定的参考点。模型原点通常位于模型的几何中心或基本组件的中心位置。如图所示&#xff1a; 可以看到模型的原点在模型的几何中心 2、模型原点的作用 知道了什么是模型原点&…

AVL Cruise 2020.1 安装教程

文章目录 安装包安装破解 安装包 链接&#xff1a;https://pan.baidu.com/s/1GxbeDj_SyvKFyPeTsstvTQ?pwd6666 提取码&#xff1a;6666 安装 安装文件&#xff1a; 双击setup.exe&#xff1a; 一直netx&#xff0c;中间要修改两次路径&#xff0c;第一次是安装位置&#xf…

常用螺栓标准、规格、用途汇总表

螺栓 1 常用螺栓标准、用途 常用螺栓标准、规格、用途见表1。 表1 常用螺栓标准、规格、用途汇总表 注&#xff1a;1.冷镦工艺生产的小六角头螺栓具有材料利用率高、生产效率高、机械性能高等优点,但由于头部尺寸较小, 不宜用于多次装拆、被联接件强度较低和易锈蚀等场合。 2.…

Mac多协议传输和文件管理工具ForkLift 4

ForkLift 4 for Mac 是一个强大的文件管理工具&#xff0c;具有直观的界面和强大的功能。它提供了一个直观的界面&#xff0c;使用户能够轻松地管理他们的文件和目录&#xff0c;以及进行各种操作&#xff0c;如复制、移动、重命名、删除等。 它还支持多种文件传输协议&#x…

PHP开发框架及特点

PHP有许多开发框架&#xff0c;每个框架都有其独特的特点和用途。以下是一些常见的PHP开发框架以及它们的特点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.Laravel Laravel是一个流行的PHP框架…

踩坑 | vue动态绑定img标签src属性的一系列报错

文章目录 踩坑 | vue项目运行后使用require()图片也不显示问题描述vue中动态设置img的src不生效问题的原因require is not defined 解决办法1&#xff1a;src属性直接传入地址解决办法2 踩坑 | vue项目运行后使用require()图片也不显示 问题描述 在网上查阅之后&#xff0c;发…

攻防世界题目练习——Reverse新手+引导模式

题目目录 1. 6662.Reversing-x64Elf-1003. easyRE14. insanity5. open-source6. game7. Hello, CTF8. re1 1. 666 下载附件 用IDA Pro打开文件&#xff0c;直接看到main入口&#xff0c;反编译查看代码如下&#xff1a; 注&#xff1a;strcmp(a,b)函数当ab时返回值为0。 int …

深度学习|如何确定 CUDA+PyTorch 版本

对于深度学习初学者来说&#xff0c;配置深度学习的环境可能是一大难题&#xff0c;因此本文主要讲解CUDA; cuDNN; Pytorch 三者是什么&#xff0c;以及他们之间的依赖关系。 CUDA CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA开发的用于并行计…

【CMU15-445 Part-12】Query Execution I

Part12-Query Execution I Processing Models Processing Model主要指的是明确如何去执行一个查询计划&#xff08;top 2 bottom or bottom 2 top,operator之间的传递&#xff09;。 Iterator Model (volcano model/pipeline model);每个算子实现一个Next( )&#xff0c;父…

Matlab中clear,close all,clc功能详细说明

背景&#xff1a; 我们在写matlab程序时&#xff0c;首行总是先敲入&#xff1a;clear; close all; clc;&#xff0c;但你真的知道这三句话的具体作用嘛&#xff0c;下面进行详细说明和演示。 一、clear的功能 clear的功能&#xff1a;清理工作区变量&#xff0c;不清理前是…

每日一练 | 华为认证真题练习Day115

1、FEC(Forwarding Equivalence Class)转发等价类&#xff0c;是一组具有某些共性的数据流的集合&#xff1b;FEC可以根据地址进行划分&#xff0c;但是不能根据业务类型、QoS等要素进行划分。 A. 对 B. 错 2、关于OSI参考模型中网络层的功能说法正确的是&#xff1f; A. OS…

Linux部署elk日志监控系统

目录 一、简介 二、部署elasticsearch 2.1 安装jdk11&#xff08;jdk版本>11&#xff09; 2.2 下载安装包 2.3 授权elk用户 2.4 配置elasticsearch.yml 2.5 启动elasticsearch 三、部署logstash 3.1 启动测试 3.2 可能出现的报错 3.3 指定配置文件启动logstash 3.4 安装El…

【基于Qt和OpenCV的多线程图像识别应用】

基于Qt和OpenCV的多线程图像识别应用 前言多线程编程为什么需要多线程Qt如何实现多线程线程间通信 图像识别项目代码项目结构各部分代码 项目演示小结 前言 这是一个简单的小项目&#xff0c;使用Qt和OpenCV构建的多线程图像识别应用程序&#xff0c;旨在识别图像中的人脸并将…

作为产品经理,你是如何分析和管理你的产品需求的?

作为一名产品经理&#xff0c;分析和管理产品需求是非常重要的工作。在产品开发周期中&#xff0c;需求调研、需求分析、需求管理等环节都是非常关键的&#xff0c;因为好的需求管理能够直接影响产品的质量和用户体验。 需求调研 在进行需求调研的过程中&#xff0c;我们首先…

App开发者如何从立项着手,奠定商业化基础,完成0到1转变?

随着移动互联技术的发展&#xff0c;流量即价值的观念深入人心&#xff0c;大量不同细分领域的移动应用进入市场。根据工信部公布数据&#xff0c;2023年上半年&#xff0c;我国国内市场上监测到活跃的APP数量为260万款&#xff08;包括安卓和苹果商店&#xff09;&#xff0c;…

Visual Studio 如何删除多余的空行,仅保留一行空行

1.CtrlH 打开替换窗口&#xff08;注意选择合适的查找范围&#xff09; VS2010: VS2017、VS2022: 2.复制下面正则表达式到上面的选择窗口&#xff1a; VS2010: ^(\s*)$\n\n VS2017: ^(\s*)$\n\n VS2022:^(\s*)$\n 3.下面的替换窗口皆写入 \n VS2010: \n VS2017: \n VS2022: \n …

铁路用热轧钢轨

声明 本文是学习GB-T 2585-2021 铁路用热轧钢轨. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了铁路用钢轨的订货内容、分类、尺寸、外形、质量及允许偏差、技术要求、试验方法、检 验规则、标志及质量证明书。 本标准适用于3…

react import爆红

如上所示&#xff0c;会标红&#xff0c; 解决办法&#xff1a;在vscode内部SHiftCtrlP 输入Reload window, 如上的第一个&#xff0c;选中后回车&#xff0c;标红就没了&#xff0c;非常好用。

版本控制系统:Perforce Helix Core -2023

Perforce Helix Core是领先的版本控制系统&#xff0c;适用于需要加速大规模创新的团队。存储并跟踪您所有数字资产的更改&#xff0c;从源代码到二进制再到IP。连接您的团队&#xff0c;让他们更快地行动&#xff0c;更好地构建。 通过 Perforce 版本控制加速创新 Perforce H…