0
点赞
收藏
分享

微信扫一扫

Spring源码解析之循环依赖

香小蕉 2021-09-21 阅读 91

什么是循环依赖?

循环依赖:在依赖注入的过程中,多个Bean对象互相持有对方的引用,比如A对象中包含B对象,B对象中包含A对象,以此类推,它们看上去就像是一个圆环,首尾相连。

了解完之后你可能会有疑问:这样的情况会引发什么问题?

所谓知其然还要知其所以然,所以解决问题之前我们还需要思考它发生的原因。

Spring中的循环依赖

原因

首先我们需要结合Spring中Bean的实例化规则,而前一篇关于解析Spring依赖注入的源码内容中(Spring源码解析之DI篇如果不清楚DI原理小伙伴推荐先了解完再来看循环依赖,相信更加容易理解),已经清楚的知道Spring在预实例化Bean之前有几种情况会先实例化其依赖的Bean对象,这样下来大概会出现什么情况呢,我们简单画一下,

是不是发现不对劲了,它们就像一个循环递归,除非有终结条件,否则就是死循环,直到内存溢出异常。

发生场景与分析

既然知道了问题发生的原因,心急的朋友可能就直接考虑Spring的解决方案了,但是不要急,这只是初步的设想,我们还得具体的分析下不同场景下的发生的循环依赖。上面说了有一些情况实例化Bean之前会先实例化其依赖的Bean对象,那么会有哪些情况呢?接下来我们先分别罗列验证下。

首先我们知道Spring中Bean的作用域其实可以分为三种:单例(singleton)、原型(prototype)、其它(request、session、application、websocket)。而Bean实例化的源码中也是根据这三种Scope分别选择不同的实例化策略,看下源码(无关代码暂时省略)

    protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
            ......

                //创建单例Bean对象
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            //创建Bean实例
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            //清除Bean实例
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
                //创建原型Bean对象
                else if (mbd.isPrototype()) {
                    Object prototypeInstance = null;
                    try {
                        //创建前回调,默认实现将beanName注册为当前正在创建的原型Bean
                        beforePrototypeCreation(beanName);
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        //创建后回调,默认实现移除刚注册的原型Bean的beanName
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }
                //创建的非单例、非原型的Bean
                else {
                    String scopeName = mbd.getScope();
                    final 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);
                    }
                }
        ......
        return (T) bean;
    }

相比单例只会保存一个对象来说,其中原型和其它都会产生多个对象,下面不同场景也会对单例、多例(原型)情况来分别验证。

开始之前我们先介绍两个会用到的属性:

/** 保存正在创建的单例Bean的beanName */
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

/** 保存正在创建的原型Bean的beanName,其中Object可能是个String,有可能是个Set<String>*/
private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");

有参构造方法注入

首先我们准备好测试类,如下:

@Component
public class BeanA {
    @Autowired
    private BeanB beanB;
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
    public BeanB getBeanB() {
        return beanB;
    }
}

@Component
public class BeanB {
    @Autowired
    private BeanA beanA;
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
    public BeanA getBeanA() {
        return beanA;
    }
}

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity");
        BeanA beanA = (BeanA) ac.getBean("beanA");
        BeanB beanB = (BeanB) ac.getBean("beanB");
        System.out.println(beanA);
        System.out.println(beanB);
        System.out.println(beanB.getBeanA());
        System.out.println(beanA.getBeanB());
    }
}

运行代码,抛出如下异常,

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

看来单例的构造器注入出现了循环依赖的错误。我们再来看看原型类型,在BeanA、BeanB的类上分别加入

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

运行测试代码,发现同样会抛出上述错误异常。

现在知道了结果,那接下来我们先分析下产生异常的源码流程图(这里源码不会贴的很细,包括后面场景的流程图,主要关注直接重要相关的点,追求代码细节的可结合前面DI篇或Spring源码),

