循环引用
问题的产生
- 代码出现两个bean在创建时都相互引用了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}
}
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Springboot.class, args);
Object a = run.getBean("a");
}
- 我们在向容器获取A对象是出错了
1
2
3
4
5
6
7
8
9
10
11
12The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| a defined in file [D:\download\SpringBoot\springboot_demo05\target\classes\com\panther\springboot\mycompoent\A.class]
↑ ↓
| b defined in file [D:\download\SpringBoot\springboot_demo05\target\classes\com\panther\springboot\mycompoent\B.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.解决办法
方案一
自己创建对象,不使用IOC自动注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55public class A {
private B b;
public A(){
}
public A(B b){
this.b = b;
}
public void setB(B b) {
this.b = b;
}
public B getB() {
return b;
}
}
public class B {
private A a;
public B(){
}
public B(A a){
this.a = a;
}
public void setA(A a) {
this.a = a;
}
public A getA() {
return a;
}
}
private static final Logger logger = Logger.getLogger("logger");
public static void main(String[] args) {
SpringApplication.run(Springboot.class, args);
// Object a = run.getBean("a");
A a = new A();
B b = new B();
b.setA(a);
a.setB(b);
logger.info(String.valueOf(a.getB() == b)); //true
logger.info(String.valueOf(b.getA() == a)); //true
logger.info(String.valueOf(a.getB()); //com.panther.test@B5947
logger.info(String.valueOf(b.getA()); //com.panther.test@A5945
}DEBUG分析
- 发现在
new
a和b时 属性都是空的 - 向下继续运行时,好像还是产生了循环依赖,但为什么a.getB()能输出B的实体类
- 有点懵逼。。。
百度的拼凑版本解释: jvm在
new
一个对象时,并不会创建这个对象,只是向内存申请了一段空间,属于半初始化的实体类。方案二
Spring框架提供的解决办法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Springboot.class, args);
A a = run.getBean(A.class);
B b = run.getBean(B.class);
logger.info(String.valueOf(a.getB() == b)); //true
logger.info(String.valueOf(b.getA() == a)); //true
logger.info(b.getA());//com.panther.springboot.test.A@27e7c77f
logger.info(a.getB());//com.panther.springboot.test.B@6f70a21b
}
- 开启循环引用配置
1
spring.main.allow-circular-references: true
- spring 引入三级缓存去解决循环引用
1
2
3
4
5
6
7
8//一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 三级缓存 map中存放早期半初始化的的bean,并提前暴露
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); - 可以发现b实体类里的A属性是一个代理对象
1
2
3
4
5
6
7if (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;
}方案三
自己写一个代理去解决
1
2
3
4
5
6
7
8
9
public class MyAspectJ {
public void before(){
System.out.println("---------");
}
}
Comment