多个bean之间相互依赖,形成了一个闭环。比如:A依赖于B、B依赖于C、C依赖于A。
通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,属性互相引用的场景。
我们AB循环依赖问题只要A的注入方式是setter且singleton ,就不会有循环依赖问题。
Spring容器循环依赖报错演示BeanCurrentlylnCreationException
循环依赖现象在spring容器中注入依赖的对象,有2种情况
构造器方式注入依赖(不可行)
@Component
public class ServiceB{
private ServiceA serviceA;
public ServiceB(ServiceA serviceA){
this.serviceA = serviceA;
}
}
@Component
public class ServiceA{
private ServiceB serviceB;
public ServiceA(ServiceB serviceB){
this.serviceB = serviceB;
}
}
public class ClientConstructor{
public static void main(String[] args){
new ServiceA(new ServiceB(new ServiceA()));//这会抛出编译异常
}
}
以set方式注入依赖(可行)
@Component
public class ServiceBB{
private ServiceAA serviceAA;
public void setServiceAA(ServiceAA serviceAA){
this.serviceAA = serviceAA;
System.out.println("B里面设置了A");
}
}
@Component
public class ServiceAA{
private ServiceBB serviceBB;
public void setServiceBB(ServiceBB serviceBB){
this.serviceBB = serviceBB;
System.out.println("A里面设置了B");
}
}
public class ClientSet{
public static void main(String[] args){
//创建serviceAA
ServiceAA a = new ServiceAA();
//创建serviceBB
ServiceBB b = new ServiceBB();
//将serviceA入到serviceB中
b.setServiceAA(a);
//将serviceB法入到serviceA中
a.setServiceBB(b);
}
}
public class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
System.out.println("A call setB.");
}
}
public class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
System.out.println("B call setA.");
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ClientSpringContainer {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
A a = context.getBean("a", A.class);
B b = context.getBean("b", B.class);
}
}
默认的单例(Singleton)的场景是支持循环依赖的,不报错
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="a" class="com.lun.interview.circular.A">
<property name="b" ref="b">property>
bean>
<bean id="b" class="com.lun.interview.circular.B">
<property name="a" ref="a">property>
bean>
beans>
输出结果
00:00:25.649 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6d86b085
00:00:25.828 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [beans.xml]
00:00:25.859 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'
00:00:25.875 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
B call setA.
A call setB.
原型(Prototype)的场景是不支持循环依赖的,会报错
重要结论(spring内部通过3级缓存来解决循环依赖) - DefaultSingletonBeanRegistry
只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
package org.springframework.beans.factory.support;
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
/** 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 HashMap<>(16);
...
}
实例化 - 内存中申请一块内存空间,如同租赁好房子,自己的家当还未搬来。
初始化属性填充 - 完成属性的各种赋值,如同装修,家具,家电进场。
3个Map和四大方法,总体相关对象
第一层singletonObjects存放的是已经初始化好了的Bean,
第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean
A / B两对象在三级缓存中的迁移说明
Spring创建 bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化
每次创建 bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个
当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完beanB填充属性时又发现它依赖了beanA又是同样的流程,
不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA.所以不需要继续创建,用它注入 beanB,完成 beanB的创建
既然 beanB创建好了,所以 beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成
Spring解决循环依赖依靠的是Bean的**"中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态—>半成品。**实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。”对
Spring为了解决单例的循坏依赖问题,使用了三级缓存:
假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。
Spring解决循环依赖过程:
redis基本类型:
其他redis的类型
最常用:
同时设置/获取多个键值:
数值增减
获取字符串长度:STRLEN key
分布式锁:
应用场景:
Redis的Hash类型相当于Java中Map
一次设置一个字段值: HSET key field value
一次获取一个字段值: HGET key field
一次设置多个字段值: HMSET key field value [field value …]
一次获取多个字段值: HMGET key field [field …]
获取所有字段值: HGETALL key
获取某个key内的全部数量: HLEN
删除一个key: HDEL
应用场景 - 购物车早期,当前小中厂可用
向列表左边添加元素: LPUSH key value [value …]
向列表右边添加元素: RPUSH key value [value …]
查看列表: LRANGE key start stop
获取列表中元素的个数 LLEN key
应用场景 - 微信文章订阅公众号
添加元素: SADD key member [member …]
删除元素: SREM key member [member …]
获取集合中的所有元素: SMEMBERS key
判断元素是否在集合中: SISMEMBER key member
获取集合中的元素个数: SCARD key
从集合中随机弹出一个元素,元素不删除: SRANDMEMBER key [数字]
从集合中随机弹出一个元素,出一个删一个: SPOP key[数字]
集合运算:
应用场景:
向有序集合中加入一个元素和该元素的分数
添加元素: ZADD key score member [score member …]
按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素: ZRANGE key start stop [WITHSCORES]
获取元素的分数: ZSCORE key member
删除元素: ZREM key member [member …]
获取指定分数范围的元素: ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
增加某个元素的分数: ZINCRBY key increment member
获取集合中元素的数量: ZCARD key
获得指定分数范围内的元素个数: ZCOUNT key min max
按照排名范围删除元素: ZREMRANGEBYRANK key start stop
获取元素的排名:
应用场景: