依赖注入的三种方式和循环依赖的产生

article/2025/9/14 21:08:08

什么是循环依赖

说白了就是对象之间的依赖关系成环
例如A->B,B->C,C->A,并不限于对象的多少,最终成环就是循环依赖,也因此循环依赖的发生可能是十分复杂的。,如果使用属性注入的话,开发过程中甚至很难察觉。

为什么要避免循环依赖

循环依赖会为系统带来很多意想不到的问题,下面我们来简单讨论一下
一、循环依赖会产生多米诺骨牌效应
换句话说就是牵一发而动全身,想象一下平静的湖面落入一颗石子,涟漪会瞬间向周围扩散。
循环依赖形成了一个环状依赖关系, 这个环中的某一点产生不稳定变化,都会导致整个环产生不稳定变化
实际的体验就是

  • 难以为代码编写测试,因为易变导致写的测试也不稳定
  • 难以重构,因为互相依赖,你改动一个自然会影响其他依赖对象
  • 难以维护,你根本不敢想象你的改动会造成什么样的后果

spring希望我们的依赖关系是单向的,这样的方式在一定程度保证了逻辑的清晰利于维护,也利于扩展

一个例子 A,B两个bean相互依赖,那么必然A和B存在其中一种状态,一个bean未完成初始化就被注入到对方属性字段可以被使用,那么可能在 A还没有完成初始化的时候,B就引用并调用了A进行某些操作,可能出现一些意料之外的情况
三、循环依赖会导致内存溢出
一个简单的例子

class A{
public void a(){b.b()
}
}class B{
public void b(){a.a()
}
}

属性注入

通过@Component创建的注入到工厂
通常使用@Autowired或者@Resource注解在属性字段上

@Resource

@Resource是Java自己的注解
@Resource有两个属性是比较重要的,分别是name和type;Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。默认按name进行注入。
如果某个类型有多个,可以通过那么指定
在这里插入图片描述

@Autowired

@AutoWired是spring的注解,Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。@Resource默认按名称方式进行bean匹配,但是Autowired额外支持require属性,是否要求一定注入
@Autowired默认按类型方式进行bean匹配。
使用@AutoWired变量注解方式时,会有黄色波浪线,idea会提示:
在这里插入图片描述

Spring团队建议:“在bean中始终使用基于构造函数的依赖注入。始终对强制依赖项使用断言”。
意思是说,用@Autowired的注入时,尽量用基于构造函数的依赖注入,而不是变量的方式注入。
这就是构造函数方式的依赖注入:
在这里插入图片描述

那么为什么不推荐属性注入?
1、可能会造成NPE,如下:

public class TestController {@Autowiredprivate TestService testService;private String name;public TestController(){this.name= testService.getName();}}

这段代码执行时会报NPE。Java类会先执行构造函数,然后在通过@Autowired注入实例,二构造函数里面需要注入的对象,因此在执行构造函数的时候就会报错。
2、还可能回导致循环依赖,即A里面注入B,B里面又注入A。(构造注入会启动时就会报错及时发现)
注:在代码中发现构造方法中注入了很多依赖,显得很臃肿,对于这个问题,说明类中有太多的责任,违反了类的单一性职责原则,这时候需要考虑使用单一职责原则进行代码重构。

属性注入对循环依赖的处理

首先说明属性注入对于循环依赖是容忍的,正常情况下出现循环依赖并不会报错,程序正常启动运行

bean工厂的三级缓存

默认bean工厂实现为DefaultListableBeanFactory
其有一个父类为DefaultSingletonBeanRegistry
其中有大名鼎鼎的三级缓存

        /*存放已经完成创建的bean  */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** 存放存放生成bean的工厂,生成bean后先放入earlySingletonObjects */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/*存放提前暴露的bean实例,还未完全初始化*/private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

那么这这三个缓存的作用分别是什么.先看bean注册到bean工厂的过程。

一个bean的注入流程

从创建一个bean开始
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
在创建bean的方法中

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

