说明
-
循环依赖是一个大家讨论很多的话题,它更多是一个工程上的问题而不是技术问题,我们需要首先有一定的认知:
- 如同两个人相互帮忙,两个类之间你调用我的,我调用你的是很正常也很自然的需求模型。
- 单一依赖确实有好处,改动一个最顶层类时不需要在意对底部类的影响,但是从本来就自然的模型非要理顺的话就需要额外付出代价,例如额外的拆分类。
-
循环依赖可以分这几种:
- 从小的来说是类之间的相互引用。
- 再大一点的来说是同一个项目下不同模块之间的引用。
- 再再大一点的来说涉及到微服务或不同类库之间引用。
对于微服务级别或是模块级别的引用来说解决循环依赖是有必要的,因为这可能牵扯到不同人分工协作的问题,而类之间尤其是同一模块下的类之间是否禁止循环依赖实际上是有争议的,本文只讨论同一模块下的类之间的循环引用。
-
一些解决循环依赖的方法类似
@Lazy
、getBean
等实际上解决的不是循环依赖,而是解决的springboot
启动时的循环依赖检测,但本质上它们还是相互引用,所以这里不讨论这些方法,只讨论拆分类的方法。 -
个人目前比较认同的是同一个人写的同一个模块下的功能是可以循环依赖的,增加额外的拆分类反而会增加复杂度及影响效率,但
springboot 2.6
版本之后默认禁止了循环依赖,所以个人也在思考,如果想要拆分的化要怎么拆分,目前总结了如下两种不同类型的循环引用拆分示例。
示例
情形一
-
最常见的
老师
、学生
这种或是主表
、子表
相互关联的:@Component public class Teacher { @Autowired private Student student; public void method() { //获取某教师下学生类别 List<String> students = student.getStudentsByTeacher("xxx"); System.out.println(students); } }
@Component public class Student { @Autowired private Teacher teacher; public void method() { //获取学生归属的教师 String teacherStr = teacher.getTeacherByStudent("xxx"); System.out.println(teacherStr); } }
-
这种拆分比较简单,类似数据库多对多的中间表,我们也创建一个中间类,然后
Teacher
和Student
类不要依赖彼此,直接抽取方法到中间类中或是都引用中间类:@Component public class TeacherStudent { @Autowired private Teacher teacher; @Autowired private Student student; public void method1() { //获取某教师下学生类别 List<String> students = student.getStudentsByTeacher("xxx"); System.out.println(students); } public void method2() { //获取学生归属的教师 String teacherStr = teacher.getTeacherByStudent("xxx"); System.out.println(teacherStr); } }
情形二
-
另一种常用的场景是引用第三方类库
A
,然后在配置类B
中用@Bean
来实例化,而类A
是通过读取数据库中的配置(通过类C
)来组装参数,而当数据库配置变更时(类C
中更新),由于参数变化同时也要重置类A
实例,我们在集成微信、钉钉等SDK时会经常遇到此情况,如果直接按照此逻辑写的话,就是下述的代码://B本身是个配置类 @Configuration class B { @Autowired private C c; @Bean public A init(){ A a = new A(); //引用c的数据库中数据来组装成A实例 a.setProp(c.getProp()); return a; } }
@Component class C { @Autowired private A a; public void update(){ //修改数据库相关后,又来重置A实例 a.reset(); } }
-
此情况下最主要的耦合就是
类A
需要类C
的数据来作为配置项,所以把这个耦合独立出来,而类B
中去除类C
的引用,仅仅是生成类A
的bean
://用@PostConstruct @Component public class D { @Autowired private A a; @Autowired private C c; @PostConstruct public void init(){ a.setProp(c.getProp()); } } //或是@Autowired注解到方法上 @Component public class D { @Autowired public void init(A a, C c) { a.setProp(c.getProp()); } }
-
虽然从需求上
类A
依赖类C
,但本质上类A
并不需要依赖任何类,和第一种情况不同的是类A
是一个第三方的类库,我们无法修改其引用及方法,而其本身并不是个bean
,需要我们额外去操作。
结果
- 上述的情况都是额外增加一个拆分类来处理,这样无形中增加了代码量,尤其是第一种情形太常见了,除非是项目初始时就规定好禁止
service
层互相调用,而是单独再划分一层来处理(类似阿里的manager
层),否则的话个人宁愿用@Lazy
来解决掉循环依赖的报错。 - 解决循环依赖上述同模块内的相对简单些,只是增加代码量而已,当涉及到模块或微服务时,则完全不一样,要考虑业务逻辑及架构等一系列问题,感觉很是麻烦。
- 以上只是个人见解,有更好的观点可发到评论区一起讨论下。
1.本站内容仅供参考,不作为任何法律依据。用户在使用本站内容时,应自行判断其真实性、准确性和完整性,并承担相应风险。
2.本站部分内容来源于互联网,仅用于交流学习研究知识,若侵犯了您的合法权益,请及时邮件或站内私信与本站联系,我们将尽快予以处理。
3.本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
4.根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
5.本站是非经营性个人站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途
暂无评论内容