Spring之循环依赖源码解析

2023-11-19

目录

1.什么是循环依赖?

2.为什么会出现循环依赖?

3.面对循环依赖问题,我们该如何思考解决?

4.Spring是怎么解决循环依赖的?

5.总结


1.什么是循环依赖?

有两个类Order、Customer,Order对象依赖了Customer对象,同时Customer对象也依赖了Order对象,这就构成了循环依赖;

// Order依赖了Customer 
public class Order{
  private Customer customer;
}

// Customer依赖了Order
public class Customer{
  private Order order;
}

如果我们自己去创建对象,不考虑Spring的话,对象之间相互依赖也并没有什么问题;但是,在Spring中循环依赖就是一个问题了, 在Spring中,一个对象并不是简单的new出来了,而是会经过一系列的Bean的生命周期,就是因为有Bean的生命周期,所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员自己来解决。

2.为什么会出现循环依赖?

在Spring中,从实例化Bean对象到初始化完成,大致要经历三步(事实上Spring创建Bean有很多步骤),依赖注入就是在属性赋值这一步中完成的,实例化bean之后,Spring需要给对象中的属性进行赋值(依赖注入),那么Spring中循环依赖的问题是怎么出现的呢?

@Component
public class ServiceA{
  @Autowired
  private ServiceB b; // ServiceA依赖了ServiceB
}


@Component
public class ServiceB{
  @Autowired
  private ServiceA a; // ServiceB依赖了ServiceA
}

比如上面的ServiceA类,ServiceA类中存在一个ServiceB类的属性,属性名为b,当ServiceA类创建出来一个bean实例之后,就要给b属性去赋值,此时就会根据b属性的类型和属性名去BeanFactory中去获取ServiceB类所对应的bean,如果此时BeanFactory中存在ServiceB类对应的bean,就直接获取并赋值给b属性,如果此时BeanFactory中不存在ServiceB对应的bean,则需创建一个ServiceB对应的bean实例,然后赋值给b属性;这个过程会存在一个问题:如果此时ServiceB类在BeanFactory中还没有生成对应的bean,则会触发ServiceB类bean的创建,就会经过ServiceB创建bean的生命周期, 在创建ServiceB的bean的过程中,如果此时ServiceB中也存在一个ServiceA类的a属性,那么在创建ServiceB的bean的过程中就需要ServiceA类对应的bean,但是,触发ServiceB类bean创建的条件是ServiceA类bean在创建过程中的依赖注入,所以,这里就出现了循环依赖,这就是循环依赖的场景;在Spring中,通过某些机制可以帮助开发者解决部分循环依赖的问题,这个机制就是三级缓存。

3.面对循环依赖问题,我们该如何思考解决?

3.1. 在ServiceA、ServiceB之间增加缓存来打破循环

想要打破ServiceA和ServiceB之间的这种循环状态,那就不能让双方在创建bean实例时都触发对方bean的创建,否则循环依赖问题依然得不到解决;那么怎么做呢?可以通过增加缓存来实现,具体做法如下:

ServiceA创建bean的过程中,在进行依赖注入之前,先把实例化出来的ServiceA的原始对象(这里的原始对象是指没有经过属性赋值,或只经过了部分属性赋值的bean)放入缓存(提前暴露,只要放到缓存中,其它bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,而ServiceA依赖了ServiceB,所以,接下来要给ServiceA找一个ServiceB类型的bean,赋值给ServiceA的b属性;如果ServiceB的bean不存在,则会触发ServiceB的bean的创建,经过ServiceB创建bean的生命周期,而创建ServiceB的bean的过程和ServiceA一样,也是先创建一个ServiceB的原始对象,然后把ServiceB的原始对象提前暴露出来放入缓存中,接着再对ServicrB的原始对象进行依赖注入,而ServicrB也依赖了ServiceA,此时能从缓存中拿到ServiceA的原始对象(这里只是ServiceA的原始对象,还不是经过完整生命周期的bean)赋值给ServiceB的a属性,ServiceB就可以正常的完成依赖注入,这样ServiceA和ServiceB都能完成正常bean的创建,就不会陷入循环中。

问题:ServiceB在进行依赖注入时,赋值给ServiceB属性的是ServiceA的原始对象,并不是经过完整生命周期的对象,这样赋值正确吗?

答:在整个bean创建过程中,都只有一个ServiceA对象,所以对于ServiceB而言,即使在属性注入时,注入的是ServiceA原始对象,也没有影响,因为这里赋的是ServiceA在内存中的地址值,ServiceA原始对象在后续的生命周期流程中在堆中的没有发生变化,当ServiceA经过了完整生命周期后,前面赋值给ServiceB属性的ServiceA对象,也就变成了具备完整生命周期的对象。

3.2.要解决循环依赖,还需要考虑AOP
1) 基于上面的场景,不知道有没有发现一个问题?