进入
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
首先从singletonObjects中获取bean,获取到了就直接返回,没有额外的操作。,如果没获取到

Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {if (this.singletonsCurrentlyInDestruction) {throw new BeanCreationNotAllowedException(beanName,"Singleton bean creation not allowed while singletons of this factory are in destruction " +"(Do not request a bean from a BeanFactory in a destroy method implementation!)");}if (logger.isDebugEnabled()) {logger.debug("Creating shared instance of singleton bean '" + beanName + "'");}beforeSingletonCreation(beanName);boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {this.suppressedExceptions = new LinkedHashSet<>();}try {singletonObject = singletonFactory.getObject();newSingleton = true;}catch (IllegalStateException ex) {// Has the singleton object implicitly appeared in the meantime ->// if yes, proceed with it since the exception indicates that state.singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {throw ex;}}

那么如果获取不到,先判断singletonsCurrentlyInDestruction,变量名看事如果这个变量正在被销毁,那么抛出异常,继续来到beforeSingletonCreation方法

	/*** Callback before singleton creation.* <p>The default implementation register the singleton as currently in creation.* @param beanName the name of the singleton about to be created* @see #isSingletonCurrentlyInCreation*/protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}}

会将beanName放入到singletonsCurrentlyInCreation中,代表这个bean正在创建中

private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));

这是一个set集合,如果已经在创建,那么会抛出BeanCurrentlyInCreationException
然后继续

try {singletonObject = singletonFactory.getObject();newSingleton = true;}

开始创建bean
进入关键方法org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// Instantiate the bean.BeanWrapper instanceWrapper = null;if (instanceWrapper == null) {//创建一个bean的实例instanceWrapper = createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();

在createBeanInstance中,有多种和实例化方法的方式,通过配置类方法创建,通过构造方法创建。或者就是根据默认的无参构造方法进行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");}addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}

判断earlySingletonExposure 是否提前暴露,是否成立有两个条件

  1. 判断是否允许循环依赖,如果不允许肯定不会提前暴露
  2. 另外还需要判断当前bean是不是处在被创建的过程中,根据singletonsCurrentlyInCreation中是否包含进行判断,再开始创建bean之前,就会将beanName先放进来
public boolean isSingletonCurrentlyInCreation(String beanName) {return this.singletonsCurrentlyInCreation.contains(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));}

添加一个回调到SingletonFactory

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

逻辑是将beanName和回调的ObjectFactory lamda,放入到singletonFactories,并且从earlySingletonObjects中移除,添加到registeredSingletons代表这个bean已经在bean工厂,后续如果通过getBean获取可以获取到。至于怎么获取,后面看
再看getEarlyBeanReference这个方法
其中的逻辑是把bean的引用,经过SmartInstantiationAwareBeanPostProcessor处理,后返回

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

注意这个SmartInstantiationAwareBeanPostProcessor对循环依赖起到重要作用
getEarlyBeanReference
获得提前暴露的bean引用,主要用于解决循环引用的问题。
getEarlyBeanReference:该触发点发生在postProcessAfterInstantiation之后,当有循环依赖的场景,当bean实例化好之后,为了防止有循环依赖,会提前暴露回调方法,用于bean实例化的后置处理。这个方法就是在提前暴露的回调方法中触发。
看下这个方法的实现类
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference

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

看的出来,如果获取提前暴露的bean,那么对于需要进行代理的ben,进行必要的代理。

继续向下

	Object exposedObject = bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {throw (BeanCreationException) ex;}else {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);}}

执行populateBean,这个方法对使用的配置文件,对依赖的bean进行使用getBean获取,并且注入到对应属性。
其实仔细想一下,对某个bean,只要populateBean方法执行完毕,那么这个bean的所有依赖关系都注入完毕,包括循环依赖也被执行完毕。
因此循环依赖的的解决过程从populateBean这个方法开始分析

例如现在假设,当前A依赖了B,B又依赖了A,构成循环
进入populateBean之后,为了注入B会调用getBean(B)
对于B的加载过程于A相同,也会提前暴露,在解析B的依赖时,会去获取调用getBean(A),那么关键来了,因为此时A还没有加载完成,但是已经提前暴露,那么看看这个过程
在org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean中

	// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);

先尝试获取bean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject 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 locksingletonObject = 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;}