在创建对象之前我们会先通过
isSingletonCurrentlyInCreation()或者isPrototypeCurrentlyInCreation()方法判断当前需要创建的Bean是否正在创建,如果不在则会把当前Bean的beanName保存到singletonsCurrentlyInCreation或prototypesCurrentlyInCreation中去;由于我们通过有参构造方法注入(见图中黄色标识),所以会调用autowireConstructor()方法,而触发构造方法参数BeanB的实例化就发生在这,我们知道它主要的作用就是先确定构造方法参数,再通过参数确定构造方法,然后将参数转换成需要的类型,最后调用strategy.instantiate()完成实例化。在方法里面有个重要的变量ArgumentsHolder,它是构造方法需要的参数的封装,通过对createArgumentArray()的调用完成它的创建初始化,

    private ArgumentsHolder createArgumentArray(
            String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
            BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
            boolean autowiring) throws UnsatisfiedDependencyException {
        TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
        TypeConverter converter = (customConverter != null ? customConverter : bw);
        ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
        Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
        Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
        for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
            Class<?> paramType = paramTypes[paramIndex];
            String paramName = (paramNames != null ? paramNames[paramIndex] : "");
            // Try to find matching constructor argument value, either indexed or generic.
            ConstructorArgumentValues.ValueHolder valueHolder = null;
            if (resolvedValues != null) {
                valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
                // If we couldn't find a direct match and are not supposed to autowire,
                // let's try the next generic, untyped argument value as fallback:
                // it could match after type conversion (for example, String -> int).
                if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
                    valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
                }
            }
            if (valueHolder != null) {
                // We found a potential match - let's give it a try.
                // Do not consider the same value definition multiple times!
                usedValueHolders.add(valueHolder);
                Object originalValue = valueHolder.getValue();
                Object convertedValue;
                if (valueHolder.isConverted()) {
                    convertedValue = valueHolder.getConvertedValue();
                    args.preparedArguments[paramIndex] = convertedValue;
                }
                else {
                    MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
                    try {
                        convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
                    }
                    catch (TypeMismatchException ex) {
                        throw new UnsatisfiedDependencyException(
                                mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
                                "Could not convert argument value of type [" +
                                        ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
                                        "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
                    }
                    Object sourceHolder = valueHolder.getSource();
                    if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) {
                        Object sourceValue = ((ConstructorArgumentValues.ValueHolder) sourceHolder).getValue();
                        args.resolveNecessary = true;
                        args.preparedArguments[paramIndex] = sourceValue;
                    }
                }
                args.arguments[paramIndex] = convertedValue;
                args.rawArguments[paramIndex] = originalValue;
            }
            else {
                MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
                // No explicit match found: we're either supposed to autowire or
                // have to fail creating an argument array for the given constructor.
                if (!autowiring) {
                    throw new UnsatisfiedDependencyException(
                            mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
                            "Ambiguous argument values for parameter of type [" + paramType.getName() +
                            "] - did you specify the correct bean references as arguments?");
                }
                try {
                    Object autowiredArgument =
                            resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter);
                    args.rawArguments[paramIndex] = autowiredArgument;
                    args.arguments[paramIndex] = autowiredArgument;
                    args.preparedArguments[paramIndex] = new AutowiredArgumentMarker();
                    args.resolveNecessary = true;
                }
                catch (BeansException ex) {
                    throw new UnsatisfiedDependencyException(
                            mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex);
                }
            }
        }
        for (String autowiredBeanName : autowiredBeanNames) {
            this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
            if (this.beanFactory.logger.isDebugEnabled()) {
                this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName +
                        "' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
                        " to bean named '" + autowiredBeanName + "'");
            }
        }
        return args;
    }

    @Nullable
    protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
            @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter) {
        if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) {
            InjectionPoint injectionPoint = currentInjectionPoint.get();
            if (injectionPoint == null) {
                throw new IllegalStateException("No current InjectionPoint available for " + param);
            }
            return injectionPoint;
        }
        return this.beanFactory.resolveDependency(
                new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
    }