从缓存中取出ServiceA的原始对象注入给ServiceB的a属性之后,待ServiceB完成创建后,回到ServiceA创建bean的流程中,在ServiceA后面的生命周期步骤中,当执行到初始化后这一步,ServiceA的原始对象进行了AOP,生成了一个代理对象,对于ServiceA来说,最终生成的bean对象应该是进行了AOP之后的代理对象,而赋值给ServiceB的a属性的bean对象,不是ServiceA进行AOP之后的代理对象,而是原始对象;所以,如果仅仅是基于上面<3.1. 在ServiceA、ServiceB之间增加缓存来打破循环>来解决的话,就会出现这样的问题;

2) 怎么解决ServiceB依赖的ServiceA对象和最终的ServiceA对象不是同一个对象?

要解决这个问题,就得提前进行AOP,如果要解决循环依赖,但依然是在初始化后进行AOP,那么赋值给ServiceB的a属性的那个对象就不是代理对象;按照正常的bean的生命周期流程,AOP是在初始化后进行的,但我们要考虑循环依赖的场景,就需要提前进行AOP,当然,这与正常的创建bean的生命周期流程不符,因为,正常bean的创建是在初始化后这一步进行AOP的,所以,也只有在出现循环依赖的情况下(例如ServiceA出现了循环依赖),才需要提前进行AOP,最后将进行过AOP步骤的对象缓存起来;

3.3.增加三级缓存

基于<3.2.要解决循环依赖,还需要考虑AOP>,还存在这样的问题:

1)出现循环依赖是怎么判断的?

增加集合来记录(Spring中是singletonsCurrentlyInCreation);当ServiceA正在创建时,将该bean对应的beanName存到集合中,当ServiceB创建需要用到ServiceA时,但发现ServiceA仍在创建中,那么就可以断定ServiceA出现了循环依赖;

2)怎么判断出现了循环依赖的bean是否要进行AOP?

基于BeanPostProcessor机制,BeanPostProcessor会根据传入的bean去判断是否要进行AOP,如果需要进行AOP就生成代理对象并返回,否则就把传入的原始bean对象返回;在这里就要增加三级缓存了,增加一个Map,key是beanName,value是判断是否要进行AOP的Lambda表达式;

4.Spring是怎么解决循环依赖的?

4.1.Spring 通过三级缓存解决了循环依赖

/** Cache of singleton objects: bean name to bean instance. */
// 单例池
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
//三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
//二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

一级缓存 : Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(依赖注入)、初始化完成的 bean实例(经过完整的生命周期的bean对象);

二级缓存 : Map<String,Object> earlySingletonObjects,早期"曝光"对象,用于保存实例化完成的bean实例(暂时还没有经过完整的生命周期的bean对象);

三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,早期"曝光"对象工厂,用于保存 bean 创建工厂,以便于后面扩展有可能创建代理对象,二级缓存中存储的就是从这个工厂中获取到的对象。

 4.2.Spring解决循环依赖源码分析

1) 向三级缓存添加