分别尝试从singletonObjects和earlySingletonObjects中获取实例
如都获取不到锁住singletonObjects再次读一遍,如果没有其他线程修改,通过singletonFactory生成对象,放入
earlySingletonObjects(提前暴露未完全创建完成的bean)并从singletonFactories中移除
从这里就能看出来
singletonObjects,earlySingletonObjects,singletonFactory分别是一二三级缓存
singletonFactory.getObject()会进入getEarlyBeanReference方法
前面已经说过SmartInstantiationAwareBeanPostProcessor会对需要代理类进行处理,对于无需代理的类 ,直接返回未创建完成的bean的引用即可(这里也是循环依赖的一个坏处,比如B完成了bean 的创建后,调用afterPropertiesSet
等方法,使用到了A,但是此时的A还没初始化完成,会发生一些意想不到的情况

代理的特殊情况

对于需要代理的bean进入org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference

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

会将bean 缓存到到earlyProxyReferences中

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}helloServiceA// Create proxy if we have advice.Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}

对于需要进行代理的bean 进行代理操作,然后返回需要代理的类

那么也就是说对于需要代理的类,注意是由AnnotationAwareAspectJAutoProxyCreator处理的,
AnnotationAwareAspectJAutoProxyCreator是用来处理aop,transactional切面的一个代理生成类,并不所所有的代理增强都由其实现,AnnotationAwareAspectJAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference,因此其具备在提前暴露就能够生成代理类的能力(有了解决循环依赖的能力!)
会在getEarlyBeanReference时就 生成代理类,使用代理类进行提前暴露。
如果不需要SmartInstantiationAwareBeanPostProcessor进行特殊处理,那么直接返回原有的bean就好了。
AnnotationAwareAspectJAutoProxyCreator除了在提前暴露的时候会判断生成代理类之外,完成bean的依赖注入后,还需要执行AnnotationAwareAspectJAutoProxyCreator对当前bean进行判断是否需要进行,aop,事务的增强代理。
在populateBean方法执行完毕之后。代表当前bean所依赖的所有bean都已经被注入到当前的bean实例
继续来到

Object exposedObject = bean;
exposedObject = initializeBean(beanName, exposedObject, mbd);

对bean做进一步加工处理,得到真正暴露的bean(getBean方法的结果)。

	if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),beanName, "Invocation of init method failed", ex);}if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}

其中会执行,前置,后置的beanPostProcess,和InitialBean的afterPropertiesSet方法。

AnnotationAwareAspectJAutoProxyCreator的后置方法中
也会对bean进行代理(并不是所有bean都会提前暴露生成代理,只有循环依赖才会提前暴露)

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

注意!如果earlyProxyReferences已经存在了这个bean,代表该bean已经被提前暴露生成过了代理,那么不再进行重复代理!

判断是否循环依赖

完成initializeBean方法后,继续

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

如果允许循环依赖,先获取earlySingletonReference,就是提前暴露的那个实例(可能时代理,也肯不是代理)
如果时空,那么确定不存在循环依赖,直接返回就好了。
如果不为空,进一步判断

	if (exposedObject == bean) {exposedObject = earlySingletonReference;}

exposedObject是对bean的进一****步加工,如果二者不相等,那么可能带来一个一个严重的问题,那就是提前暴露的是bean。那么别的实例提通过获取bean的提前暴露依赖并注入了的当前bean的,这对于单例模式的bean显然是无法接受的。(这种情况下,会报出循环依赖的错误)
因此spring要求exposedObject和bean要相等,并且相等的情况下要让exposedObject = earlySingletonReference;,因为在候取提前暴露的bean实例时,同样可以更改这个bean。
AnnotationAwareAspectJAutoProxyCreator就是通过在获取提前暴露的bean时生成代理,并且避免initializeBean中代理,这样保证了exposedObject == bean,解决循环依赖的报错。

setter注入属性

  @Autowired@Qualifier("helloServiceA")public void setHelloServiceA(HelloService helloServiceA) {this.helloServiceA = helloServiceA;}

这种形式进行依赖注入于Autowire的流程相同,支持bean循环依赖

方法参数注入

@ Bean注解注入循环依赖的产生

例子

  @Beanpublic HelloService helloServiceA(@Qualifier("helloServiceB") HelloService helloServiceB){HelloServiceA helloServiceA = new HelloServiceA();helloServiceA.setHelloServiceB(helloServiceB);return helloServiceA;}@Beanpublic HelloService helloServiceB(@Qualifier("helloServiceA") HelloService helloServiceA){HelloServiceB helloServiceB = new HelloServiceB();helloServiceB.setHelloServiceA(helloServiceA);return helloServiceB;}