它主要实现了构造函数参数值的解析,而其中自动装配的参数autowiredArgument的解析真正通过调用模板方法resolveDependency()来完成的,

    @Override
    @Nullable
    public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
        descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
        if (Optional.class == descriptor.getDependencyType()) {
            return createOptionalDependency(descriptor, requestingBeanName);
        }
        else if (ObjectFactory.class == descriptor.getDependencyType() ||
                ObjectProvider.class == descriptor.getDependencyType()) {
            return new DependencyObjectProvider(descriptor, requestingBeanName);
        }
        else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
            return new Jsr330ProviderFactory().createDependencyProvider(descriptor, requestingBeanName);
        }
        else {
             //处理需要延迟解析的对象
            Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
                    descriptor, requestingBeanName);
            if (result == null) {
                result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
            }
            return result;
        }
    }

它会根据参数的声明类型选择不同的依赖处理策略,正常的情况下会走doResolveDependency()方法去处理,

    @Nullable
    public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
        InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
        try {
            Object shortcut = descriptor.resolveShortcut(this);
            if (shortcut != null) {
                return shortcut;
            }
            Class<?> type = descriptor.getDependencyType();
            Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
            if (value != null) {
                if (value instanceof String) {
                    String strVal = resolveEmbeddedValue((String) value);
                    BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
                    value = evaluateBeanDefinitionString(strVal, bd);
                }
                TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
                return (descriptor.getField() != null ?
                        converter.convertIfNecessary(value, type, descriptor.getField()) :
                        converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
            }
            Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
            if (multipleBeans != null) {
                return multipleBeans;
            }
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
            if (matchingBeans.isEmpty()) {
                if (isRequired(descriptor)) {
                    raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
                }
                return null;
            }
            String autowiredBeanName;
            Object instanceCandidate;
            if (matchingBeans.size() > 1) {
                autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
                if (autowiredBeanName == null) {
                    if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
                        return descriptor.resolveNotUnique(type, matchingBeans);
                    }
                    else {
                        // In case of an optional Collection/Map, silently ignore a non-unique case:
                        // possibly it was meant to be an empty collection of multiple regular beans
                        // (before 4.3 in particular when we didn't even look for collection beans).
                        return null;
                    }
                }
                instanceCandidate = matchingBeans.get(autowiredBeanName);
            }
            else {
                // We have exactly one match.
                Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
                autowiredBeanName = entry.getKey();
                instanceCandidate = entry.getValue();
            }
            if (autowiredBeanNames != null) {
                autowiredBeanNames.add(autowiredBeanName);
            }
             // 重点
            if (instanceCandidate instanceof Class) {
                instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
            }
            Object result = instanceCandidate;
            if (result instanceof NullBean) {
                if (isRequired(descriptor)) {
                    raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
                }
                result = null;
            }
            if (!ClassUtils.isAssignableValue(type, result)) {
                throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
            }
            return result;
        }
        finally {
            ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
        }
    }

这个方法就是实现了具体的解析过程,包括查找确定装配的Bean,重点在resolveCandidate()方法,我们点进去,

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

发现它最后调用的其实就是getBean()方法,而这里就是真正触发依赖Bean的实例化源头了。千回百绕,最终还是回到原点。

接下来的流程也应该很清楚了,递归走下去会再次触发对BeanB中的构造参数BeanA的实例化,再调用getBean()方法对BeanA实例化时,判断
isSingletonCurrentlyInCreation()或者isPrototypeCurrentlyInCreation()方法,发现BeanA已经正在创建中,最后抛出异常。

throw new BeanCurrentlyInCreationException(beanName);

所以不管单例还是多例,这种有参构造方法注入(也就是Spring中说的构造函数注入)的场景导致的循环依赖在Spring中常规情况是无法解决的。但是Spring中允许通过一个非常规办法来绕开这个死结,就是@Lazy注解,待会我们再详细讲。

