十一、循环依赖

十一、循环依赖

    1、依赖注入方式

        (1)、构造方法注入

            如果循环依赖通过构造方法注入会抛出BeanCurrentlyInCreationException异常。例如:A类通过构造函数注入需要B类的实例,而B类通过构造函数注入需要A类的实例,如果将A类和B类的bean配置为互相注入,则SpringIOC容器会在运行时检测到此循环依赖,并抛出BeanCurrentlyInCreationException异常。以下这种情况,编译不会通过。

            如果类A、类B的构造函数无参,只有new A()和new B()操作,启动会抛出java.lang.StackOverflowError错误。

public class A {
    private B b;
    /**
     * 通过构造方法注入
     */
    public A (B b){
        this.b = b;
    }
}
public class B {
    private A a;
    /**
     * 通过构造方法注入
     */
    public B(A a){
        this.a = a;
    }
}

        (2)、Setter方法注入 

            通过Setter方式注入能够解决循环依赖问题,但是注入方式需要是singleton,其他方式依然会报错。

public class A {
    private B b;
    /**
     * 通过set方法注入B
     */
    public void setB (B b){
        this.b = b;
    }
}
public class B {
    private A a;
    /**
     * 通过set方法注入A
     */
    public void setA(A a){
        this.a = a;
    }
}

        (3)、接口注入

    2、scope(作用域)

        Scope字面意思是范围,Spring中用来声明IOC器中的对象应该处的限定场景或者说该对象的存活空间,即在IOC器在对象进入相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。

        (1)、设置方式

            ①、注解

@Configuration
public class ProtoTypeConfig {
    @Scope("singleton")
    @Bean
    public A a() {
        return new A();
    }
}

            ②、xml配置