在创建bean的过程中,当执行createBeanInstance()方法创建完原始的bean对象,之后调用populateBean方法进行属性赋值(依赖注入),但是在属性赋值之前,Spring会判断当前正在创建的bean是否支持循环依赖,如果支持,就会添加到三级缓存(singletonFactories),singletonFactories中存的是某个beanName对应的ObjectFactory,这个ObjectFactory 是一个函数式接口,所以支持Lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean),就是一个ObjectFactory,执行该Lambda表达式就会去执行getEarlyBeanReference方法,注意这里只会往三级缓存添加,并不会执行该Lambda表达式,具体调用过程是在后面调用ObjectFactory的getObject方法时调用,具体源码如下:

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
//如果当前bean支持循环依赖就会提前往三级缓存里存
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");
    }
  // 为了解决循环依赖提前缓存单例创建工厂
  // 循环依赖-添加到三级缓存
  // 执行到这里的时候,Spring并不知道当前正在创建的bean会不会出现循环依赖,先缓存起来,目的是后面 
  // 判断出现循环依赖的时候使用
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, 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)) {
			this.singletonFactories.put(beanName, singletonFactory);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

2)Lambda表达式中的getEarlyBeanReference方法分析

getEarlyBeanReference方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法, 在整个Spring中,默认只有AbstractAutoProxyCreator类实现了SmartInstantiationAwareBeanPostProcessor接口中getEarlyBeanReference方法,而该类就是用来进行AOP的,AOP是通过一个BeanPostProcessor来实现的,开启AOP之后,Spring容器中就会增加一个BeanPostProcessor,这个BeanPostProcessor就是 AnnotationAwareAspectJAutoProxyCreator,它的父类是AbstractAutoProxyCreator,而在 Spring中AOP利用的是JDK的动态代理,或者是CGLib的动态代理,如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象;

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

3) 什么时候执行Lambda表达式,并执行getEarlyBeanReference方法?

 当判断出现循环依赖时,执行ObjectFactory的getObject()方法,就会执行getEarlyBeanReference方法,循环依赖是通过getSingleton方法中的isSingletonCurrentlyInCreation()来判断的;

/**
 * Return the (raw) singleton object registered under the given name.
 * <p>Checks already instantiated singletons and also allows for an early
 * reference to a currently created singleton (resolving a circular reference).
 * @param beanName the name of the bean to look for
 * @param allowEarlyReference whether early references should be created or not
 * @return the registered singleton object, or {@code null} if none found
 */
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   // Quick check for existing instance without full singleton lock
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
	   singletonObject = this.earlySingletonObjects.get(beanName);
	   if (singletonObject == null && allowEarlyReference) {
		    synchronized (this.singletonObjects) {
			    // Consistent creation of early reference within full singleton lock
				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;
}

4) AbstractAutoProxyCreator的getEarlyBeanReference方法到底做了些什么?

首先得到一个cachekey,cachekey就是beanName,然后把beanName和bean(这是原始对象)存入earlyProxyReferences中,调用 wrapIfNecessary方法, wrapIfNecessary方法是用来进行AOP的,wrapIfNecessary方法中会判断当前bean是否要进行AOP,如果要则要生成一个代理对象,否则还是返回原始对象,而Spring用于解决循环依赖的getEarlyBeanReference方法和Bean的生命周期初始化后的postProcessAfterInitialization方法中都会调用wrapIfNecessary方法;

// AbstractAutoProxyCreator
// 提前进行AOP
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   //记录当前的bean进行了AOP
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}
//正常进行AOP的地方
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
        //判断当前bean有没有提前进行AOP
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

说明:只有出现了循环依赖,Spring才需要将AOP提前,才会执行getEarlyBeanReference方法,调用wrapIfNecessary方法,但是,不管有没有出现循环依赖都会执行初始化后的postProcessAfterInitialization方法,进而调用其中的wrapIfNecessary方法;earlyProxyReferences的作用是在Spring出现了循环依赖的情况下,记录提前进行了AOP的bean,避免在进行初始化后这一步时,重复进行AOP,生成新的代理对象(wrapIfNecessary方法会判断当前bean是否要进行AOP,如果需要则生成代理对象,如果不需要还是返回原始对象,所以上述所说的提前进行AOP,最终返回的对象并不一定是代理对象)。