无参构造方法注入

接下来再看无参构造方法注入的场景,也是就Spring中说的setter注入,先准备测试类,

@Component
public class BeanA {
    @Autowired
    private BeanB beanB;
    public BeanB getBeanB() {
        return beanB;
    }
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
public class BeanB {
    @Autowired
    private BeanA beanA;
    public BeanA getBeanA() {
        return beanA;
    }
    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity");
        BeanA beanA = (BeanA) ac.getBean("beanA");
        BeanB beanB = (BeanB) ac.getBean("beanB");
        System.out.println(beanA);
        System.out.println(beanB);
        System.out.println(beanB.getBeanA());
        System.out.println(beanA.getBeanB());
    }
}

运行代码,控制台打印如下,

com.test.spring.entity.BeanA@47db50c5
com.test.spring.entity.BeanB@5c072e3f
com.test.spring.entity.BeanA@47db50c5
com.test.spring.entity.BeanB@5c072e3f

看来单例的构造器注入是正常的。我们再来看看原型类型,在BeanA、BeanB的类上分别加入

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

运行测试代码,发现与单例情况不一样,这里会抛出错误异常,看来多例的构造器注入出现了循环依赖的错误。

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

与有参构造方法注入不一样,这里单例和原型会出现不一样的结果,那接下来我们先分别分析下它们的源码流程图。

这里我们又需要用到一个直接相关属性了,先来介绍下:

/** 保存创建Bean的ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

之前DI篇我们已经详细介绍过ObjectFactory了,它是一个对象工厂接口,通过它可以返回对象的实例,而源码中其实我们也经常能看见它,通常以匿名内部类出现,比如:

sharedInstance = this.getSingleton(beanName, () -> {
                        try {
                            return this.createBean(beanName, mbd, args);
                        } catch (BeansException var5) {
                            this.destroySingleton(beanName);
                            throw var5;
                        }
                    });

  • 单例Singleton: 与有参构造方法注入不同的是,添加@Autowired注解的属性BeanA或BeanB,触发对它们的实例化是在属性注入过程中发生的,而这时候createBeanInstance()已经调用完成,表示Bean对象已经完成创建了,只是还未完成初始化操作,还在实例化过程中。我们看下源码, protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { return; } } //实例化后处理 boolean continueWithPropertyPopulation = true; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continueWithPropertyPopulation = false; break; } } } } if (!continueWithPropertyPopulation) { return; } PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); //处理自动装配 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); //根据名称自动注入 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } //根据类型自动注入 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE); if (hasInstAwareBpps || needsDepCheck) { if (pvs == null) { pvs = mbd.getPropertyValues(); } //属性值处理 PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); if (hasInstAwareBpps) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvs == null) { return; } } } } //依赖检查 if (needsDepCheck) { checkDependencies(beanName, mbd, filteredPds, pvs); } } if (pvs != null) { //属性注入 applyPropertyValues(beanName, mbd, bw, pvs); } } 复制代码 如果通过XML的去配置Bean的属性依赖,那么会在autowireByName()或autowireByType()中触发调用getBean()来完成对依赖Bean的实例化,但是实际开发中我们可能更多是通过注解去属性注入,比如@Autowired、@Value、@Resouce等;这里我们还是以测试类中的@Autowired为例,在遍历Bean对象的BeanPostProcessor中,会获取到AutoWiredAnnotationBeanPostProcessor这个后置处理器,它就会扫描到配置了@Autowired注解的属性字段,然后会调用postProcessPropertyValues()方法来对属性进行自动注入,我们来看看postProcessPropertyValues()的实现, @Override public PropertyValues postProcessPropertyValues( PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { //获取autowire相关注解的元数据 InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { //属性注入 metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } return pvs; } public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { boolean debug = logger.isDebugEnabled(); //遍历需要注入的属性字段 for (InjectedElement element : elementsToIterate) { if (debug) { logger.debug("Processing injected element of bean '" + beanName + "': " + element); } element.inject(target, beanName, pvs); } } } 复制代码 通过上面的代码,我们能发现真正属性注入的处理是调用element.inject(target, beanName, pvs)来完成的,查看element的源码不难发现,这个InjectedElement类是metadata的一个内部类,而它有两个子类继承:AutowiredFieldElement和AutowiredMethodElement,它们分别重写了inject()方法,同时它们也是AutoWiredAnnotationBeanPostProcessor的内部类,其中AutowiredFieldElement是扫描属性字段上面的注解处理,AutowiredMethodElement是扫描方法参数上面的注解处理。这里测试类实际调用的是AutowiredFieldElement中的inject()方法, @Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { //获取属性字段 Field field = (Field) this.member; Object value; //判断是否在容器中缓存 if (this.cached) { //获取缓存值 value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); Set<String> autowiredBeanNames = new LinkedHashSet<>(1); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); try { //根据容器中Bean定义,解析指定的依赖关系,获取依赖对象 value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); } synchronized (this) { if (!this.cached) { if (value != null || this.required) { this.cachedFieldValue = desc; //注册依赖Bean registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName)) { //类型匹配 if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { //创建依赖对象的引用并缓存 this.cachedFieldValue = new ShortcutDependencyDescriptor( desc, autowiredBeanName, field.getType()); } } } } else { this.cachedFieldValue = null; } this.cached = true; } } } if (value != null) { //通过JDK的反射机制,设置允许访问 ReflectionUtils.makeAccessible(field); //属性字段赋值 field.set(bean, value); } } } 复制代码 是不是注意到了value的值是通过调用模板方法resolveDependency()方法获取到的,而这个方法及后面的实现刚才我们已经详细分析过了,和有参构造方法注入的resolveDependency()处理是一样的。其实如果你去查看AutowiredMethodElement中的inject()方法源码,你会发现最后也是通过resolveDependency()方法来获取属性注入的对象的。 所以综上所述,单例的setter循环注入为什么是正常允许的我们就很清楚原因了。
  • 原型prototype: 前面的原型实例化策略代码中,我们知道原型Bean调用createBean()创建之前会调用beforePrototypeCreation(beanName),它就是用来缓存正在创建的Bean的beanName,而原型和单例不一样的是在每次请求都会创建一个对象,而缓存prototypesCurrentlyInCreation我们已经知道是ThreadLocal中的线程隔离的私有变量,当前线程已经正在创建原型Bean的时候Spring就不允许再次创建了,所以这里抛出了循环依赖异常错误。

@DependsOn注解

我们先介绍下@DependsOn注解的作用:任何指定此注解的依赖Bean都保证要在被依赖Bean之前由容器创建。

同时再介绍两个会用到的属性:

/** 保存Bean和依赖Bean的关系 */
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

/** 保存依赖Bean和被依赖Bean的关系 */
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

接下来我们准备好测试类,如下:

@Component
@DependsOn("beanB")
public class BeanA {
    @Autowired
    private BeanB beanB;
    public BeanB getBeanB() {
        return beanB;
    }
}

@Component
@DependsOn("beanA")
public class BeanB {
    @Autowired
    private BeanA beanA;
    public BeanA getBeanA() {
        return beanA;
    }
}

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity");
        BeanA beanA = (BeanA) ac.getBean("beanA");
        BeanB beanB = (BeanB) ac.getBean("beanB");
        System.out.println(beanA);
        System.out.println(beanB);
        System.out.println(beanB.getBeanA());
        System.out.println(beanA.getBeanB());
    }
}

运行代码,抛出如下异常,

Circular depends-on relationship between 'beanB' and 'beanA'

看来单例的构造器注入出现了循环依赖的错误。我们再来看看原型类型,在BeanA、BeanB的类上分别加入

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

运行测试代码,发现同样会抛出上述错误异常。

那接下来我们再分析下产生异常的源码流程图,

