Maven Build 循环依赖的危害及发现解决

提到maven循环依赖是maven 解析的痛点。循环依赖的存在会使maven build阶段出现build fail严重的会出现死循环,进而导致maven栈溢出。这篇文章我们将从最简单的maven循环依赖讲起,结合笔者的项目经历对循环依赖进行分析

最简单的循环依赖

Sample 项目

为了更好的理解maven循环依赖。我们创建一个最简单的sample项目:maven-test。这是一个maven多模块项目,包含a-component和b-component这两个模块。分析一下这个项目的pom.xml和java类,两个模块的依赖关系是a->b->a,两个模块中的类的依赖关系是A->B->A。 这就是最简单的循环依赖的case。

maven-test(parent) pom.xml



    4.0.0

    org.example
    maven-test
    1.0-SNAPSHOT
    
        a-component
        b-component
    
    pom
    
        
            
                org.example
                a-component
                1.0-SNAPSHOT
            
            
                org.example
                b-component
                1.0-SNAPSHOT
            
        
    

    
        
            org.codehaus.mojo
            build-helper-maven-plugin
            3.2.0
        
    


a-component pom.xml



    
        maven-test
        org.example
        1.0-SNAPSHOT
    
    4.0.0

    a-component
    
        
            org.example
            b-component
        
    

a-component A.java

public class A {
    private B b;
}

b-component pom.xml



    
        maven-test
        org.example
        1.0-SNAPSHOT
    
    4.0.0

    b-component
    
        
            org.example
            a-component
        
    

b-component B.java

public class B {
    private A a;
}

使用maven定位最简单循环依赖

此时我们run一下maven clean install命令。通过观察log,发现对于多模块项目的各个模块,maven不仅能够帮我们判断项目中是否存在循环依赖,更会帮我们找到具体的循环依赖。

LM-SHC-16507782:maven-test xianghan$ mvn clean install
[INFO] Scanning for projects...
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='org.example:b-component:1.0-SNAPSHOT'}' and 'Vertex{label='org.example:a-component:1.0-SNAPSHOT'}' introduces to cycle in the graph org.example:a-component:1.0-SNAPSHOT --> org.example:b-component:1.0-SNAPSHOT --> org.example:a-component:1.0-SNAPSHOT @ 
[ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='org.example:b-component:1.0-SNAPSHOT'}' and 'Vertex{label='org.example:a-component:1.0-SNAPSHOT'}' introduces to cycle in the graph org.example:a-component:1.0-SNAPSHOT --> org.example:b-component:1.0-SNAPSHOT --> org.example:a-component:1.0-SNAPSHOT -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectCycleException

最简单循环依赖的解决思路

解决循环依赖的方式就是打断循环依赖链。无论是使用build-helper-maven-plugin的方式或者项目重构,思路都是抽出耦合部分代码创建为新的component,之后将a-component和b-component的依赖关系转向这个新的component即可。下面我们介绍一下使用build-helper-maven-plugin进行解决的方法。

使用build-helper-maven-plugin

  1. 我们在a-component和b-component之外,创建一个新的模块c-component。
  2. 修改a-component和b-component的pom.xml,将他们的依赖关系从彼此转向c-component。此时项目中各模块的依赖关系从 a->b->a改为了a->c,b->c,已经不存在循环依赖的问题,但是此时会发现a-component和b-component会由于找不到类文件而报错。
  3. 在c-component中添加build-helper-maven-plugin插件,通过build-helper-maven-plugin整合a-component和b-component中耦合部分的代码。

   
           
               org.codehaus.mojo
               build-helper-maven-plugin
               3.2.0
               
                   
                       add-source
                       generate-sources
                       
                           add-source
                       
                       
                           
                               ../a-component/src/main/java
                               ../b-component/src/main/java
                           
                       
                   
               
           
   

如何发现循环依赖

在一个复杂系统中,我们需要同时release几千个libraries。这些libraries由于数量众多,分别属于不同的部门, 因此很难管理,没有人清楚这些libraries中是不是存在循环依赖。
为了release工作的顺利开展,我们需要提前确认这些libraries中是否存在循环依赖,如果存在循环依赖的话,我们需要定位出这个循环依赖。

解决思路

libraries之间的依赖关系可以通过有向图这种数据结构来描述。所以,我们现在的任务就可以通过三个步骤来解决。

得到有向图

有向图的基本元素是节点和弧。我们来规定一下在我们的case中节点和弧分别指代的内容。

节点:每一个需要被release的library都是图中的一个节点。
:项目间的依赖关系,从依赖方指向被依赖方。

接下来就是通过计算来得到有向图了。经过各种尝试后,笔者最后通过下面这种方法得到以每一个节点为起点的弧的信息。

  1. git clone下载这些library的源码
  2. 运行mvn dependency:list获取library的依赖关系

至此,我们已经得到了由libraries和它们之间的依赖关系组成的有向图了。

判断有向图是否有环

我们假设上一步得到的有向图如图1所示。检查有向图是否有环的算法已经比较多了,我们简单介绍其中的一个办法,供大家参考。

  1. 求出图中所有节点的出度。
  2. 将所有出度==0的节点放入队列
  3. 当队列不空时进入循环,弹出队首节点h,把节点h所依赖的节点的出度减1,如果这些节点的出度变为0,则将这些节点入队。队列为空则退出循环。


    图1:有向图

    图2:环的计算过程
  4. 循环结束时判断已经访问过(进入过队列)的节点数是否等于 n。等于 n 说明全部节点都被访问过,无环;反之,则有环。


    image.png

从上图看,还剩下Project1,Project2, Project 3 此时依赖有环。

有环的话,我们需要找出其中的一个环

根据上一步的计算结果,已经确定在这些libraries中存在循环依赖,下一步的任务就是找到循环依赖中的一个环,抽丝剥茧的解决项目中的循环依赖。

通过在上一个小节中的知识,我们从有向图的正向(出度为0)出发,可以排除掉一批进入过队列的节点以及与之对应的弧。同样的,我们也可以从有向图的反向(入度为0)出发,排除掉另一批进入过队列的节点和对应的弧。

此时,我们沿着任意一个节点的弧走下去,无论这个节点本身是否处于一个环中,沿着它出发必然能够找到一个环。算法如下:

  1. 创建空的队列Q。
  2. 任选一个节点。将节点标志成已读之后放入到队列Q中。
  3. 以上次进入队列的节点H为起点,任选一条弧,找到弧的终点的节点T。检查T的节点标志是否为已读,如果是,则进入步骤4;否则,将节点标志成已读之后放入到队列Q中,重复步骤3。


    enqueue sequence
  1. 将队中元素依次出队并与T进行比较,直到出队元素为T。此时,T以及队列中剩余的元素构成循环依赖。


    result

你可能感兴趣的:(Maven Build 循环依赖的危害及发现解决)