方法创建的bean是不支持循环依赖的
首先创建helloServiceA,在创建bean实例时,不是使用默认构造创建的,而是通过方法创建实例返回

	if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}

其中会对方法的每个入参进行解析

	for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {Object autowiredArgument = resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter, fallback);args.rawArguments[paramIndex] = autowiredArgument;args.arguments[paramIndex] = autowiredArgument;args.preparedArguments[paramIndex] = autowiredArgumentMarker;args.resolveNecessary = true;}

对每个参数进行解析注入
看是如何进行解析的

return this.beanFactory.resolveDependency(new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);

最终在解析的时候调用getBean方法获取bean实例

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)throws BeansException {return beanFactory.getBean(beanName);}

那么又会对依赖的bean进行获取
被依赖helloServiceB,刚好也依赖helloServiceA
**创建helloServiceB的过程与创建helloServiceA的一致,不在重复·。**同样在解析入参时也会调用
getBean(helloServiceA),进行对helloServiceA的获取
那么看下这个doGetBean方法

//首先尝试从缓存中获取
Object sharedInstance = getSingleton(beanName);

前面看过这个getSingleton(String beanName, boolean allowEarlyReference)这个方法,

protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject 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 locksingletonObject = 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;}

如果singletonObjects中为空的话会尝试,获取提前暴露的实例
但是通过方法创建的bean并没有提前暴露这个步骤。因此这里的值就是空的。

		          sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}}

发现又使用了getSingleton获取,不过这是一个重载方法。和上面的getSingleton(beanName);有些不同
getSingleton(String beanName, ObjectFactory<?> singletonFactory)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");synchronized (this.singletonObjects) {······beforeSingletonCreation(beanName);······// 不会尝试获取提前依赖的bean获取不到直接创建singletonObject = singletonFactory.getObject();newSingleton = true;········return singletonObject;}}

发现这里如果singletonObjects不存在该bean,会检查这个bean是否正在创建,如果还处于创建阶段,会抛出循环依赖的异常。

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

ObjectProvider避免强依赖

正常情况下,方法的入参bean要求必须能够在bean工厂找到,否则启动就会报错

 @Beanpublic HelloService helloServiceB(@Qualifier("helloServiceA") ObjectProvider<HelloService> helloServiceA){HelloServiceB helloServiceB = new HelloServiceB();helloServiceB.setHelloServiceA(helloServiceA.getIfAvailable());return helloServiceB;}

入参ObjectProvider不要求获取到bean,当使用调用get方法时会获取bean

 	@Override@Nullablepublic Object getIfAvailable() throws BeansException {if (this.optional) {return createOptionalDependency(this.descriptor, this.beanName);}else {DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {@Overridepublic boolean isRequired() {return false;}};return doResolveDependency(descriptorToUse, this.beanName, null, null);}}

属性注入不会失效

首先要明确的是@Bean注解目的是让用户能够自定义的去创建一个bean实例。不过方法入参bean需要提前进行创建。
创建bean实例发生在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

	instanceWrapper = createBeanInstance(beanName, mbd, args);

@Bean除了创建bean实例与@Component 的方式不同外,其他的处理都是相同的。
如果是使用@Bean 注册类中使用了@Autowired等注解,那么这些注解也不会失效,同样会对对应属性进行注入。
并且在属性注入阶段,存在循环依赖依然可以借助提前暴露解决

构造参数注入

形式

    public HelloServiceB(Qualifier("helloServiceA") HelloService helloServiceA) {this.helloServiceA = helloServiceA;}public HelloServiceA(Qualifier("helloServiceB")HelloService helloServiceB) {this.helloServiceB = helloServiceB;}

注意。如果有多个构造函数,默认使用无参的构造函数创建实例
指定构造函数的话,加上@Autowired

 @Autowiredpublic HelloServiceA(HelloService helloServiceB) {this.helloServiceB = helloServiceB;}

循环依赖的产生

构造和使用@Bean方法产生的原因基本相同,同样需要解析入参的同样是在执行构造函数之前需要解析入参,

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

同样也会对依赖的bean进行获取/创建,同样也是没有提前暴露的过程,产生循环依赖会报错。与方法暴露逻辑相同,不再重复看这个流程了。