发现异常结束的流程很短,查看源码,其实我们选择不同作用域的实例化策略之前就会先处理depenson依赖问题,

//获取Bean所有依赖的Bean
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 + "'");
        }
        //Bean和依赖的Bean互相缓存
        registerDependentBean(dep, beanName);
        //创建依赖Bean
        getBean(dep);
    }
}

所以不管单例原型再这一步就已经处理了循环依赖的异常。而这其实也是符合了Spring中对@DependsOn注解的作用描述。

解决方案

分析完上述几种循环依赖发生场景的具体源码处理,应该已经大概知道Spring中怎么去处理解决循环依赖的以及允许和不允许循环依赖出现的情况。其实在Spring官方文档上也已经给了我们答案了(谷歌翻译,可直接去查看原文)。

主、辅缓存

而循环依赖处理代码中有一些很重要的属性字段我们并没有直接去说,但上面的每个场景都应用到了,在这我们统一列出来,包括已经介绍过的,

    /** 缓存已经实例化的单例Bean */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** 缓存已完成创建但还未完成实例化的单例Bean */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    /** 缓存创建Bean的ObjectFactory */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    /** 缓存正在创建的单例Bean的beanName */
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /** 缓存正在创建的原型Bean的beanName,其中Object可能是个String,有可能是个Set<String>*/
    private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");

    /** 缓存正在创建的Bean的白名单*/
    private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /** 缓存Bean和依赖Bean的关系 */
    private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

    /** 缓存依赖Bean和被依赖Bean的关系 */
    private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

如果分类的话,我认为前三个是主缓存,可以直接从中获取Bean对象,后五个属于辅缓存,用于在实例化过程中记录Bean的状态信息;它们各自都在Spring循环依赖的处理中起了很重要的校验衔接作用,用来保障容器中Bean的实例化的健壮性。我们知道实例化Bean之前都会调用getSingleton()方法尝试先从缓存中获取Bean,而主缓存的作用在这里面得到了很重要的体现,主缓存采用了三层缓存,在实例化过程中根据Bean的不同状态纵向的分层承接缓存。

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

下面会通过流程图来展现这些缓存在整个实例化过程中的应用,

我不知道你有没有想过,有参构造方法注入或@DependsOn注解导致启动的循环依赖错误和setter注入对比是真的无法解决吗?

@Lazy注解

前面分析有参构造方法注入导致的循环依赖异常,我们留下了一个埋点,就是通过@Lazy注解可以解决异常报错的问题 。先上代码验证下,

@Component
public class BeanA {
    @Autowired
    private BeanB beanB;
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
    public BeanB getBeanB() {
        return beanB;
    }
}

@Component
public class BeanB {
    @Autowired
    private BeanA beanA;
    public BeanB(@Lazy BeanA beanA) {
        this.beanA = beanA;
    }
    public BeanA getBeanA() {
        return beanA;
    }
}

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext("com.test.spring.entity");
        BeanA beanA = (BeanA) ac.getBean("beanA");
        BeanB beanB = (BeanB) ac.getBean("beanB");
        System.out.println(beanA);
        System.out.println(beanB);
        System.out.println(beanB.getBeanA());
        System.out.println(beanA.getBeanB());
    }
}

运行代码,结果如下,发现结果竟然是正常的。

com.test.spring.entity.BeanA@7c417213
com.test.spring.entity.BeanB@15761df8
com.test.spring.entity.BeanA@7c417213
com.test.spring.entity.BeanB@15761df8

那这是怎么回事呢?

我们回看下之前的源码分析中,构造函数参数的解析通过调用模板方法resolveDependency()来完成的,在其源码中有这样一句代码,

