• Spring循环依赖解决(五)


    spring可以解决属性注入循环依赖,默认不能解决构造器注入循环依赖。

    spring创建对象分两步,①初始化实例对象,②初始化对象属性。

    spring循环依赖,最初引用的就是半成品,也就是只初始化示例对象,还没有初始化对象属性。

    1.  循环依赖导致系统启动失败的情况

      发生循环依赖一般是在构造方法中注入引起循环依赖错误。

    1. 循环依赖

      一般发生循环依赖也就是构造方法发生依赖,比如:

    类A:

    package qz;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyA {
    
        private CircularDependencyB circB;
    
        @Autowired
        public CircularDependencyA(CircularDependencyB circB) {
            this.circB = circB;
        }
    }

    类B:

    package qz;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyB {
    
        private CircularDependencyA circA;
    
        @Autowired
        public CircularDependencyB(CircularDependencyA circA) {
            this.circA = circA;
        }
    }

    主类:

    package qz;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    
    import java.io.IOException;
    
    @ComponentScan("qz")
    public class MainApp {
    
        public static void main(String[] args) throws InterruptedException, IOException {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainApp.class);
            applicationContext.close();
        }
    }

    结果:

    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'circularDependencyB' defined in file [E:xiangmuspringsource2spring-framework-5.1.xmytestuildclassesjavamainqzCircularDependencyB.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?

    2. 解决办法

    1.  重新设计,说白了就是构造注入改为属性注入

    类A:

    package qz;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyA {
    
        @Autowired
        private CircularDependencyB circB;
    }

    类B:

    package qz;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyB {
    
        @Autowired
        private CircularDependencyA circA;
    
    }

    2. 使用@Lazy 注解: 只用一方打注解即可

    package qz;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyA {
    
        private CircularDependencyB circB;
    
        @Autowired
        public CircularDependencyA(@Lazy CircularDependencyB circB) {
            this.circB = circB;
        }
    }

    3.  使用@PostConstruct

    类A

    package qz;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PostConstruct;
    
    @Component
    public class CircularDependencyA {
    
        @Autowired
        private CircularDependencyB circB;
    
        @PostConstruct
        public void init() {
            circB.setCircA(this);
        }
    
        public CircularDependencyB getCircB() {
            return circB;
        }
    }

    类B:

    package qz;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyB {
    
        private CircularDependencyA circA;
    
        public void setCircA(CircularDependencyA circA) {
            this.circA = circA;
        }
    }

    4. 实现ApplicationContextAware and InitializingBean 接口

    类A:

    package qz;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
    
        private CircularDependencyB circB;
    
        private ApplicationContext context;
    
        public CircularDependencyB getCircB() {
            return circB;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            circB = context.getBean(CircularDependencyB.class);
        }
    
        @Override
        public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
            context = ctx;
        }
    }

    类B:

    package qz;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CircularDependencyB {
    
        private CircularDependencyA circA;
    
        @Autowired
        public void setCircA(CircularDependencyA circA) {
            this.circA = circA;
        }
    }

     2. 属性注入循环依赖Spring解决方式分析

    通过之前分析得到,Spring创建Bean是从getBean开始。主要分为两部分。当前对象实例化和对象属性的实例化。对象的实例化通过反射实现,对象的属性是在实例化之后通过注入或者其他方式实现的。

    org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 源码如下:

        protected <T> T doGetBean(
                String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
                throws BeansException {
    
            String beanName = transformedBeanName(name);
            Object bean;
    
            Object sharedInstance = getSingleton(beanName);
            if (sharedInstance != null && args == null) {
                if (logger.isTraceEnabled()) {
                    if (isSingletonCurrentlyInCreation(beanName)) {
                        logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                                "' that is not fully initialized yet - a consequence of a circular reference");
                    }
                    else {
                        logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
                    }
                }
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
            }
    
            else {
                // Fail if we're already creating this bean instance:
                // We're assumably within a circular reference.
                if (isPrototypeCurrentlyInCreation(beanName)) {
                    throw new BeanCurrentlyInCreationException(beanName);
                }
    
                // Check if bean definition exists in this factory.
                BeanFactory parentBeanFactory = getParentBeanFactory();
                if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                    // Not found -> check parent.
                    String nameToLookup = originalBeanName(name);
                    if (parentBeanFactory instanceof AbstractBeanFactory) {
                        return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                                nameToLookup, requiredType, args, typeCheckOnly);
                    }
                    else if (args != null) {
                        // Delegation to parent with explicit args.
                        return (T) parentBeanFactory.getBean(nameToLookup, args);
                    }
                    else if (requiredType != null) {
                        // No args -> delegate to standard getBean method.
                        return parentBeanFactory.getBean(nameToLookup, requiredType);
                    }
                    else {
                        return (T) parentBeanFactory.getBean(nameToLookup);
                    }
                }
    
                if (!typeCheckOnly) {
                    markBeanAsCreated(beanName);
                }
    
                try {
                    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                    checkMergedBeanDefinition(mbd, beanName, args);
    
                    // Guarantee initialization of beans that the current bean depends on.
                    String[] dependsOn = mbd.getDependsOn();
                    if (dependsOn != null) {
                        for (String dep : dependsOn) {
                            if (isDependent(beanName, dep)) {
                                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                            }
                            registerDependentBean(dep, beanName);
                            try {
                                getBean(dep);
                            }
                            catch (NoSuchBeanDefinitionException ex) {
                                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                        "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                            }
                        }
                    }
    
                    // Create bean instance.
                    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);
                    }
    
                    else if (mbd.isPrototype()) {
                        // It's a prototype -> create a new instance.
                        Object prototypeInstance = null;
                        try {
                            beforePrototypeCreation(beanName);
                            prototypeInstance = createBean(beanName, mbd, args);
                        }
                        finally {
                            afterPrototypeCreation(beanName);
                        }
                        // create bean
                        bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                    }
    
                    else {
                        String scopeName = mbd.getScope();
                        if (!StringUtils.hasLength(scopeName)) {
                            throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
                        }
                        Scope scope = this.scopes.get(scopeName);
                        if (scope == null) {
                            throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                        }
                        try {
                            Object scopedInstance = scope.get(beanName, () -> {
                                beforePrototypeCreation(beanName);
                                try {
                                    return createBean(beanName, mbd, args);
                                }
                                finally {
                                    afterPrototypeCreation(beanName);
                                }
                            });
                            bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                        }
                        catch (IllegalStateException ex) {
                            throw new BeanCreationException(beanName,
                                    "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                    "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                    ex);
                        }
                    }
                }
                catch (BeansException ex) {
                    cleanupAfterBeanCreationFailure(beanName);
                    throw ex;
                }
            }
    
            // Check if required type matches the type of the actual bean instance.
            if (requiredType != null && !requiredType.isInstance(bean)) {
                try {
                    T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                    if (convertedBean == null) {
                        throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                    }
                    return convertedBean;
                }
                catch (TypeMismatchException ex) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Failed to convert bean '" + name + "' to required type '" +
                                ClassUtils.getQualifiedName(requiredType) + "'", ex);
                    }
                    throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
                }
            }
            return (T) bean;
        }

    解释:

    1. Object sharedInstance = getSingleton(beanName); 先尝试获取对象实例,查看getSingleton(beanName) 源码如下:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

        /**
         * 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);
            // 如果一级不存在,则判断当前对象是否处于创建过程中(第一次尝试获取A对象实例之后就会标记为创建中)。
            if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
                // 从二级缓存中查询,获取Bean的早期引用,实例化完成,但未赋值完成的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) {
                                // 从三级缓存中查询,实例化完成,属性为装配完成
                                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                                if (singletonFactory != null) {
                                    // 获取目标对象的实例
                                    singletonObject = singletonFactory.getObject();
                                    // //添加到二级缓存中
                                    this.earlySingletonObjects.put(beanName, singletonObject);
                                    // 从三级缓存中移除
                                    this.singletonFactories.remove(beanName);
                                }
                            }
                        }
                    }
                }
            }
            return singletonObject;
        }

    org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation源码如下:

        public boolean isSingletonCurrentlyInCreation(String beanName) {
            return this.singletonsCurrentlyInCreation.contains(beanName);
        }

    一二三级缓存全部查询,如果三级缓存存在则将Bean早期引用存放在二级缓存中并移除三级缓存。(升级为二级缓存)。这里看到用到了三个map,如下:

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

    singletonObjects 对应一级缓存。 存放完全实例化属性赋值完成的Bean,可以直接使用

    earlySingletonObjects  对应二级缓存,存放早起Bean的引用,尚未属性装配的Bean,也就是半成品

    singletonFactories  对应三级缓存,存放实例化完成的Bean工厂。

    2. 如下代码是创建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);
                    }

     2.1 这段代码实际上用了一个lambda表达式,也就是如果getSingleton 获取不到bean则会创建bean。org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)源码如下:

        public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
            Assert.notNull(beanName, "Bean name must not be null");
            synchronized (this.singletonObjects) {
                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;
                        }
                    }
                    catch (BeanCreationException ex) {
                        if (recordSuppressedExceptions) {
                            for (Exception suppressedException : this.suppressedExceptions) {
                                ex.addRelatedCause(suppressedException);
                            }
                        }
                        throw ex;
                    }
                    finally {
                        if (recordSuppressedExceptions) {
                            this.suppressedExceptions = null;
                        }
                        afterSingletonCreation(beanName);
                    }
                    if (newSingleton) {
                        addSingleton(beanName, singletonObject);
                    }
                }
                return singletonObject;
            }
        }

    beforeSingletonCreation(beanName); 方法会将创建标记存入Map中,标记着该Bean开始创建。org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation源码如下:

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

    2.2 也就是获取不到Bean调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean方法,方法中委托给org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

        protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
                throws BeanCreationException {
    
            // Instantiate the bean.
            BeanWrapper instanceWrapper = null;
            if (mbd.isSingleton()) {
                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;
            }
    
            // Allow post-processors to modify the merged bean definition.
            synchronized (mbd.postProcessingLock) {
                if (!mbd.postProcessed) {
                    try {
                        applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                    }
                    catch (Throwable ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                "Post-processing of merged bean definition failed", ex);
                    }
                    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) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Eagerly caching bean '" + beanName +
                            "' to allow for resolving potential circular references");
                }
                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 (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                    throw (BeanCreationException) ex;
                }
                else {
                    throw new BeanCreationException(
                            mbd.getResourceDescription(), beanName, "Initialization of bean failed", 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 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.");
                        }
                    }
                }
            }
    
            // Register bean as disposable.
            try {
                registerDisposableBeanIfNecessary(beanName, bean, mbd);
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
            }
    
            return exposedObject;
        }

    2.3 可以看到 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)) 代码调用添加三级缓存。org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory如下:

        /**
         * Add the given singleton factory for building the specified singleton
         * if necessary.
         * <p>To be called for eager registration of singletons, e.g. to be able to
         * resolve circular references.
         * @param beanName the name of the bean
         * @param singletonFactory the factory for the singleton object
         */
        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.4 上面2.2中的 populateBean 方法调用会进行依赖注入等操作,如果属性注入其他bean会循环调用getBean方法

    2.5 在2.1的代码块中可以看到addSingleton(beanName, singletonObject);  加入缓存的代码。org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton:

        /**
         * Add the given singleton object to the singleton cache of this factory.
         * <p>To be called for eager registration of singletons.
         * @param beanName the name of the bean
         * @param singletonObject the singleton object
         */
        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);
            }
        }

      加入缓存之后完成整个bean的创建以及赋值。

      也就是二级缓存相当于个过渡。从三级到一级的过渡,放的是一个半成品。

    总结下流程相当于:假设A依赖B、B依赖A。

    (1)A依次执行doGetBean、查询缓存、createBean创建实例,实例化完成放入三级缓存singletonFactories中,接着执行populateBean方法装配属性,但是发现有一个属性是B的对象。

    (2)再次调用doGetBean方法创建B的实例,依次执行doGetBean、查询缓存、createBean创建实例,实例化完成之后放入三级缓存singletonFactories中,执行populateBean装配属性,但是此时发现有一个属性是A对象。

    (3)因此再次调用doGetBean创建A的实例,但是执行到getSingleton查询缓存的时候,从三级缓存中查询到了A的实例(早期引用,未完成属性装配),此时移到二级缓存中然后返回,不用执行后续的流程创建A了,那么B就完成了属性装配,此时是一个完整的对象放入到一级缓存singletonObjects中。

    (4)B创建完成了,则A自然完成了属性装配,也创建完成放入了一级缓存singletonObjects中。

    参考:https://www.baeldung.com/circular-dependencies-in-spring

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    C语言作业
    nrf52832(nrf52810)制作升级包出错及解决方法
    nrf52810的升级命令
    nrfutil生成的DFU设置十六进制文件未按字对齐
    Android CameraHal NativeWindow相关(一):从CameraHal::setPreviewWindow(struct preview_stream_ops *window)开始
    Android Camera 调用流程及调试
    Android MediaPlayer的核心原理
    Android MediaPlayer状态图明晰注释
    Android MediaPlayer状态机
    Android Camera Preview ANativeWindow的处理
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/14417890.html
Copyright © 2020-2023  润新知