5) 如果提前进行AOP,在初始化后这一步就不会再进行AOP,返回的对象就不是代理对象而是原始对象,由于最终要把代理对象放到单例池,这个对象是从哪里获取的?

从二级缓存中获取

 //说明:当某个正在创建的bean出现了循环依赖,同时该bean也要进行AOP,那么就会提前进行AOP,一但提前进行了AOP,就会生成代理对象,
		//那么在初始化后这一步就不会再进行AOP,返回的对象就不是代理对象而是原始对象,由于最终要把代理对象放到单例池,所以,这里还要去二级缓存去获取
		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
					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 new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
对于上面 exposedObject == bean 的判断,大多数情况下是相同的,但是也有某些场景会不同,比如Spring的异步调用,此时List<BeanPostProcessor> beanPostProcessors中会增加一个AsyncAnnotationBeanPostProcessor,经过AsyncAnnotationBeanPostProcessor处理的bean就会生成新的代理对象;

4.3.为什么要有二级缓存和三级缓存?

只有二级缓存确实可以解决循环依赖的问题,但有一个前提:这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用二级缓存(这里的二级缓存是指singletonFactories,缓存的是Lambda表达式)是无法解决问题,这样会导致每次执行singleFactory.getObject()方法都产生一个新的代理对象,无法保证对象的单例性,所以还要借助另外一个缓存来保存产生的代理对象;如果出现循环依赖+AOP时,多个地方注入这个动态代理对象需要保证都是同一个对象,而三级缓存中的取出来的动态代理对象每次都是新对象,地址值不一样。

三级缓存的作用是为了解决Spring中Bean在进行依赖注入时发生的循环依赖。如果不需要AOP,那么只需要二级缓存即可实现,如果有AOP,其实二级缓存也能够实现,但是会打破Bean的生命周期,这不符合Spring的设计原则,由于需要把AOP对象放入二级缓存中,那么就必须在所有需要AOP处理的Bean对象初始化之前就对Bean对象进行后置处理(生成AOP对象),即使没有出现循环依赖,这并不是Spring想看到的,所以Spring引入了三级缓存,而且存入的是结构,ObjectFactory是一个Lambda表达式,相当于一个回调函数,当出现循环依赖的时候,会执行Lambda表达式,获取到Bean对象或者 AOP代理对象,再将Bean对象或者 AOP代理对象存入二级缓存中,如果之后还有循环依赖指向该对象(类似 A 依赖 B , B 依赖 A , C 依赖 A这种情况),就直接从二级缓存里面获取,从而解决了循环依赖。(为什么不直接在二级缓存里存放Lambda表达式?因为同一个Lambda表达式每执行一次,就会生成一个新的代理对象,不能保证单例)。

4.4.Spring有没有解决singleton下的构造注入产生的循环依赖?

@Component
public class ServiceA {

  private ServiceB serviceB;

  public ServiceA(ServiceB serviceB) {
	this.serviceB = serviceB;
  }

  public void test() {
	System.out.println(serviceB);
  }

}


@Component
public class ServiceB {

  private ServiceA serviceA;

  public ServiceB(ServiceA serviceA) {
	this.serviceA = serviceA;
  }

  public void test() {
	System.out.println(serviceA);
  }

}


基于构造注入的方式下产生的循环依赖也是无法解决的,原因是ServiceA通过构造方法创建对象时,无法创建ServiceA所依赖的ServiceB对象,获取所依赖bean对象的引用。
创建bean对象,执行顺序为:1.调用构造方法  2. 放到三级缓存  3. 属性赋值

调用构造方法时会触发所依赖的bean对象的创建,createBeanInstance是调用构造方法创建bean对象,在里面会注入构造方法中所依赖的bean,而此时并没有执行到addSingletonFactory方法来添加主bean对象的创建工厂到三级缓存singletonFactories中。