//处理需要延迟解析的对象
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);
if (result == null) {
    result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result;

而这个
getLazyResolutionProxyIfNecessary()实际调用的是实现类ContextAnnotationAutowireCandidateResolver中的getLazyResolutionProxyIfNecessary()方法,我们来看看它里面到底做了什么,

    @Override
    @Nullable
    public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
        return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
    }

    protected boolean isLazy(DependencyDescriptor descriptor) {
        for (Annotation ann : descriptor.getAnnotations()) {
            Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
            if (lazy != null && lazy.value()) {
                return true;
            }
        }
        MethodParameter methodParam = descriptor.getMethodParameter();
        if (methodParam != null) {
            Method method = methodParam.getMethod();
            if (method == null || void.class == method.getReturnType()) {
                Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
                if (lazy != null && lazy.value()) {
                    return true;
                }
            }
        }
        return false;
    }

    protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
        Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,
                "BeanFactory needs to be a DefaultListableBeanFactory");
        final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
        TargetSource ts = new TargetSource() {
            @Override
            public Class<?> getTargetClass() {
                return descriptor.getDependencyType();
            }
            @Override
            public boolean isStatic() {
                return false;
            }
            @Override
            public Object getTarget() {
                Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
                if (target == null) {
                    Class<?> type = getTargetClass();
                    if (Map.class == type) {
                        return Collections.EMPTY_MAP;
                    }
                    else if (List.class == type) {
                        return Collections.EMPTY_LIST;
                    }
                    else if (Set.class == type || Collection.class == type) {
                        return Collections.EMPTY_SET;
                    }
                    throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
                            "Optional dependency not present for lazy injection point");
                }
                return target;
            }
            @Override
            public void releaseTarget(Object target) {
            }
        };
        ProxyFactory pf = new ProxyFactory();
        pf.setTargetSource(ts);
        Class<?> dependencyType = descriptor.getDependencyType();
        if (dependencyType.isInterface()) {
            pf.addInterface(dependencyType);
        }
        return pf.getProxy(beanFactory.getBeanClassLoader());
    }

这里面的代码应该比较容易理解,首先会判断这个属性对象是否配置了@Lazy注解,如果没有就返回null对象,否则会调用buildLazyResolutionProxy()方法创建一个懒加载对象的代理类,而这个代理类中封装了一个自定义实现的TargetSource,这个类中覆写了一个getTarget()的方法,而这个方法的实现中能看到Object对象是调用doResolveDependency()来获取的,不用说,我们也知道它能做什么。

这时候返回的BeanA的代理对象就不是null了,所以BeanB也可以完成实例化,接着BeanA也完成实例化了。但是回到main方法内,在打印BeanB时,如果你打开debug,你会发现BeanB中的属性BeanA还是一个代理类,但是接着打印beanB.getBeanA()时发现它就是指向BeanA的引用地址。其实BeanB中的属性BeanA拿到真正BeanA实例化的引用就是在第一次调用getBeanA()的时候,它就会调用之前自定义封装的TargetSource的getTarget()的方法,最后还是通过调用getBean()方法,从缓存中拿到BeanA的实例。

设计本意

到这里也算比较深刻的了解Spring中的循环依赖了,但是不知道你有没有想过,有参构造方法注入或@DependsOn注解导致启动的循环依赖错误和setter注入对比是真的无法解决而抛出异常吗?或者这可能也是为什么会设计三层缓存来去处理循环依赖的问题?

其实每个人可能都有自己的独特见解,就像一千个读者就有一千个哈姆雷特,而我们唯一能找到依据的可能就是Spring官方文档的说的那样,

总结

我们知道依赖注入的实现逻辑其实是非常复杂的,而循环依赖是其中不可避免的问题,在我看来,再结合IOC容器,它们三个才是共同组成了Spring中Bean实例化的整体,就像下面这张图,

而明白循环依赖及其解决的实现才是更深层次的了解Spring中Bean实例化过程的核心。同时Spring还有其他很多重要功能及实现,像AOP、BeanPostProcessor、Aware等等,如果说Bean是心脏,那它们更像是围绕着的血管一样前后遍布,加强扩展了Spring整体的功能。

举报

相关推荐

0 条评论