<bean id="a" class="com.thc.bpm.A" scope="singleton" />

        (2)、模式

            ①、singleton(不写默认)

                a、单例模式,全局有且仅有一个实例,也就是说在整个Spring IOC容器中,使用singleton定义的Bean将有且仅有只有一个实例。

            ②、prototype

                原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。

                prototype模式下出现循环依赖会抛出BeanCurrentlyInCreationException异常。

            ③、request

                对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。同时该Bean实例仅在当前HTTP Request请求内有效。

            ④、session

                对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同时该bean仅在当前HTTP session内有效。

            ⑤、globalsession

                每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。

            Session知识补充

                Session是用于存放用户与web服务器之间的会话,即服务器为客户端开辟的存储空间。Session存在的意义是为了提高安全性,它将关键数据存在服务器端,与cookie不同,cookie则是将数据存在客户端的浏览器中。Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。Tomcat中Session的默认失效时间为20分钟。

    3、DefaultSingletonBeanRegistry类

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    /**
     * 第一级缓存/单例缓存池
     * Map<Bean名称, Bean实例>
     * 存放已经完全初始化完成的Bean对象,从该缓存中取出的Bean可以直接使用
     */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    /**
     * 第二级缓存
     * Map<Bean名称, Bean实例>
     * 存放原始的Bean对象,只是进行了实例化,但没有初始化(尚未填充属性),用于解决循环依赖
     */
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
    /**
     * 第三级缓存
     * Map<Bean名称, ObjectFactory>
     * 存放可以生成Bean的Bean工厂对象(lamda表达式),用于解决循环依赖
     */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}

    4、解决循环依赖

        (1)、前提条件

            ①、通过Setter注入

                Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态(内部属性未填充)。而构造器是完成实例化的,没有办法处理中间态,所以构造器的循环依赖无法解决。  

            ②、Bean为单例

                只有单例的Bean才能通过三级缓存提前暴露解决循环依赖的问题,而非单例的Bean,每次从容器中获取都是一个新的实例,都会重新创建,所以非单例的Bean是没有缓存的,不会将其放在三级缓存中。

        (2)、流程

            假设类A、类B循环引用。

            ①、执行getSingleton()方法从一级缓存获取A实例,获取到返回,如果没有获取到,执行doCrateBean()方法实例化A,并将A放入三级缓存中。然后执行populateBean()方法对A进行初始化,此时发现依赖B。

            ②、执行getSingleton()方法从一级缓存获取B实例,获取到返回,如果没有获取到执行doCrateBean()方法实例化B,并将B放入三级缓存中。然后执行populateBean()方法对B进行初始化,此时发现B又依赖A。

            ③、从缓存一级缓存中查找,没有,再查二级缓存,再查三级缓存找到早期暴露的A,然后将A放到二级缓存里,并删除三级缓存里的A。如果有AOP代理先获取代理后的对象A,没有的话,就直接将A的原始对象注入B,继续执行对象B的属性填充和初始化。

            ④、B的属性填充和初始化完成后,将初始化完的B实例放到一级缓存里面(此时B里面的A依然是中间态)。然后继续执行A的属性填充操作,如果没有AOP代理,就直接从一级缓存拿到B,将B的实例注入A中,并将A放到一级缓存。

    5、循环依赖源码解析

        (1)、DefaultSingletonBeanRegistry源码

            该类定义三级缓存。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    /**
     * 第一级缓存/单例缓存池
     * 存放已经完全初始化完成的Bean对象,从该缓存中取出的Bean可以直接使用
     */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    /**
     * 第二级缓存
     * 存放原始的Bean对象,只是进行了实例化,但没有初始化(尚未填充属性),用于解决循环依赖
     */
    private final Map<String, Object> earlySingletonObjects = new HashMap(16);
    /**
     * 第三级缓存
     * 存放可以生成Bean的Bean工厂对象(lamda表达式),用于解决循环依赖
     */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    /**
     * 在Bean开始创建时放入,创建完成时会将其移出
     */
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));

    /**
     * 从一级缓存获取Bean
     */
    @Nullable
    public Object getSingleton(String beanName) {
        // 传入beanName,allowEarlyReference设置为true表示允许早期引用
        return this.getSingleton(beanName, true);
    }

    /**
     * 从一级缓存中获取Bean
     */
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Map<String, Object> singletonObjects,从第一级缓存中获取Bean
        Object singletonObject = this.singletonObjects.get(beanName);
        // isSingletonCurrentlyInCreation方法底层是判断singletonsCurrentlyInCreation的Set集合(存放正在创建的bean的集合)中是否包含beanName
        // 从第一级缓存获取结果为空 && bean没有正在创建
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            Map var4 = this.singletonObjects;
            // 同步代码块
            synchronized (this.singletonObjects) {
                // 从第二级缓存中查找
                singletonObject = this.earlySingletonObjects.get(beanName);
                // 从第二级缓存获取结果为空 && 允许早期引用
                if (singletonObject == null && allowEarlyReference) {
                    // 从第三级缓存查找
                    ObjectFactory<?> singletonFactory = (ObjectFactory) this.singletonFactories.get(beanName);
                    // 从第二级缓存获取结果不为空
                    if (singletonFactory != null) {
                        // 获取第三级缓存中beanName对应的Object
                        singletonObject = singletonFactory.getObject();
                        // 放入第二级缓存
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 从第三级缓存中移除
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

    /**
     * 重载方法
     * @param singletonFactory
     * singletonFactory可能为的实例化bean的lamda表达式
     */
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {......}
}

        (2)、AbstractBeanFactory源码

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    /**
     * 根据Bean名称,获取Bean实例
     */
    protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
        // beanName转换,获取去掉前缀的beanName,因为缓存里存的beanName没有以&为前缀的修饰符等
        String beanName = this.transformedBeanName(name);
        // 从缓存中获取bean
        Object sharedInstance = this.getSingleton(beanName);
        Object bean;
        if (sharedInstance != null && args == null) {
            ......
        } else {
            ......
            // 如果不是只检查类型,那就标记这个bean被创建
            if (!typeCheckOnly) {
                this.markBeanAsCreated(beanName);
            }
            try {
                ......
                //创建bean实例
                if (mbd.isSingleton()) {
                    // 这个getSingleton方法不是SingletonBeanRegistry的接口方法,是其实现类DefaultSingletonBeanRegistry的一个public重载方法
                    // getSingleton(String beanName, ObjectFactory<?> singletonFactory)重载方法的特点
                    // 是在执行singletonFactory.getObject();前后会执行beforeSingletonCreation(beanName);和afterSingletonCreation(beanName);
                    // 也就是保证这个Bean在创建过程中,放入正在创建的缓存池里,可以看到它实际创建bean调用的是我们的createBean方法
                    sharedInstance = this.getSingleton(beanName, () -> {
                        try {
                            return this.createBean(beanName, mbd, args);
                        } catch (BeansException var5) {
                            this.destroySingleton(beanName);
                            throw var5;
                        }
                    });
                    bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                } else if (mbd.isPrototype()) {......} else {......}
            } catch (BeansException var26) {
                ......
            }
        }
        ......
    }
}

        (3)、AbstractAutowireCapableBeanFactory源码

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    /**
     * 创建Bean实例
     */
    protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
        // 实例化后的对象会被封装在BeanWrapper对象
        BeanWrapper instanceWrapper = null;
        ......
        if (instanceWrapper == null) {
            // 使用构造器/工厂方法,创建bean对象,并且将对象包裹在BeanWrapper中
            instanceWrapper = this.createBeanInstance(beanName, mbd, args);
        }
        // 从BeanWrapper中获取bean的原始对象
        Object bean = instanceWrapper.getWrappedInstance();
        // 从BeanWrapper中获取bean的类信息
        Class<?> beanType = instanceWrapper.getWrappedClass();
        ......
        // earlySingletonExposure用于表示是否”提前暴露“原始对象的引用,用于解决循环依赖。
        // mbd.isSingleton()根据该scope设置的属性,判断是不是设置的单例模式
        // allowCircularReferences表示是否允许循环引用,对于单例Bean,该变量一般为true,但你也可以通过属性allowCircularReferences = false来关闭循环引用。
        // isSingletonCurrentlyInCreation(beanName) 表示当前bean必须在创建中。判断该beanName是否包含在DefaultSingletonBeanRegistry对象的singletonsCurrentlyInCreation变量中。
        boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
        if (earlySingletonExposure) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
            }
            // 传入ObjectFactory对象也就是lamda表达式,并将该beanName和objectFactory放入第三级缓存,并从第二级缓存移除
            this.addSingletonFactory(beanName, () -> {
                // getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法
                // 也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑
                // 比如在getEarlyBeanReference()里可以实现AOP的逻辑,参考自动代理创建器AbstractAutoProxyCreator实现了这个方法来创建代理对象
                // 若不需要执行AOP的逻辑,直接返回bean
                return this.getEarlyBeanReference(beanName, mbd, bean);
            });
        }

        Object exposedObject = bean;

        try {
            // 给bean注入相关的依赖
            this.populateBean(beanName, mbd, instanceWrapper);
            // 执行初始化回调方法
            // invokeAwareMethods - 激活Aware方法
            //  (1)、如果bean实现了BeanNameAware 接口,则将 beanName设值进去
            //  (2)、如果bean实现了BeanClassLoaderAware接口,则将 ClassLoader 设值进去
            //  (3)、如果bean实现了BeanFactoryAware接口,则将 beanFactory 设值进去
            // invokeInitMethods - 激活自定义的init方法
            //  (1)、Bean的初始化方法除了可以使用 init-method 属性(或者 @Bean(initMethod=''”)),还可以通过实现InitializingBean接口,并且在afterPropertiesSet 方法中实现自己初始化的业务逻辑。
            exposedObject = this.initializeBean(beanName, exposedObject, mbd);
        } catch (Throwable var18) {
            ......
        }
        // earlySingletonExposure:如果你的bean允许被早期暴露出去 也就是说可以被循环引用  那这里就会进行检查
        if (earlySingletonExposure) {
            // 从一级缓存获取数据,第二参数为false表示不会再去三级缓存里查。
            // 但是此时一级、二级缓存都没数据
            // 此处非常巧妙的一点:::因为上面各式各样的实例化、初始化的后置处理器都执行了,如果你在上面执行了这一句
            //  ((ConfigurableListableBeanFactory)this.beanFactory).registerSingleton(beanName, bean);
            // 那么此处得到的earlySingletonReference的引用最终会是你手动放进去的Bean最终返回,完美的实现了"偷天换日" 特别适合中间件的设计
            // 我们知道,执行完此doCreateBean后执行addSingleton()  其实就是把自己再添加一次  **再一次强调,完美实现偷天换日**
            Object earlySingletonReference = this.getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                // 经过了initializeBean()后,exposedObject还是没有变,那就直接返回。
                // initializeBean会调用后置处理器,这个时候可以生成一个代理对象,就不会相等,进入else
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                    // allowRawInjectionDespiteWrapping这个值默认是false
                    // hasDependentBean:若它有依赖的bean 那就需要继续校验,没有继续执行
                } else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
                    // 获取它所依赖的一系列Bean
                    String[] dependentBeans = this.getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
                    String[] var12 = dependentBeans;
                    int var13 = dependentBeans.length;
                    // 循环遍历所依赖的Bean
                    for(int var14 = 0; var14 < var13; ++var14) {
                        String dependentBean = var12[var14];
                        // removeSingletonIfCreatedForTypeCheckOnly这个方法在AbstractBeanFactory里面
                        // 就是判断到该dependentBean是不是没有创建完成,如果没有创建完成,返回true。
                        // 否则创建完成就返回false,进入我们的if里面,表示所谓的真正依赖。
                        //(解释:就是真的需要依赖它先实例化,才能实例化自己的依赖)
                        // 就是如果判断到该dependentBean并没有在创建中,那就把它从所有缓存中移除,并且返回true,后续报错提示有依赖的bean没有创建。
                        // 否则(比如确实在创建中) 那就返回false,进入我们的if里面,表示所谓的真正依赖。
                        //(解释:就是真的需要依赖它先实例化,才能实例化自己的依赖)
                        if (!this.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 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }
        }

        try {
            this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
            return exposedObject;
        } catch (BeanDefinitionValidationException var16) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", var16);
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值