4.5.Spring有没有解决多例下的set注入产生的循环依赖?
多实例并不会调用getSingleton方法,自然也不会缓存。也就是说,无论多少次创建多实例bean都不会调用beforeSingletonCreation;

注意:当两个bean的scope都是prototype的时候,才会出现异常,如果其中一个是singleton,就不会出现。

4.6.Spring解决循环依赖的机理

Spring为什么可以解决set + singleton模式下循环依赖?根本原因在于:该方式可以做到将"实例化Bean"和"给Bean属性赋值"这两个动作分开去完成,实例化Bean的时候:调用无参数构造方法来完成,此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界,给Bean属性赋值的时候,调用setter方法来完成。两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值,这样就解决了循环依赖的问题。

5.总结

总结一下三级缓存:

1. singletonObjects:缓存经过了完整生命周期的bean

2. earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖, 就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要进行AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入 earlySingletonObjects,但是不管怎么样,即使是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,所以放入earlySingletonObjects中的可以统一认为是未经过完整生命周期的bean。

3. singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean 的创建过程中,经过实例化得到一个原始对象后,都会提前基于原始对象构造一个Lambda表达式,并保存到三级缓存中,如果当前Bean没有出现循环依赖,那么这个Lambda表达式不会被执行,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖 (当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行 Lambda表达式得到一个对象,并把得到的对象放入二级缓存(如果当前Bean需要AOP,那么执行Lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring之循环依赖源码解析 的相关文章

随机推荐

  • Hands-On Hyperledger Fabric——Fabric模块命令之cryptogen

    文章目录 Fabric 核心模块功能 Fabric模块的子命令和配置文件 cryptogen 生成配置自己的证书 cryptogen 证书文件结构 Fabric 核心模块功能 再来复习Fabric的模块组成 peer 主节点模块 负责存储区
  • IntelliJ IDEA扩展_使用Run Dashboard运行

    为什么要使用Run Dashboard 在微服务项目下 服务之前可能出现依赖 需要启动多个服务 服务比较多的情况 在Run导航栏下启动的窗口就比较多 不直观找到并查看服务的运行情况 这个时候Run Dashboard就能更好的运行和查看每个
  • 求两个整数之和

    链接 https www nowcoder com questionTerminal 59ac416b4b944300b617d4f7f111b215 来源 牛客网 题目描述 写一个函数 求两个整数之和 要求在函数体内不得使用 四则运算符号
  • 完美解决UnicodeEncodeError: ‘gbk‘ codec can‘t encode character ‘\xa0‘ in position XX: illegal multiby...

    目录 项目场景 问题描述 原因分析 解决方案 replace函数 其他解决方法 来源网络 使用re sub 使用translate方法 利用split方法 使用unicodedata模块 项目场景 从网上抓了一些小说的数据 但实际上网页是g
  • 小梅哥Xilinx FPGA学习笔记9——语法(阻塞与非阻塞赋值)

    阻塞赋值与非阻塞赋值详解 注意 阻塞赋值 1 设计文件 2 激励文件 3 原理图 4 仿真图 非阻塞赋值 1 设计文件 2 激励文件 3 原理图 4 仿真图 注意 阻塞赋值与非阻塞赋值 只有在时序逻辑中才有 不是阻塞赋值 也不是非阻塞赋值
  • 在uni-app中,如果data中的对象属性改变了,但是页面没有相应更新的情况,通常有以下几点需要注意:

    1 使用this set更新对象属性直接修改对象属性是无法触发页面更新的 需要使用this set方法 this set this obj key value 2 确保数据层级不太深如果对象层级过深 改变内层属性也可能无法触发更新 建议关键
  • SQL研习录(23)——默认值约束(DEFAULT)

    SQL研习录 23 默认值约束 DEFAULT 版权声明 一 DEFAULT约束 1 基本语法 版权声明 本文原创作者 清风不渡 博客地址 https blog csdn net WXKKang 一 DEFAULT约束 DEFAULT 约束
  • Git项目中分支的提交转移到另一个分支

    项目场景 提示 项目中经常用到需要将这个分支的提交代码转移到另一个分支的情况 解决方案 git cherry pick
  • KITTI数据集--参数

    一 kitti数据集 label解析 16个数代表的含义 第1个字符串 代表物体类别 Car Van Truck Pedestrian Person sitting Cyclist Tram Misc or DontCare 注意 Dont
  • 在vue中使用typescript

    在Vue中使用typescript 注 本文为翻译文章 非原创 文章原文链接在末尾附上 因为最近项目中需要在vue中使用typescript 查了很多资料 大部分都是一笔带过 没有特详细的资料 直到朋友发了这个链接给我 简直是宝藏文章啊 本
  • surface 其实是UEFI与BIOS并存,借用官网的进入方法(少有更改)

    surface 其实是UEFI与BIOS并存 借用官网的进入方法 少有更改 第一种 1 Swipe in from the right edge of the screen and then tap Settings 从右向左滑动屏幕 选择
  • [BDSec CTF 2022] 部分WP

    组队参加了个国外的小线上赛 题目比较简单 目录 PWN pwnrace Reverse BDSec License Checker 0x1 shashdot Flag Box Simple Math Poster BDSec License
  • 电路及esd防护

    摘自网络 1 并联放电器件 常用的放电器件有TVS 齐纳二极管 压敏电阻 气体放电管等 如图 1 1 齐纳二极管 Zener Diodes 也称稳压二极管 利用齐纳二极管的反向击穿特性可以保护 ESD敏感器件 但是齐纳二极管通常有几十 pF
  • 2023最新华为OD机试,独家整理总结上岸技巧,答读者问华为OD 华为OD机试备考攻略

    文章目录 华为OD在线刷题OJ 华为OD统一考试A卷 B卷 新题库说明 什么是华为OD 华为 OD 应聘流程 华为 od 机试 机试 分数 院校问题 华为 OD 机试 二本院校有机会吗 华为 OD 机试 跨专业可以参加华为OD 华为 OD
  • 新手程序媛要怎么做

    作为一名新手程序媛 之前一直很茫然 不知道该从哪里入手 上班了两个月才想起来可以百度下 总结了一下 想要成为一名大牛 要做到几点 一 知识和技能 程序员的能力就是编程水平 没有足够的编程知识都不好意思说自己是程序员 1 程序员是个青春饭 体
  • HTTPS 之fiddler抓包--jmeter请求

    一 浅谈HTTPS 我们都知道HTTP并非是安全传输 在HTTPS基础上使用SSL协议进行加密构成的HTTPS协议是相对安全的 目前越来越多的企业选择使用HTTPS协议与用户进行通信 如百度 谷歌等 HTTPS在传输数据之前需要客户端 浏览
  • frida客户端安装.txt

    frida客户端安装 Linux安装 https www eolink com news post 32270 html window安装 https www modb pro db 124929
  • NetworkManager network-manager 的 /etc/NetworkManager/system-connections/*.nmconnection

    用 gnome 图形界面的 Linux 一般都用 NetworkManager 管理网络 NetworkManager就是network manager systemctl status NetworkManager 在Debian10中
  • 【教程】详解如何将云服务器从一个平台转移到腾讯云服务器

    转载请注明出处 小锋学长生活大爆炸 xfxuezhang cn 背景介绍 我现在有一台华为云服务器 但是快到期了 考虑到腾讯云服务器比较便宜 可以看这里 特惠产品合集页 因此想转过来 但华为云上东西 环境都存满了 如果重新搭建 那未免太麻烦
  • Spring之循环依赖源码解析

    目录 1 什么是循环依赖 2 为什么会出现循环依赖 3 面对循环依赖问题 我们该如何思考解决 4 Spring是怎么解决循环依赖的 5 总结 1 什么是循环依赖 有两个类Order Customer Order对象依赖了Customer对象