一次偶发项目循环依赖引发的spring循环依赖简析

一次偶发项目循环依赖引发的spring循环依赖简析

spring可以自行帮你解决循环依赖,当然前提是,不能使用构造器注入法来注入属性,否则在项目启动时,spring会直接抛出循环依赖的错误,无法启动。但是当使用@Autowired或者是setter注入时,spring却可以帮你解决循环依赖。那spring到底是如何帮助你解决循环依赖的呢?假如我构造器注入和setter注入混合使用时,spring会不会也乱了呢?

一,问题重现:

一个项目写久了,产品不停的加功能,不停地催促上线,没有时间重构,那在重用某些service方法的时候,就难免会产生循环依赖,而有时,这个依赖圈可能非常之大。由于种种原因,最后构造器注入和@Autowired注入混合使用了。一开始没出现过什么问题,偶然一次出现循环依赖问题了,但是只要重新构建一下项目,就没问题了。随后,这个问题就没断过。直到把所有的构造器注入全部改为@Autowired注入才正常。
代码如下:

public class Grand {
    private final Enterprise b;
    public Grand (Enterprise  b) {
        this.b = b;
    }
}

public class Enterprise   {

 @Autowired
    private Person c;
}

public class Person  {

  @Autowired
    private Grand a;
}

这三个类达成的jar包,有时可以启动成功,有时就会报循环依赖的问题。问题重现了,那到底是因为什么呢?

二,原因剖析:

要剖析原因,首先要知道spring加载bean的顺序,如下图:
摘自Java知音公众号
这个前提是,所有的类都使用@Autowired注入或者setter注入。但是为什么我之前的代码有时候能启动成功,有时候启动失败呢?
我分析如下:

  1. 启动异常

如果构造器注入的Grand要先创建,此时需要Enterprise和Person同时被创建好。首先创建Enterprise,暴露Enterprise工厂,填充Enterprise的属性Person,再创建Person,暴露Person的工厂,填充Person的属性Grand,发现Grand还没有创建,也没有提前暴露工厂,此时spring抛出异常,循环依赖。

  1. 启动正常

如果是@Autowired注入的Enterprise先创建,此时暴露工厂,填充属性Person,创建Person,暴露工厂,填充属性Grand,创建Grand发现需要Enterprise和Person,而这两个类都有提前暴露的工厂,则Grand可以顺利通过构造器创建。之后Enterprise和Person被顺利创建,放到单例的缓存中。启动成功。

三,为什么spring加载bean时读取的资源顺序不一样呢?

当使用jar包启动时,spring加载bean的资源顺序取决于jarFile中的顺序,jarFile其实是一种ZipFile,通过文件系统读到对应的资源,其顺序与文件读取的顺序有关。


public void print() throws Exception{
        URL rootDirURL = new URL("xxx.jar!/BOOT-INF/classes");
        URLConnection con = rootDirURL.openConnection();
        JarURLConnection jarCon = (JarURLConnection) con;
        JarFile jarFile = jarCon.getJarFile();

        for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
            JarEntry entry = entries.nextElement();
            String entryPath = entry.getName();
            System.out.println(entryPath);
        }
    }

我将发生异常的jar包和正常运行的jar包,分别读取并打印其资源顺序,果然发现两者顺序并不一致。
至此,构建之后就不会有循环依赖的问题解决了。

四,建议:

尽量提前做好业务规划,服务层之间的关系要理清楚之后再开始开发。若后期有其他的改动,可能导致循环依赖,则需要及时重构,抽象,避免循环依赖的产生。
最好的方式,还是全部都采用构造器注入,虽然增加属性时,写的麻烦一些,但是能在第一时间知晓循环依赖的产生,尽早修改重构。否则到以后,这个项目可能就没办法继续复用方法甚至无法增加新功能。当初偷的懒,到时候都会变成面前的拦路虎。

参考资料:
原来Spring Bean默认加载顺序是这样的
https://mp.weixin.qq.com/s/4avWQm9A9YaS2WC_Rxb0PQ
图解Spring循环依赖,看过之后再也不怕面试被问到了!
https://mp.weixin.qq.com/s/pZjfogfqqtMdFjiFdnMtLg

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值