SpringBoot:解决循环依赖

SpringBoot:解决循环依赖
1、什么是循环依赖?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

在这里插入图片描述

spring的单例对象的初始化主要分为三步:

1、实例化:其实也就是调用对象的构造方法实例化对象。
2、注入:填充属性,这一步主要是对bean的依赖属性进行填充。
3、初始化:属性注入后,执行自定义初始化。

2、Spring为了解决单例的循环依赖问题,使用了三级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

这三级缓存分别指:
singletonObjects:单例对象的cache,一级缓存。

earlySingletonObjects :提前暴光的单例对象的Cache,二级缓存。

singletonFactories : 单例对象工厂的cache,三级缓存。
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就是:

 @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            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 = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }

分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:
从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

在这里插入图片描述

3.1、单例的setter注入
@Service
public class ServiceA {

	@Autowired
	private ServiceB serviceB;
}

@Service
public class ServiceB {

	@Autowired
	private ServiceA serviceA;
}

下面用一张图告诉你,spring是如何解决循环依赖的:

在这里插入图片描述

3.2、多例的setter注入
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceA {

	@Autowired
	private ServiceB serviceB;
}

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceB {

	@Autowired
	private ServiceA serviceA;
}

可能你会认为这种情况spring容器启动会报错,其实是不会,为什么呢?其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了beanFactory.preInstantiateSingletons()方法:能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。如何让它提前初始化bean呢?只需要再定义一个单例的类,在它里面注入serviceA:

@Service
public class ServiceC {

    @Autowired
    private ServiceA serviceA;
}

重新启动程序,执行结果:果然出现了循环依赖。

注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。

3.3、构造器注入
@Service
public class ServiceA {

	public ServiceA(ServiceB serviceB) {
	}
}

@Service
public class ServiceB {

	public ServiceB(ServiceA serviceA) {
	}
}

出现了循环依赖,为什么呢?看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。

3.4、单例的代理对象setter注入

这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。

@Service
@EnableAsync
public class ServiceA {

	@Autowired
	private ServiceB serviceB;

	@Async
	public void test1() {
		System.out.println("async");
	}
}

@Service
public class ServiceB {

	@Autowired
	private ServiceA serviceA;
}

程序启动会报错,出现了循环依赖:

在这里插入图片描述

说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存是否存在, 代理对象和原始对象是否相等。到此发现第二级缓存存在并且代理对象和原始对象是不相等,因此抛出上面异常。如果这时候把ServiceA改个名字,改成:ServiceC,其他的都不变。

@Service
public class ServiceB {

	@Autowired
	private ServiceC serviceC;
}

@Service
@EnableAsync
public class ServiceC {

	@Autowired
	private ServiceB serviceB;

	@Async
	public void test1() {
		System.out.println("async");
	}
}


再重新启动一下程序,成功了,没有报错了!这又是为什么?
这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以ServiceA比ServiceB先加载,而改了文件名称之后,ServiceB比ServiceC先加载。为什么ServiceB比ServiceC先加载就没问题呢?答案在下面这张图中:

在这里插入图片描述

这种情况ServiceC第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。

3.5、DependsOn循环依赖
@Service
@DependsOn("serviceB")
public class ServiceA {
	
}

@Service
@DependsOn("serviceA")
public class ServiceB {
	
}

这个例子中本来如果ServiceA和ServiceB都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题。在AbstractBeanFactory类的doGetBean方法的这段代码中:初始化bean前,它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
## springboot整合thymeleaf ### 1. 导入起步依赖 ```xml org.springframework.boot spring-boot-starter-thymeleaf ``` ### 2. 更改引入版本 ```xml 3.0.2.RELEASE 2.1.1 ``` > 1. springboot自带的thymeleaf依赖为2.1.3版本,使用thymeleaf-layout-dialect版本为2以下版本。 > 2. 使用3或3以上的thymeleaf时,需要thymeleaf-layout-dialect的版本为2或以上。 > 3. 锁定thymeleaf版本时不能使用thymeleaf.version标签,会和springboot内部的依赖标签冲突。应当使用springboot-thymeleaf.version标签来锁定版本。 ### 3. 配置文件配置 ```properties spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.check-template-location=true spring.thymeleaf.suffix=.html spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.content-type=text/html spring.thymeleaf.mode=HTML spring.thymeleaf.cache=false ``` > spring.thymeleaf.cache为缓存,需要热部署时,需要设置为false ## 语法 ### 1. 替换标签体内容 ```html 显示欢迎 显示欢迎 ``` ### 2. 替换属性 ```html 显示欢迎 ``` ### 3. 在表达式中访问属性域 ```html 访问属性域 访问请求域 方式一 访问请求域 方式二 访问Session域 访Session域 方式一 访问Application域 方式一 ``` ### 4. 解析url地址 ```html 解析URL地址,获取ContextPath的值 @{}是把ContextPath的值附加到指定的地址前 @{}是把ContextPath的值附加到指定的地址前 ``` ### 5. 直接执行表达式 ```html 直接执行表达式 无转义效果 : [[${attrRequestScope}]] 有转义效果 : [(${attrRequestScope})] ``` ### 6. 分支与迭代 #### 1. if 判断 ```html if判断字符串是否为空 <p th

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一枚尘世中迷途小书童

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值