构造注入如何避免强依赖

测试@Autowired(required = false)

@Autowired(required = false)public HelloServiceA(@Qualifier("helloServiceB") HelloService helloServiceB) {this.helloServiceB = helloServiceB;}

实测不生效
那么使用ObjectProvider呢

    @Autowired(required = false)public HelloServiceA(@Qualifier("helloServiceB") ObjectProvider<HelloService> helloServiceB) {this.helloServiceB = helloServiceB.getIfAvailable();}

实测可行

属性注入不会失效

与@Bean的原因相同


http://chatgpt.dhexx.cn/article/9eSDfKAF.shtml

相关文章

[Spring] IoC的理解及三种依赖注入方式

[Spring] IoC的理解及三种依赖注入方式 Spring---IoC的理解及三种依赖注入方式IoC是什么意思依赖控制反转 Spring提供的依赖注入的三种方式setter注入&#xff08;属性注入&#xff09;构造器注入p命名空间注入&#xff08;工厂方法注入&#xff09; Autowired Spring—IoC的理…

Spring依赖注入的三种方式

目录 一、变量注入&#xff08;Field Injection&#xff09; 二、构造器注入&#xff08;Constructor Injection&#xff09; 三、setter方法注入 &#xff08;Setter Injection&#xff09; 四、使用场景 Spring的依赖注入&#xff0c;我们一般使用Autowired注解来完成&am…

依赖注入的三种方式

DI(依赖注入) 注入的三种方法&#xff1a;构造器方法注入&#xff0c;set注入&#xff0c;基于注解的注入&#xff08;接口注入&#xff09; 1&#xff1a;构造器方法注入 创建一个Address类&#xff1b; public class Address {private String address;public Address() {}pu…

Pubmedy的使用教程

使用方法如下&#xff1a; 使用前先配置Sci-Hub的地址&#xff0c;如果网址不失效&#xff0c;配置一次即可 选中文章的DOI&#xff0c;右击选择Sci-Hub Search即可自动跳转到文章对应的Sci-Hub界面

Pubmedy加载时显示程序包无效的解决方案

目前谷歌应用商城已经下架Pubmedy&#xff0c;本地安装又遇到程序包无效&#xff1a;“CRX_HEADER_INVALID”。 解决方案&#xff1a; 将PubMedy.crx重命名为PubMedy.rar或者PubMedy.zip解压到要安装的位置找到扩展程序选项&#xff0c;并启用开发者选项选择加载已解压的扩展程…

细胞实验文献检索——PubMed | MedChemExpress

今天我们就以小白的课题——自噬 (Autophagy) 为例&#xff0c;给大家展示一波。这个时候给大家隆重介绍我们的——PubMed。 PubMed 提到 PubMed&#xff0c;相信大家应该都不陌生&#xff0c;它是常用的国外数据库之一&#xff0c;也是小编查找文献最喜欢的工具。自成立以来…

免费获取论文全文的方法,SCI-HUB的使用教程

很多人不在学校期间需要看文献全文&#xff0c;很多人获取文章的方式或是在网上求助或是给原作者索要。在SCI—HUB出现后&#xff0c;这些麻烦都不需要。SCI—PUB上保存了超过了4700万篇科研文献。SCI—PUB的网址 使用方法&#xff1a; 方法一、打开网页将想要论文的URL地址&…

SCI-HUB丨最新文献网址

sci-hub&#xff1a;在我们获取文献与学术论文的道路上提供了极好的便利&#xff0c;可以从中得到免费的文献下载&#xff0c;但也因为这样遭到各大出版社&#xff1a;ai思为尔&#xff0c;施普林格&#xff0c;wiley等出版社的打击与封杀&#xff0c;使得Sci-Hub在域名上不得不…

pubmed显示服务器不稳定,PubMed天天用,可是你真的用对了吗?

你是用关键词在PubMed上找论文的吗&#xff1f;如果关键词好几个&#xff0c;由于单词间的空格键存在AND命令&#xff0c;导致明明要搜的是词组&#xff0c;搜索不是被放大就是缩小&#xff0c;很难检索到合适量且关联度高的文。例如&#xff0c;你想找和肠炎与肿瘤坏死因子相关…

pubmed文献批量化下载器

1.代码如下 import time import requests import pandas as pd import osdef getArticle(PMCID,NIHMSID,DOI,title,path):print(PMCID,NIHMSID,DOI,title,path)os.chdir(path)headers {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit…

PubMed插件:分区、影响因子和即时IF一目了然,还能秒下文献(亲测有效)

Pubmed作为生物医药研究者最常用的免费文摘数据库&#xff0c;素有检索江湖上的泰山北斗之称&#xff0c;用好Pubmed&#xff0c;其他一切pubmed镜像网站都是浮云。今天小编给大家介绍一款完全免费的无需登录的pubmed插件&#xff0c;他可以解决pubmed本身不显示杂志影响因子的…

干货分享|被PubMed收录的论文,在MEDLINE和SCIE能检索到吗?

PubMed PubMed是由美国国家医学图书馆&#xff08;National Library of Medicine&#xff0c;NLM&#xff09;的国家生物技术信息中心&#xff08;National Center for Biotechnology Information&#xff0c;NCBI&#xff09;开发研制的一个医学文献网络数据库。PubMed是当今…

MEDLINE与PubMed有什么区别?检索范围包含哪些?

PubMed PubMed是由美国国家医学图书馆&#xff08;National Library of Medicine&#xff0c;NLM&#xff09;的国家生物技术信息中心&#xff08;National Center for Biotechnology Information&#xff0c;NCBI&#xff09;开发研制的一个医学文献网络数据库。 PubMed是当今…

怎么找生物信息论文的数据,PubMed太有用了!

小白的创作灵感日志&#xff1a; 由于要跑通一篇Paper A的代码&#xff0c;我需要找到它用到的所有数据集&#xff0c;今天我要找的是 Nagano 细胞周期数据集&#xff0c;刚开始脑子一团浆糊&#xff0c;直接在谷歌上搜“Nagano”&#xff0c;出来的全都是日本的一个名叫Nagano…

PubMed文献影响因子怎么显示保姆教程

NCBI文献影响因子显示&#xff1a; 安装的第一个插件&#xff1a;Tampermonkey插件网址&#xff1a;Greasy Fork - 安全、实用的用户脚本大全 1.选择合适的浏览器进行安装 2.点击安装 安装的第二个插件&#xff1a;scholarscope 1.网址&#xff1a;Scholarscope | 更好的科研…

文献管理器PubMedPro使用

PubMedPro介绍 Pubmed.pro文献检索文献管理网站&#xff1a; 您可以将自己电脑中的文献PDF批量上传至本网站&#xff0c;所有文件会自动同步至云端&#xff0c;您可以子啊任意一台电脑、平板、手机&#xff0c;通过自己的账号在线访问文献PDF。也可以分享自己的文献给身边的同…

科研小助手PubMed的插件PubMedy

Scholarscope首页地址&#xff1a; https://www.scholarscope.cn/(此链接就可以帮你安装&#xff0c;里面“查看安装教程”下面有个“立即前往安装教程页面”按钮&#xff0c;点击即可。) 此插件的安装也很简单&#xff0c;在Chrome浏览器的安装跟其他插件的安装没有区别。 而…

PubMed插件神器之PubMedy和Scholarscope(末尾附赠Google截屏神器)

生信读研第一件事绝对是读大量文献&#xff0c;那么如何在PubMed大量的文献中筛选出自己想要并且高质量的呢&#xff1f;当然第一反应肯定是检索式&#xff0c;但这得自己清楚的知道课题的关键词才能准确筛选。 刚进实验室&#xff0c;大师兄就扔给我两篇文献让我去了解。但是!…

jdk完全卸载方法

Win7系统下安装jdk报错&#xff1b;错误信息&#xff1a;“ Windows Installer 程序包有问题。此安装需要的DLL不能运行。请与您的支持人员或程序包开发商联系”。 造成该问题的原因是系统以前安装过jdk&#xff0c;重新安装需要彻底卸载旧的jdk&#xff0c;这样重新安装才能成…

JDK卸载与安装

JDK卸载 **第一步:**通过环境变量找到 JDK安装路径。 **第二步&#xff1a;**删除JDK安装包。 **第三步&#xff1a;**删除JAVA_HOME及path下关于java的目录 **第四步&#xff1a;**打开cmd 命令输入java -version检验是否删除成功。 安装JDK 1、百度搜索JDK8&#xff…